/ src / reopen-project-menu-manager.js
reopen-project-menu-manager.js
  1  const { CompositeDisposable } = require('event-kit');
  2  const path = require('path');
  3  
  4  module.exports = class ReopenProjectMenuManager {
  5    constructor({ menu, commands, history, config, open }) {
  6      this.menuManager = menu;
  7      this.historyManager = history;
  8      this.config = config;
  9      this.open = open;
 10      this.projects = [];
 11  
 12      this.subscriptions = new CompositeDisposable();
 13      this.subscriptions.add(
 14        history.onDidChangeProjects(this.update.bind(this)),
 15        config.onDidChange(
 16          'core.reopenProjectMenuCount',
 17          ({ oldValue, newValue }) => {
 18            this.update();
 19          }
 20        ),
 21        commands.add('atom-workspace', {
 22          'application:reopen-project': this.reopenProjectCommand.bind(this)
 23        })
 24      );
 25  
 26      this.applyWindowsJumpListRemovals();
 27    }
 28  
 29    reopenProjectCommand(e) {
 30      if (e.detail != null && e.detail.index != null) {
 31        this.open(this.projects[e.detail.index].paths);
 32      } else {
 33        this.createReopenProjectListView();
 34      }
 35    }
 36  
 37    createReopenProjectListView() {
 38      if (this.reopenProjectListView == null) {
 39        const ReopenProjectListView = require('./reopen-project-list-view');
 40        this.reopenProjectListView = new ReopenProjectListView(paths => {
 41          if (paths != null) {
 42            this.open(paths);
 43          }
 44        });
 45      }
 46      this.reopenProjectListView.toggle();
 47    }
 48  
 49    update() {
 50      this.disposeProjectMenu();
 51      this.projects = this.historyManager
 52        .getProjects()
 53        .slice(0, this.config.get('core.reopenProjectMenuCount'));
 54      const newMenu = ReopenProjectMenuManager.createProjectsMenu(this.projects);
 55      this.lastProjectMenu = this.menuManager.add([newMenu]);
 56      this.updateWindowsJumpList();
 57    }
 58  
 59    static taskDescription(paths) {
 60      return paths
 61        .map(path => `${ReopenProjectMenuManager.betterBaseName(path)} (${path})`)
 62        .join(' ');
 63    }
 64  
 65    // Windows users can right-click Atom taskbar and remove project from the jump list.
 66    // We have to honor that or the group stops working. As we only get a partial list
 67    // each time we remove them from history entirely.
 68    async applyWindowsJumpListRemovals() {
 69      if (process.platform !== 'win32') return;
 70      if (this.app === undefined) {
 71        this.app = require('remote').app;
 72      }
 73  
 74      const removed = this.app
 75        .getJumpListSettings()
 76        .removedItems.map(i => i.description);
 77      if (removed.length === 0) return;
 78      for (let project of this.historyManager.getProjects()) {
 79        if (
 80          removed.includes(
 81            ReopenProjectMenuManager.taskDescription(project.paths)
 82          )
 83        ) {
 84          await this.historyManager.removeProject(project.paths);
 85        }
 86      }
 87    }
 88  
 89    updateWindowsJumpList() {
 90      if (process.platform !== 'win32') return;
 91      if (this.app === undefined) {
 92        this.app = require('remote').app;
 93      }
 94  
 95      this.app.setJumpList([
 96        {
 97          type: 'custom',
 98          name: 'Recent Projects',
 99          items: this.projects.map(project => ({
100            type: 'task',
101            title: project.paths
102              .map(ReopenProjectMenuManager.betterBaseName)
103              .join(', '),
104            description: ReopenProjectMenuManager.taskDescription(project.paths),
105            program: process.execPath,
106            args: project.paths.map(path => `"${path}"`).join(' '),
107            iconPath: path.join(
108              path.dirname(process.execPath),
109              'resources',
110              'cli',
111              'folder.ico'
112            ),
113            iconIndex: 0
114          }))
115        },
116        { type: 'recent' },
117        {
118          items: [
119            {
120              type: 'task',
121              title: 'New Window',
122              program: process.execPath,
123              args: '--new-window',
124              description: 'Opens a new Atom window'
125            }
126          ]
127        }
128      ]);
129    }
130  
131    dispose() {
132      this.subscriptions.dispose();
133      this.disposeProjectMenu();
134      if (this.reopenProjectListView != null) {
135        this.reopenProjectListView.dispose();
136      }
137    }
138  
139    disposeProjectMenu() {
140      if (this.lastProjectMenu) {
141        this.lastProjectMenu.dispose();
142        this.lastProjectMenu = null;
143      }
144    }
145  
146    static createProjectsMenu(projects) {
147      return {
148        label: 'File',
149        submenu: [
150          {
151            label: 'Reopen Project',
152            submenu: projects.map((project, index) => ({
153              label: this.createLabel(project),
154              command: 'application:reopen-project',
155              commandDetail: { index: index, paths: project.paths }
156            }))
157          }
158        ]
159      };
160    }
161  
162    static createLabel(project) {
163      return project.paths.length === 1
164        ? project.paths[0]
165        : project.paths.map(this.betterBaseName).join(', ');
166    }
167  
168    static betterBaseName(directory) {
169      // Handles Windows roots better than path.basename which returns '' for 'd:' and 'd:\'
170      const match = directory.match(/^([a-z]:)[\\]?$/i);
171      return match ? match[1] + '\\' : path.basename(directory);
172    }
173  };