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.

702 lines (634 loc) 26.7 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 _atom2; function _atom() { return _atom2 = require('atom'); } var _FileTreeConstants2; function _FileTreeConstants() { return _FileTreeConstants2 = require('./FileTreeConstants'); } var _FileSystemActions2; function _FileSystemActions() { return _FileSystemActions2 = _interopRequireDefault(require('./FileSystemActions')); } var _FileTreeActions2; function _FileTreeActions() { return _FileTreeActions2 = _interopRequireDefault(require('./FileTreeActions')); } var _FileTreeContextMenu2; function _FileTreeContextMenu() { return _FileTreeContextMenu2 = _interopRequireDefault(require('./FileTreeContextMenu')); } var _FileTreeHelpers2; function _FileTreeHelpers() { return _FileTreeHelpers2 = _interopRequireDefault(require('./FileTreeHelpers')); } var _FileTreeStore2; function _FileTreeStore() { return _FileTreeStore2 = require('./FileTreeStore'); } var _immutable2; function _immutable() { return _immutable2 = _interopRequireDefault(require('immutable')); } var _nuclideAnalytics2; function _nuclideAnalytics() { return _nuclideAnalytics2 = require('../../nuclide-analytics'); } var _os2; function _os() { return _os2 = _interopRequireDefault(require('os')); } var _electron2; function _electron() { return _electron2 = require('electron'); } var _assert2; function _assert() { return _assert2 = _interopRequireDefault(require('assert')); } var VALID_FILTER_CHARS = '!#./0123456789-:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '_abcdefghijklmnopqrstuvwxyz~'; var FileTreeController = (function () { function FileTreeController(state) { var _this = this; _classCallCheck(this, FileTreeController); this._actions = (_FileTreeActions2 || _FileTreeActions()).default.getInstance(); this._store = (_FileTreeStore2 || _FileTreeStore()).FileTreeStore.getInstance(); this._repositories = new (_immutable2 || _immutable()).default.Set(); this._subscriptionForRepository = new (_immutable2 || _immutable()).default.Map(); this._subscriptions = new (_atom2 || _atom()).CompositeDisposable(new (_atom2 || _atom()).Disposable(function () { if (_this._cwdApiSubscription != null) { _this._cwdApiSubscription.dispose(); } })); // Initial root directories this._updateRootDirectories(); // Subsequent root directories updated on change this._subscriptions.add(atom.project.onDidChangePaths(function () { return _this._updateRootDirectories(); })); this._subscriptions.add(atom.commands.add('atom-workspace', { // Pass undefined so the default parameter gets used. // NOTE: This is specifically for use in Diff View, so don't expose a menu item. // eslint-disable-next-line nuclide-internal/command-menu-items 'nuclide-file-tree:reveal-text-editor': this._revealTextEditor.bind(this), // This command is to workaround the initialization order problem between the // nuclide-remote-projects and nuclide-file-tree packages. // The file-tree starts up and restores its state, which can have a (remote) project root. // But at this point it's not a real directory. It is not present in // atom.project.getDirectories() and essentially it's a fake, but a useful one, as it has // the state (open folders, selection etc.) serialized in it. So we don't want to discard // it. In most cases, after a successful reconnect the real directory instance will be // added to the atom.project.directories and the previously fake root would become real. // The problem happens when the connection fails, or is canceled. // The fake root just stays in the file tree. To workaround that, this command can be used // to force the file-tree reflect the actual state of the projects. // Ideally, we'd like a service dependency between the two packages, but I (advinskyt) don't // see it happening any time soon. // eslint-disable-next-line nuclide-internal/command-menu-items 'nuclide-file-tree:force-refresh-roots': this._updateRootDirectories.bind(this), 'nuclide-file-tree:reveal-active-file': this.revealActiveFile.bind(this, undefined) })); var letterKeyBindings = { 'nuclide-file-tree:remove-letter': this._handleRemoveLetterKeypress.bind(this), 'nuclide-file-tree:clear-filter': this._handleClearFilter.bind(this) }; for (var i = 0, c = VALID_FILTER_CHARS.charCodeAt(0); i < VALID_FILTER_CHARS.length; i++, c = VALID_FILTER_CHARS.charCodeAt(i)) { var char = String.fromCharCode(c); letterKeyBindings['nuclide-file-tree:go-to-letter-' + char] = this._handlePrefixKeypress.bind(this, char); } this._subscriptions.add(atom.commands.add((_FileTreeConstants2 || _FileTreeConstants()).EVENT_HANDLER_SELECTOR, _extends({ 'core:move-down': this._moveDown.bind(this), 'core:move-up': this._moveUp.bind(this), 'core:move-to-top': this._moveToTop.bind(this), 'core:move-to-bottom': this._moveToBottom.bind(this), 'nuclide-file-tree:add-file': function nuclideFileTreeAddFile() { (_FileSystemActions2 || _FileSystemActions()).default.openAddFileDialog(_this._openAndRevealFilePath.bind(_this)); }, 'nuclide-file-tree:add-folder': function nuclideFileTreeAddFolder() { (_FileSystemActions2 || _FileSystemActions()).default.openAddFolderDialog(_this._openAndRevealDirectoryPath.bind(_this)); }, 'nuclide-file-tree:collapse-directory': this._collapseSelection.bind(this, /* deep */false), 'nuclide-file-tree:recursive-collapse-directory': this._collapseSelection.bind(this, true), 'nuclide-file-tree:recursive-collapse-all': this._collapseAll.bind(this), 'nuclide-file-tree:copy-full-path': this._copyFullPath.bind(this), 'nuclide-file-tree:expand-directory': this._expandSelection.bind(this, /* deep */false), 'nuclide-file-tree:recursive-expand-directory': this._expandSelection.bind(this, true), 'nuclide-file-tree:open-selected-entry': this._openSelectedEntry.bind(this), 'nuclide-file-tree:open-selected-entry-up': this._openSelectedEntrySplitUp.bind(this), 'nuclide-file-tree:open-selected-entry-down': this._openSelectedEntrySplitDown.bind(this), 'nuclide-file-tree:open-selected-entry-left': this._openSelectedEntrySplitLeft.bind(this), 'nuclide-file-tree:open-selected-entry-right': this._openSelectedEntrySplitRight.bind(this), 'nuclide-file-tree:remove': this._deleteSelection.bind(this), 'nuclide-file-tree:remove-project-folder-selection': this._removeRootFolderSelection.bind(this), 'nuclide-file-tree:rename-selection': function nuclideFileTreeRenameSelection() { return (_FileSystemActions2 || _FileSystemActions()).default.openRenameDialog(); }, 'nuclide-file-tree:duplicate-selection': function nuclideFileTreeDuplicateSelection() { (_FileSystemActions2 || _FileSystemActions()).default.openDuplicateDialog(_this._openAndRevealFilePath.bind(_this)); }, 'nuclide-file-tree:search-in-directory': this._searchInDirectory.bind(this), 'nuclide-file-tree:show-in-file-manager': this._showInFileManager.bind(this), 'nuclide-file-tree:set-current-working-root': this._setCwdToSelection.bind(this) }, letterKeyBindings))); this._subscriptions.add(atom.commands.add('[is="tabs-tab"]', { 'nuclide-file-tree:reveal-tab-file': this._revealTabFileOnClick.bind(this) })); if (state && state.tree) { this._store.loadData(state.tree); } this._contextMenu = new (_FileTreeContextMenu2 || _FileTreeContextMenu()).default(); } _createClass(FileTreeController, [{ key: '_moveUp', value: function _moveUp() { this._actions.moveSelectionUp(); } }, { key: '_moveDown', value: function _moveDown() { this._actions.moveSelectionDown(); } }, { key: '_moveToTop', value: function _moveToTop() { this._actions.moveSelectionToTop(); } }, { key: '_moveToBottom', value: function _moveToBottom() { this._actions.moveSelectionToBottom(); } }, { key: 'getContextMenu', value: function getContextMenu() { return this._contextMenu; } }, { key: '_handleClearFilter', value: function _handleClearFilter() { this._store.clearFilter(); } }, { key: '_handlePrefixKeypress', value: function _handlePrefixKeypress(letter) { if (!this._store.usePrefixNav()) { return; } this._store.addFilterLetter(letter); } }, { key: '_handleRemoveLetterKeypress', value: function _handleRemoveLetterKeypress() { if (!this._store.usePrefixNav()) { return; } this._store.removeFilterLetter(); } }, { key: '_openAndRevealFilePath', value: function _openAndRevealFilePath(filePath) { if (filePath != null) { atom.workspace.open(filePath); this.revealNodeKey(filePath); } } }, { key: '_openAndRevealDirectoryPath', value: function _openAndRevealDirectoryPath(path) { if (path != null) { this.revealNodeKey((_FileTreeHelpers2 || _FileTreeHelpers()).default.dirPathToKey(path)); } } }, { key: '_updateRootDirectories', value: function _updateRootDirectories() { // If the remote-projects package hasn't loaded yet remote directories will be instantiated as // local directories but with invalid paths. We need to exclude those. var rootDirectories = atom.project.getDirectories().filter(function (directory) { return (_FileTreeHelpers2 || _FileTreeHelpers()).default.isValidDirectory(directory); }); var rootKeys = rootDirectories.map(function (directory) { return (_FileTreeHelpers2 || _FileTreeHelpers()).default.dirPathToKey(directory.getPath()); }); this._actions.setRootKeys(rootKeys); this._actions.updateRepositories(rootDirectories); } }, { key: '_revealTextEditor', value: function _revealTextEditor(event) { var editorElement = event.target; if (editorElement == null || typeof editorElement.getModel !== 'function' || !atom.workspace.isTextEditor(editorElement.getModel())) { return; } var filePath = editorElement.getModel().getPath(); this._revealFilePath(filePath); } /** * Reveal the file that currently has focus in the file tree. If showIfHidden is false, * this will enqueue a pending reveal to be executed when the file tree is shown again. */ }, { key: 'revealActiveFile', value: function revealActiveFile() { var showIfHidden = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; var editor = atom.workspace.getActiveTextEditor(); var filePath = editor != null ? editor.getPath() : null; this._revealFilePath(filePath, showIfHidden); } }, { key: '_revealFilePath', value: function _revealFilePath(filePath) { var showIfHidden = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1]; if (showIfHidden) { // Ensure the file tree is visible before trying to reveal a file in it. Even if the currently // active pane is not an ordinary editor, we still at least want to show the tree. atom.commands.dispatch(atom.views.getView(atom.workspace), 'nuclide-file-tree:toggle', { display: true }); } if (!filePath) { return; } this.revealNodeKey(filePath); } /** * Reveal the file of a given tab based on the path stored on the DOM. * This method is meant to be triggered by the context-menu click. */ }, { key: '_revealTabFileOnClick', value: function _revealTabFileOnClick(event) { var tab = event.currentTarget; var title = tab.querySelector('.title[data-path]'); if (!title) { // can only reveal it if we find the file path return; } var filePath = title.dataset.path; atom.commands.dispatch(atom.views.getView(atom.workspace), 'nuclide-file-tree:toggle', { display: true }); this.revealNodeKey(filePath); } }, { key: 'revealNodeKey', value: function revealNodeKey(nodeKey) { if (nodeKey == null) { return; } this._actions.ensureChildNode(nodeKey); } }, { key: '_setCwdToSelection', value: function _setCwdToSelection() { var node = this._store.getSingleSelectedNode(); if (node == null) { return; } var path = (_FileTreeHelpers2 || _FileTreeHelpers()).default.keyToPath(node.uri); if (this._cwdApi != null) { this._cwdApi.setCwd(path); } } }, { key: 'setCwdApi', value: function setCwdApi(cwdApi) { var _this2 = this; if (cwdApi == null) { this._actions.setCwd(null); this._cwdApiSubscription = null; } else { (0, (_assert2 || _assert()).default)(this._cwdApiSubscription == null); this._cwdApiSubscription = cwdApi.observeCwd(function (directory) { var path = directory == null ? null : directory.getPath(); var rootKey = path && (_FileTreeHelpers2 || _FileTreeHelpers()).default.dirPathToKey(path); _this2._actions.setCwd(rootKey); }); } this._cwdApi = cwdApi; } }, { key: 'setExcludeVcsIgnoredPaths', value: function setExcludeVcsIgnoredPaths(excludeVcsIgnoredPaths) { this._actions.setExcludeVcsIgnoredPaths(excludeVcsIgnoredPaths); } }, { key: 'setHideIgnoredNames', value: function setHideIgnoredNames(hideIgnoredNames) { this._actions.setHideIgnoredNames(hideIgnoredNames); } }, { key: 'setIgnoredNames', value: function setIgnoredNames(ignoredNames) { this._actions.setIgnoredNames(ignoredNames); } }, { key: 'setUsePreviewTabs', value: function setUsePreviewTabs(usePreviewTabs) { this._actions.setUsePreviewTabs(usePreviewTabs); } }, { key: 'setUsePrefixNav', value: function setUsePrefixNav(usePrefixNav) { this._actions.setUsePrefixNav(usePrefixNav); } }, { key: 'updateWorkingSet', value: function updateWorkingSet(workingSet) { this._actions.updateWorkingSet(workingSet); } }, { key: 'updateWorkingSetsStore', value: function updateWorkingSetsStore(workingSetsStore) { this._actions.updateWorkingSetsStore(workingSetsStore); } }, { key: 'updateOpenFilesWorkingSet', value: function updateOpenFilesWorkingSet(openFilesWorkingSet) { this._actions.updateOpenFilesWorkingSet(openFilesWorkingSet); } /** * Collapses all selected directory nodes. If the selection is a single file or a single collapsed * directory, the selection is set to the directory's parent. */ }, { key: '_collapseSelection', value: function _collapseSelection() { var _this3 = this; var deep = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0]; var selectedNodes = this._store.getSelectedNodes(); var firstSelectedNode = selectedNodes.first(); if (selectedNodes.size === 1 && !firstSelectedNode.isRoot && !(firstSelectedNode.isContainer && firstSelectedNode.isExpanded)) { /* * Select the parent of the selection if the following criteria are met: * * Only 1 node is selected * * The node is not a root * * The node is not an expanded directory */ var _parent = firstSelectedNode.parent; this._selectAndTrackNode(_parent); } else { selectedNodes.forEach(function (node) { // Only directories can be expanded. Skip non-directory nodes. if (!node.isContainer) { return; } if (deep) { _this3._actions.collapseNodeDeep(node.rootUri, node.uri); } else { _this3._actions.collapseNode(node.rootUri, node.uri); } }); } } }, { key: '_selectAndTrackNode', value: function _selectAndTrackNode(node) { this._actions.setSelectedNode(node.rootUri, node.uri); } }, { key: '_collapseAll', value: function _collapseAll() { var _this4 = this; var roots = this._store.roots; roots.forEach(function (root) { return _this4._actions.collapseNodeDeep(root.uri, root.uri); }); } }, { key: '_deleteSelection', value: function _deleteSelection() { var _this5 = this; var nodes = this._store.getSelectedNodes(); if (nodes.size === 0) { return; } var rootPaths = nodes.filter(function (node) { return node.isRoot; }); if (rootPaths.size === 0) { var selectedPaths = nodes.map(function (node) { return (_FileTreeHelpers2 || _FileTreeHelpers()).default.keyToPath(node.uri); }); var message = 'Are you sure you want to delete the following ' + (nodes.size > 1 ? 'items?' : 'item?'); atom.confirm({ buttons: { Delete: function Delete() { _this5._actions.deleteSelectedNodes(); }, Cancel: function Cancel() {} }, detailedMessage: 'You are deleting:' + (_os2 || _os()).default.EOL + selectedPaths.join((_os2 || _os()).default.EOL), message: message }); } else { var message = undefined; if (rootPaths.size === 1) { message = 'The root directory \'' + rootPaths.first().nodeName + '\' can\'t be removed.'; } else { var rootPathNames = rootPaths.map(function (node) { return '\'' + node.nodeName + '\''; }).join(', '); message = 'The root directories ' + rootPathNames + ' can\'t be removed.'; } atom.confirm({ buttons: ['OK'], message: message }); } } /** * Expands all selected directory nodes. */ }, { key: '_expandSelection', value: function _expandSelection(deep) { var _this6 = this; this._handleClearFilter(); this._store.getSelectedNodes().forEach(function (node) { // Only directories can be expanded. Skip non-directory nodes. if (!node.isContainer) { return; } if (deep) { _this6._actions.expandNodeDeep(node.rootUri, node.uri); _this6._actions.setTrackedNode(node.rootUri, node.uri); } else { if (node.isExpanded) { // Node is already expanded; move the selection to the first child. var firstChild = node.children.first(); if (firstChild != null && !firstChild.shouldBeShown) { firstChild = firstChild.findNextShownSibling(); } if (firstChild != null) { _this6._selectAndTrackNode(firstChild); } } else { _this6._actions.expandNode(node.rootUri, node.uri); _this6._actions.setTrackedNode(node.rootUri, node.uri); } } }); } }, { key: '_openSelectedEntry', value: function _openSelectedEntry() { this._handleClearFilter(); var singleSelectedNode = this._store.getSingleSelectedNode(); // Only perform the default action if a single node is selected. if (singleSelectedNode != null) { this._actions.confirmNode(singleSelectedNode.rootUri, singleSelectedNode.uri); } } }, { key: '_openSelectedEntrySplit', value: function _openSelectedEntrySplit(orientation, side) { var singleSelectedNode = this._store.getSingleSelectedNode(); // Only perform the default action if a single node is selected. if (singleSelectedNode != null && !singleSelectedNode.isContainer) { // for: is this feature used enough to justify uncollapsing? (0, (_nuclideAnalytics2 || _nuclideAnalytics()).track)('filetree-split-file', { orientation: orientation, side: side }); this._actions.openSelectedEntrySplit(singleSelectedNode.uri, orientation, side); } } }, { key: '_openSelectedEntrySplitUp', value: function _openSelectedEntrySplitUp() { this._openSelectedEntrySplit('vertical', 'before'); } }, { key: '_openSelectedEntrySplitDown', value: function _openSelectedEntrySplitDown() { this._openSelectedEntrySplit('vertical', 'after'); } }, { key: '_openSelectedEntrySplitLeft', value: function _openSelectedEntrySplitLeft() { this._openSelectedEntrySplit('horizontal', 'before'); } }, { key: '_openSelectedEntrySplitRight', value: function _openSelectedEntrySplitRight() { this._openSelectedEntrySplit('horizontal', 'after'); } }, { key: '_removeRootFolderSelection', value: function _removeRootFolderSelection() { var _this7 = this; var rootNode = this._store.getSingleSelectedNode(); if (rootNode != null && rootNode.isRoot) { (function () { // close all the files associated with the project before closing var projectEditors = atom.workspace.getTextEditors(); var roots = _this7._store.getRootKeys(); var canceled = projectEditors.some(function (editor) { var path = editor.getPath(); // if the path of the editor is not null AND // is part of the currently selected root that would be removed AND // is not part of any other open root, then close the file. if (path != null && path.startsWith(rootNode.uri) && roots.filter(function (root) { return path.startsWith(root); }).length === 1) { return !atom.workspace.paneForURI(path).destroyItem(editor); } return false; }); if (!canceled) { // actually close the project atom.project.removePath((_FileTreeHelpers2 || _FileTreeHelpers()).default.keyToPath(rootNode.uri)); } })(); } } }, { key: '_searchInDirectory', value: function _searchInDirectory(event) { var targetElement = event.target; var shouldClearPath = false; // If the event was sent to the entire tree, rather then a single element - attempt to derive // the path to work on from the current selection. if (targetElement.classList.contains('nuclide-file-tree')) { var node = this._store.getSingleSelectedNode(); if (node == null) { return; } var path = node.uri; if (!node.isContainer) { (0, (_assert2 || _assert()).default)(node.parent); path = node.parent.uri; } // What we see here is an unfortunate example of "DOM as an API" paradigm :-( // Atom's handler for the "show-in-current-directory" command is context sensitive // and it derives the context from the custom "data-path" attribute. // This attribute is available through the `.dataset.path` property of the event's target // element. If missing in the target element, the descendants are queried. // See: https://github.com/atom/find-and-replace/blob/66f09c532bb4f7b941282b99d4daf85a08d2288c/lib/project-find-view.coffee#L277 // // This works when the command is targeted at an entry in the file-tree DOM structure, because // we add these attributes too, to maintain compatibility with Atom. But, obviously, the // file-tree root can't have one. Unfortunately, when we use keyboard shortcuts to trigger the // commands the focused element is the tree root. // So, to pass the contextual information somehow, we temporarily // add this attribute to the root element (and cleanup once the command is issued). targetElement.dataset.path = path; shouldClearPath = true; } // Dispatch a command to show the `ProjectFindView`. This opens the view and focuses the search // box. atom.commands.dispatch(targetElement, 'project-find:show-in-current-directory'); if (shouldClearPath) { delete targetElement.dataset.path; } } }, { key: '_showInFileManager', value: function _showInFileManager() { var node = this._store.getSingleSelectedNode(); if (node == null) { // Only allow revealing a single directory/file at a time. Return otherwise. return; } (_electron2 || _electron()).shell.showItemInFolder(node.uri); } }, { key: '_copyFullPath', value: function _copyFullPath() { var singleSelectedNode = this._store.getSingleSelectedNode(); if (singleSelectedNode != null) { atom.clipboard.write(singleSelectedNode.localPath); } } }, { key: 'destroy', value: function destroy() { this._subscriptions.dispose(); for (var disposable of this._subscriptionForRepository.values()) { disposable.dispose(); } this._store.reset(); this._contextMenu.dispose(); } }, { key: 'serialize', value: function serialize() { return { tree: this._store.exportData() }; } }]); return FileTreeController; })(); module.exports = FileTreeController;