UNPKG

atom-nuclide

Version:

A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.

386 lines (342 loc) 14.1 kB
Object.defineProperty(exports, '__esModule', { value: true }); /* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _commonsAtomContextMenu2; function _commonsAtomContextMenu() { return _commonsAtomContextMenu2 = _interopRequireDefault(require('../../commons-atom/ContextMenu')); } var _atom2; function _atom() { return _atom2 = require('atom'); } var _FileTreeConstants2; function _FileTreeConstants() { return _FileTreeConstants2 = require('./FileTreeConstants'); } var _FileTreeStore2; function _FileTreeStore() { return _FileTreeStore2 = require('./FileTreeStore'); } var _commonsNodeNuclideUri2; function _commonsNodeNuclideUri() { return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri')); } // It's just atom$ContextMenuItem with an optional `callback` property added. // I wish flow would let add it in a more elegant way. var FILE_TREE_CSS = '.nuclide-file-tree'; var NEW_MENU_PRIORITY = 0; var ADD_PROJECT_MENU_PRIORITY = 1000; var SOURCE_CONTROL_MENU_PRIORITY = 2000; var MODIFY_FILE_MENU_PRIORITY = 3000; var SPLIT_MENU_PRIORITY = 4000; var TEST_SECTION_PRIORITY = 5000; var SHOW_IN_MENU_PRIORITY = 6000; /** * This context menu wrapper exists to address some of the limitations in the ContextMenuManager: * https://atom.io/docs/api/latest/ContextMenuManager. * * Specifically, a context menu item would often like to know which file (or directory) the user * right-clicked on in the file tree when selecting the menu item. The fundamental problem is that * the way a menu item is notified that it was selected is that the Atom command associated with * the item is fired. By the time the function associated with the command is called, the state * with which the menu item was created is lost. Here we introduce a pattern where the callback * registered with the command can get the selection via the FileTreeContextMenu: * ``` * // Subscribe to the nuclide-file-tree.context-menu service by ensuring the package.json for your * // Atom package contains the following stanza: * "consumedServices": { * "nuclide-file-tree.context-menu": { * "versions": { * "0.1.0": "addItemsToFileTreeContextMenu" * } * } * }, * * // Include the following in the main.js file for your package: * import {CompositeDisposable, Disposable} from 'atom'; * import invariant from 'assert'; * * let subscriptions: ?CompositeDisposable = null; * * export function activate(state: ?Object): void { * subscriptions = new CompositeDisposable(); * } * * export function deactivate(): void { * if (subscriptions != null) { * subscriptions.dispose(); * subscriptions = null; * } * } * * export function addItemsToFileTreeContextMenu(contextMenu: FileTreeContextMenu): IDisposable { * invariant(subscriptions); * * const contextDisposable = contextMenu.addItemToSourceControlMenu( * { * label: 'Label for the menu item that acts on a file', * command: 'command-that-should-only-be-fired-from-the-context-menu', * // If the callback below is given a new atom command with the given name will be * // automatically registered. You can omit it if you prefer to register the command * // manually. * callback() { * Array.from(contextMenu.getSelectedNodes()) * .filter(node => !node.isContainer) * .forEach((node: FileTreeNode) => { * const uri = node.uri; * // DO WHAT YOU LIKE WITH THE URI! * }); * }, * shouldDisplay() { * return Array.from(contextMenu.getSelectedNodes()).some(node => !node.isContainer); * }, * }, * 1000, // priority * ); * * subscriptions.add(contextDisposable); * return new Disposable(() => { * invariant(subscriptions); * if (subscriptions != null) { * subscriptions.remove(contextDisposable); * } * }); * } * ``` */ var FileTreeContextMenu = (function () { function FileTreeContextMenu() { var _this = this; _classCallCheck(this, FileTreeContextMenu); this._contextMenu = new (_commonsAtomContextMenu2 || _commonsAtomContextMenu()).default({ type: 'root', cssSelector: (_FileTreeConstants2 || _FileTreeConstants()).EVENT_HANDLER_SELECTOR }); this._subscriptions = new (_atom2 || _atom()).CompositeDisposable(); this._store = (_FileTreeStore2 || _FileTreeStore()).FileTreeStore.getInstance(); var shouldDisplaySetToCurrentWorkingRootOption = function shouldDisplaySetToCurrentWorkingRootOption() { var node = _this._store.getSingleSelectedNode(); return node != null && node.isRoot && _this._store.hasCwd() && !node.isCwd; }; this._addContextMenuItemGroup([{ label: 'Set to Current Working Root', command: 'nuclide-file-tree:set-current-working-root', shouldDisplay: shouldDisplaySetToCurrentWorkingRootOption }, { type: 'separator', shouldDisplay: shouldDisplaySetToCurrentWorkingRootOption }, { label: 'New', shouldDisplay: function shouldDisplay() { return _this._store.getSingleSelectedNode() != null; }, submenu: [{ label: 'File', command: 'nuclide-file-tree:add-file' }, { label: 'Folder', command: 'nuclide-file-tree:add-folder' }] }], NEW_MENU_PRIORITY); this._addContextMenuItemGroup([{ label: 'Add Project Folder', command: 'application:add-project-folder' }, { label: 'Add Remote Project Folder', command: 'nuclide-remote-projects:connect' }, { label: 'Remove Project Folder', command: 'nuclide-file-tree:remove-project-folder-selection', shouldDisplay: function shouldDisplay() { var node = _this.getSingleSelectedNode(); return node != null && node.isRoot; } }], ADD_PROJECT_MENU_PRIORITY); this._sourceControlMenu = new (_commonsAtomContextMenu2 || _commonsAtomContextMenu()).default({ type: 'submenu', label: 'Source Control', parent: this._contextMenu, shouldDisplay: function shouldDisplay(e) { return !_this._sourceControlMenu.isEmpty() && !_this._store.getSelectedNodes().isEmpty(); } }); this._contextMenu.addSubmenu(this._sourceControlMenu, SOURCE_CONTROL_MENU_PRIORITY); this._contextMenu.addItem({ type: 'separator', shouldDisplay: function shouldDisplay(e) { return !_this._sourceControlMenu.isEmpty(); } }, SOURCE_CONTROL_MENU_PRIORITY + 1); this._addContextMenuItemGroup([{ label: 'Rename', command: 'nuclide-file-tree:rename-selection', shouldDisplay: function shouldDisplay() { var node = _this._store.getSingleSelectedNode(); // For now, rename does not apply to root nodes. return node != null && !node.isRoot; } }, { label: 'Duplicate', command: 'nuclide-file-tree:duplicate-selection', shouldDisplay: function shouldDisplay() { var node = _this.getSingleSelectedNode(); return node != null && !node.isContainer; } }, { label: 'Delete', command: 'nuclide-file-tree:remove', shouldDisplay: function shouldDisplay() { var nodes = _this.getSelectedNodes(); // We can delete multiple nodes as long as no root node is selected return nodes.size > 0 && nodes.every(function (node) { return node != null && !node.isRoot; }); } }], MODIFY_FILE_MENU_PRIORITY); this._addContextMenuItemGroup([{ label: 'Split', shouldDisplay: function shouldDisplay() { var node = _this.getSingleSelectedNode(); return node != null && !node.isContainer; }, submenu: [{ label: 'Up', command: 'nuclide-file-tree:open-selected-entry-up' }, { label: 'Down', command: 'nuclide-file-tree:open-selected-entry-down' }, { label: 'Left', command: 'nuclide-file-tree:open-selected-entry-left' }, { label: 'Right', command: 'nuclide-file-tree:open-selected-entry-right' }] }], SPLIT_MENU_PRIORITY); this._addContextMenuItemGroup([{ label: 'Copy Full Path', command: 'nuclide-file-tree:copy-full-path', shouldDisplay: function shouldDisplay() { var node = _this.getSingleSelectedNode(); return node != null; } }, { label: 'Show in Finder', // Mac OS X command: 'nuclide-file-tree:show-in-file-manager', shouldDisplay: this._shouldDisplayShowInFileManager.bind(this, 'darwin') }, { label: 'Show in Explorer', // Windows command: 'nuclide-file-tree:show-in-file-manager', shouldDisplay: this._shouldDisplayShowInFileManager.bind(this, 'win32') }, { label: 'Show in File Manager', // Linux command: 'nuclide-file-tree:show-in-file-manager', shouldDisplay: this._shouldDisplayShowInFileManager.bind(this, 'linux') }, { label: 'Search in Directory', command: 'nuclide-file-tree:search-in-directory', shouldDisplay: function shouldDisplay() { var nodes = _this.getSelectedNodes(); return nodes.size > 0 && nodes.every(function (node) { return node.isContainer; }); } }], SHOW_IN_MENU_PRIORITY); } /** * @param priority must be an integer in the range [0, 1000). */ _createClass(FileTreeContextMenu, [{ key: 'addItemToTestSection', value: function addItemToTestSection(originalItem, priority) { if (priority < 0 || priority >= 1000) { throw Error('Illegal priority value: ' + priority); } return this._addItemToMenu(originalItem, this._contextMenu, TEST_SECTION_PRIORITY + priority); } }, { key: 'addItemToSourceControlMenu', value: function addItemToSourceControlMenu(originalItem, priority) { return this._addItemToMenu(originalItem, this._sourceControlMenu, priority); } }, { key: '_addItemToMenu', value: function _addItemToMenu(originalItem, menu, priority) { var _this2 = this; var _initCommandIfPresent = initCommandIfPresent(originalItem); var itemDisposable = _initCommandIfPresent.itemDisposable; var item = _initCommandIfPresent.item; itemDisposable.add(menu.addItem(item, priority)); this._subscriptions.add(itemDisposable); return new (_atom2 || _atom()).Disposable(function () { _this2._subscriptions.remove(itemDisposable); itemDisposable.dispose(); }); } }, { key: 'getSelectedNodes', value: function getSelectedNodes() { return this._store.getSelectedNodes(); } }, { key: 'getSingleSelectedNode', value: function getSingleSelectedNode() { return this._store.getSingleSelectedNode(); } }, { key: 'dispose', value: function dispose() { this._subscriptions.dispose(); } }, { key: '_addContextMenuItemGroup', value: function _addContextMenuItemGroup(menuItems, priority_) { var _this3 = this; var priority = priority_; // Atom is smart about only displaying a separator when there are items to // separate, so there will never be a dangling separator at the end. // $FlowFixMe: The conversion between MenuItemDefinition and atom$ContextMenuItem is a mess. var allItems = menuItems.concat([{ type: 'separator' }]); allItems.forEach(function (item) { _this3._contextMenu.addItem(item, ++priority); }); } /** * @return A {boolean} whether the "Show in File Manager" context menu item should be displayed * for the current selection and the given `platform`. */ }, { key: '_shouldDisplayShowInFileManager', value: function _shouldDisplayShowInFileManager(platform) { var node = this.getSingleSelectedNode(); return node != null && (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.isAbsolute(node.uri) && process.platform === platform; } }]); return FileTreeContextMenu; })(); function initCommandIfPresent(item) { var itemDisposable = new (_atom2 || _atom()).CompositeDisposable(); if (item.callback != null && item.label != null) { var _command = item.command || generateNextInternalCommand(item.label); itemDisposable.add(atom.commands.add(FILE_TREE_CSS, _command, item.callback)); return { itemDisposable: itemDisposable, item: _extends({}, item, { command: _command }) }; } return { itemDisposable: itemDisposable, item: item }; } var nextInternalCommandId = 0; function generateNextInternalCommand(itemLabel) { var cmdName = itemLabel.toLowerCase().replace(/[^\w]+/g, '-') + '-' + nextInternalCommandId++; return 'nuclide-file-tree:' + cmdName; } module.exports = FileTreeContextMenu;