UNPKG

4.69 kBPlain TextView Raw
1// *****************************************************************************
2// Copyright (C) 2018 TypeFox and others.
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License v. 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0.
7//
8// This Source Code may also be made available under the following Secondary
9// Licenses when the conditions for such availability set forth in the Eclipse
10// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11// with the GNU Classpath Exception which is available at
12// https://www.gnu.org/software/classpath/license.html.
13//
14// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15// *****************************************************************************
16
17import { inject, injectable, postConstruct } from 'inversify';
18import { Disposable, DisposableCollection } from '../../common/disposable';
19import { Event, Emitter } from '../../common/event';
20import { Tree, TreeNode } from './tree';
21import { TreeDecoration } from './tree-decorator';
22import { FuzzySearch } from './fuzzy-search';
23import { TopDownTreeIterator } from './tree-iterator';
24import { LabelProvider } from '../label-provider';
25
26@injectable()
27export class TreeSearch implements Disposable {
28
29 @inject(Tree)
30 protected readonly tree: Tree;
31
32 @inject(FuzzySearch)
33 protected readonly fuzzySearch: FuzzySearch;
34
35 @inject(LabelProvider)
36 protected readonly labelProvider: LabelProvider;
37
38 protected readonly disposables = new DisposableCollection();
39 protected readonly filteredNodesEmitter = new Emitter<ReadonlyArray<Readonly<TreeNode>>>();
40
41 protected _filterResult: FuzzySearch.Match<TreeNode>[] = [];
42 protected _filteredNodes: ReadonlyArray<Readonly<TreeNode>> = [];
43 protected _filteredNodesAndParents: Set<string> = new Set();
44
45 @postConstruct()
46 protected init(): void {
47 this.disposables.push(this.filteredNodesEmitter);
48 }
49
50 getHighlights(): Map<string, TreeDecoration.CaptionHighlight> {
51 return new Map(this._filterResult.map(m => [m.item.id, this.toCaptionHighlight(m)] as [string, TreeDecoration.CaptionHighlight]));
52 }
53
54 /**
55 * Resolves to all the visible tree nodes that match the search pattern.
56 */
57 async filter(pattern: string | undefined): Promise<ReadonlyArray<Readonly<TreeNode>>> {
58 const { root } = this.tree;
59 this._filteredNodesAndParents = new Set();
60 if (!pattern || !root) {
61 this._filterResult = [];
62 this._filteredNodes = [];
63 this.fireFilteredNodesChanged(this._filteredNodes);
64 return [];
65 }
66 const items = [...new TopDownTreeIterator(root)];
67 const transform = (node: TreeNode) => this.labelProvider.getName(node);
68 this._filterResult = await this.fuzzySearch.filter({
69 items,
70 pattern,
71 transform
72 });
73 this._filteredNodes = this._filterResult.map(({ item }) => {
74 this.addAllParentsToFilteredSet(item);
75 return item;
76 });
77 this.fireFilteredNodesChanged(this._filteredNodes);
78 return this._filteredNodes.slice();
79 }
80
81 protected addAllParentsToFilteredSet(node: TreeNode): void {
82 let toAdd: TreeNode | undefined = node;
83 while (toAdd && !this._filteredNodesAndParents.has(toAdd.id)) {
84 this._filteredNodesAndParents.add(toAdd.id);
85 toAdd = toAdd.parent;
86 };
87 }
88
89 /**
90 * Returns with the filtered nodes after invoking the `filter` method.
91 */
92 get filteredNodes(): ReadonlyArray<Readonly<TreeNode>> {
93 return this._filteredNodes.slice();
94 }
95
96 /**
97 * Event that is fired when the filtered nodes have been changed.
98 */
99 get onFilteredNodesChanged(): Event<ReadonlyArray<Readonly<TreeNode>>> {
100 return this.filteredNodesEmitter.event;
101 }
102
103 passesFilters(node: TreeNode): boolean {
104 return this._filteredNodesAndParents.has(node.id);
105 }
106
107 dispose(): void {
108 this.disposables.dispose();
109 }
110
111 protected fireFilteredNodesChanged(nodes: ReadonlyArray<Readonly<TreeNode>>): void {
112 this.filteredNodesEmitter.fire(nodes);
113 }
114
115 protected toCaptionHighlight(match: FuzzySearch.Match<TreeNode>): TreeDecoration.CaptionHighlight {
116 return {
117 ranges: match.ranges.map(this.mapRange.bind(this))
118 };
119 }
120
121 protected mapRange(range: FuzzySearch.Range): TreeDecoration.CaptionHighlight.Range {
122 const { offset, length } = range;
123 return {
124 offset,
125 length
126 };
127 }
128}