/ src / history-manager.js
history-manager.js
  1  const { Emitter, CompositeDisposable } = require('event-kit');
  2  
  3  // Extended: History manager for remembering which projects have been opened.
  4  //
  5  // An instance of this class is always available as the `atom.history` global.
  6  //
  7  // The project history is used to enable the 'Reopen Project' menu.
  8  class HistoryManager {
  9    constructor({ project, commands, stateStore }) {
 10      this.stateStore = stateStore;
 11      this.emitter = new Emitter();
 12      this.projects = [];
 13      this.disposables = new CompositeDisposable();
 14      this.disposables.add(
 15        commands.add(
 16          'atom-workspace',
 17          { 'application:clear-project-history': this.clearProjects.bind(this) },
 18          false
 19        )
 20      );
 21      this.disposables.add(
 22        project.onDidChangePaths(projectPaths => this.addProject(projectPaths))
 23      );
 24    }
 25  
 26    destroy() {
 27      this.disposables.dispose();
 28    }
 29  
 30    // Public: Obtain a list of previously opened projects.
 31    //
 32    // Returns an {Array} of {HistoryProject} objects, most recent first.
 33    getProjects() {
 34      return this.projects.map(p => new HistoryProject(p.paths, p.lastOpened));
 35    }
 36  
 37    // Public: Clear all projects from the history.
 38    //
 39    // Note: This is not a privacy function - other traces will still exist,
 40    // e.g. window state.
 41    //
 42    // Return a {Promise} that resolves when the history has been successfully
 43    // cleared.
 44    async clearProjects() {
 45      this.projects = [];
 46      await this.saveState();
 47      this.didChangeProjects();
 48    }
 49  
 50    // Public: Invoke the given callback when the list of projects changes.
 51    //
 52    // * `callback` {Function}
 53    //
 54    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 55    onDidChangeProjects(callback) {
 56      return this.emitter.on('did-change-projects', callback);
 57    }
 58  
 59    didChangeProjects(args = { reloaded: false }) {
 60      this.emitter.emit('did-change-projects', args);
 61    }
 62  
 63    async addProject(paths, lastOpened) {
 64      if (paths.length === 0) return;
 65  
 66      let project = this.getProject(paths);
 67      if (!project) {
 68        project = new HistoryProject(paths);
 69        this.projects.push(project);
 70      }
 71      project.lastOpened = lastOpened || new Date();
 72      this.projects.sort((a, b) => b.lastOpened - a.lastOpened);
 73  
 74      await this.saveState();
 75      this.didChangeProjects();
 76    }
 77  
 78    async removeProject(paths) {
 79      if (paths.length === 0) return;
 80  
 81      let project = this.getProject(paths);
 82      if (!project) return;
 83  
 84      let index = this.projects.indexOf(project);
 85      this.projects.splice(index, 1);
 86  
 87      await this.saveState();
 88      this.didChangeProjects();
 89    }
 90  
 91    getProject(paths) {
 92      for (var i = 0; i < this.projects.length; i++) {
 93        if (arrayEquivalent(paths, this.projects[i].paths)) {
 94          return this.projects[i];
 95        }
 96      }
 97  
 98      return null;
 99    }
100  
101    async loadState() {
102      const history = await this.stateStore.load('history-manager');
103      if (history && history.projects) {
104        this.projects = history.projects
105          .filter(p => Array.isArray(p.paths) && p.paths.length > 0)
106          .map(p => new HistoryProject(p.paths, new Date(p.lastOpened)));
107        this.didChangeProjects({ reloaded: true });
108      } else {
109        this.projects = [];
110      }
111    }
112  
113    async saveState() {
114      const projects = this.projects.map(p => ({
115        paths: p.paths,
116        lastOpened: p.lastOpened
117      }));
118      await this.stateStore.save('history-manager', { projects });
119    }
120  }
121  
122  function arrayEquivalent(a, b) {
123    if (a.length !== b.length) return false;
124    for (var i = 0; i < a.length; i++) {
125      if (a[i] !== b[i]) return false;
126    }
127    return true;
128  }
129  
130  class HistoryProject {
131    constructor(paths, lastOpened) {
132      this.paths = paths;
133      this.lastOpened = lastOpened || new Date();
134    }
135  
136    set paths(paths) {
137      this._paths = paths;
138    }
139    get paths() {
140      return this._paths;
141    }
142  
143    set lastOpened(lastOpened) {
144      this._lastOpened = lastOpened;
145    }
146    get lastOpened() {
147      return this._lastOpened;
148    }
149  }
150  
151  module.exports = { HistoryManager, HistoryProject };