UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

225 lines • 9.8 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2018 TypeFox and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** Object.defineProperty(exports, "__esModule", { value: true }); exports.TreeSelectionState = exports.FocusableTreeSelection = void 0; const tree_iterator_1 = require("./tree-iterator"); const tree_selection_1 = require("./tree-selection"); var FocusableTreeSelection; (function (FocusableTreeSelection) { /** * `true` if the argument is a focusable tree selection. Otherwise, `false`. */ function is(arg) { return tree_selection_1.TreeSelection.is(arg) && 'focus' in arg; } FocusableTreeSelection.is = is; /** * Returns with the tree node that has the focus if the argument is a focusable tree selection. * Otherwise, returns `undefined`. */ function focus(arg) { return is(arg) ? arg.focus : undefined; } FocusableTreeSelection.focus = focus; })(FocusableTreeSelection = exports.FocusableTreeSelection || (exports.FocusableTreeSelection = {})); /** * Class for representing and managing the selection state and the focus of a tree. */ class TreeSelectionState { constructor(tree, selectionStack = []) { this.tree = tree; this.selectionStack = selectionStack; } nextState(selection) { const { node, type } = Object.assign({ type: tree_selection_1.TreeSelection.SelectionType.DEFAULT }, selection); switch (type) { case tree_selection_1.TreeSelection.SelectionType.DEFAULT: return this.handleDefault(this, node); case tree_selection_1.TreeSelection.SelectionType.TOGGLE: return this.handleToggle(this, node); case tree_selection_1.TreeSelection.SelectionType.RANGE: return this.handleRange(this, node); default: throw new Error(`Unexpected tree selection type: ${type}.`); } } selection() { const copy = this.checkNoDefaultSelection(this.selectionStack); const nodeIds = new Set(); for (let i = 0; i < copy.length; i++) { const { node, type } = copy[i]; if (tree_selection_1.TreeSelection.isRange(type)) { const selection = copy[i]; for (const id of this.selectionRange(selection).map(n => n.id)) { nodeIds.add(id); } } else if (tree_selection_1.TreeSelection.isToggle(type)) { if (nodeIds.has(node.id)) { nodeIds.delete(node.id); } else { nodeIds.add(node.id); } } } return Array.from(nodeIds.keys()).map(id => this.tree.getNode(id)).filter(tree_selection_1.SelectableTreeNode.is).reverse(); } get focus() { var _a; const copy = this.checkNoDefaultSelection(this.selectionStack); const candidate = (_a = copy[copy.length - 1]) === null || _a === void 0 ? void 0 : _a.focus; return this.toSelectableTreeNode(candidate); } get node() { var _a; const copy = this.checkNoDefaultSelection(this.selectionStack); return this.toSelectableTreeNode((_a = copy[copy.length - 1]) === null || _a === void 0 ? void 0 : _a.node); } handleDefault(state, node) { const { tree } = state; return new TreeSelectionState(tree, [{ node, type: tree_selection_1.TreeSelection.SelectionType.TOGGLE, focus: node }]); } handleToggle(state, node) { const { tree, selectionStack } = state; const copy = this.checkNoDefaultSelection(selectionStack).slice(); const focus = (() => { const allRanges = copy.filter(selection => tree_selection_1.TreeSelection.isRange(selection)); for (let i = allRanges.length - 1; i >= 0; i--) { const latestRangeIndex = copy.indexOf(allRanges[i]); const latestRangeSelection = copy[latestRangeIndex]; const latestRange = (latestRangeSelection === null || latestRangeSelection === void 0 ? void 0 : latestRangeSelection.focus) ? this.selectionRange(latestRangeSelection) : []; if (latestRange.indexOf(node) !== -1) { if (this.focus === latestRangeSelection.focus) { return latestRangeSelection.focus || node; } else { return this.focus; } } } return node; })(); return new TreeSelectionState(tree, [...copy, { node, type: tree_selection_1.TreeSelection.SelectionType.TOGGLE, focus }]); } handleRange(state, node) { const { tree, selectionStack } = state; const copy = this.checkNoDefaultSelection(selectionStack).slice(); let focus = FocusableTreeSelection.focus(copy[copy.length - 1]); // Drop the previous range when we are trying to modify that. if (tree_selection_1.TreeSelection.isRange(copy[copy.length - 1])) { const range = this.selectionRange(copy.pop()); // And we drop all preceding individual nodes that were contained in the range we are dropping. // That means, anytime we cover individual nodes with a range, they will belong to the range so we need to drop them now. for (let i = copy.length - 1; i >= 0; i--) { if (range.indexOf(copy[i].node) !== -1) { // Make sure to keep a reference to the focus while we are discarding previous elements. Otherwise, we lose this information. focus = copy[i].focus; copy.splice(i, 1); } } } return new TreeSelectionState(tree, [...copy, { node, type: tree_selection_1.TreeSelection.SelectionType.RANGE, focus }]); } /** * Returns with an array of items representing the selection range. The from node is the `focus` the to node * is the selected node itself on the tree selection. Both the `from` node and the `to` node are inclusive. */ selectionRange(selection) { const fromNode = selection.focus; const toNode = selection.node; if (fromNode === undefined) { return []; } if (toNode === fromNode) { return [toNode]; } const { root } = this.tree; if (root === undefined) { return []; } const to = this.tree.validateNode(toNode); if (to === undefined) { return []; } const from = this.tree.validateNode(fromNode); if (from === undefined) { return []; } let started = false; let finished = false; const range = []; for (const node of new tree_iterator_1.DepthFirstTreeIterator(root, { pruneCollapsed: true })) { if (finished) { break; } // Only collect items which are between (inclusive) the `from` node and the `to` node. if (node === from || node === to) { if (started) { finished = true; } else { started = true; } } if (started) { range.push(node); } } // We need to reverse the selection range order. if (range.indexOf(from) > range.indexOf(to)) { range.reverse(); } return range.filter(tree_selection_1.SelectableTreeNode.is); } toSelectableTreeNode(node) { if (!!node) { const candidate = this.tree.getNode(node.id); if (!!candidate) { if (tree_selection_1.SelectableTreeNode.is(candidate)) { return candidate; } else { console.warn(`Could not map to a selectable tree node. Node with ID: ${node.id} is not a selectable node.`); } } else { console.warn(`Could not map to a selectable tree node. Node does not exist with ID: ${node.id}.`); } } return undefined; } /** * Checks whether the argument contains any `DEFAULT` tree selection type. If yes, throws an error, otherwise returns with a reference the argument. */ checkNoDefaultSelection(selections) { if (selections.some(selection => selection.type === undefined || selection.type === tree_selection_1.TreeSelection.SelectionType.DEFAULT)) { throw new Error(`Unexpected DEFAULT selection type. [${selections.map(selection => `ID: ${selection.node.id} | ${selection.type}`).join(', ')}]`); } return selections; } } exports.TreeSelectionState = TreeSelectionState; //# sourceMappingURL=tree-selection-state.js.map