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 };