UNPKG

8.65 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 { injectable } from 'inversify';
18import { Tree, TreeNode } from './tree';
19import { Event, Emitter, Disposable, DisposableCollection, MaybePromise } from '../../common';
20import { WidgetDecoration } from '../widget-decoration';
21
22/**
23 * The {@link TreeDecorator} allows adapting the look and the style of the tree items within a widget. Changes are reflected in
24 * the form of `decoration data`. This `decoration data` is a map storing {@link TreeDecoration.Data} for affected tree nodes (using the unique node id as key).
25 * It is important to notice that there is no common contribution point for `TreeDecorators`. Instead, each {@link TreeDecoratorService} is
26 * supposed to declare its own contribution provider for `TreeDecorators`.
27 *
28 * ### Example usage
29 * A simple tree decorator that changes the background color of each tree node to `red`.
30 *
31 * ```typescript
32 * @injectable()
33 * export class MyTreeDecorator implements TreeDecorator {
34 * id = 'myTreeDecorator';
35 *
36 * protected readonly emitter = new Emitter<(tree: Tree) => Map<string, TreeDecoration.Data>>();
37 *
38 * get onDidChangeDecorations(): Event<(tree: Tree) => Map<string, TreeDecoration.Data>> {
39 * return this.emitter.event;
40 * }
41 *
42 * decorations(tree: Tree): MaybePromise<Map<string, TreeDecoration.Data>> {
43 * const result = new Map();
44 *
45 * if (tree.root === undefined) {
46 * return result;
47 * }
48 * for (const treeNode of new DepthFirstTreeIterator(tree.root)) {
49 * result.set(treeNode.id,<TreeDecoration.Data>{backgroundColor:'red'})
50 * }
51 * return result;
52 * }
53 * }
54 * ```
55 */
56export interface TreeDecorator {
57
58 /**
59 * The unique identifier of the decorator. Ought to be unique in the application.
60 */
61 readonly id: string;
62
63 /**
64 * Fired when this decorator has calculated all the `decoration data` for the tree nodes.
65 */
66 readonly onDidChangeDecorations: Event<(tree: Tree) => Map<string, TreeDecoration.Data>>;
67
68 /**
69 * Computes the current `decoration data` for the given tree. Might return a promise if the computation is handled asynchronously.
70 *
71 * @param tree the tree to decorate.
72 *
73 * @returns (a promise of) a map containing the current {@linkTreeDecoration.Data} for each node. Keys are the unique identifier of the tree nodes.
74 */
75 decorations(tree: Tree): MaybePromise<Map<string, TreeDecoration.Data>>;
76
77}
78
79export const TreeDecoratorService = Symbol('TreeDecoratorService');
80
81/**
82 * The {@link TreeDecoratorService} manages a set of known {link TreeDecorator}s and emits events when
83 * any of the known decorators has changes. Typically, a `TreeDecoratorService` provides a contribution point that can be used to
84 * register {@link TreeDecorator}s exclusively for this service.
85 *
86 * ### Example usage
87 * ```typescript
88 * export const MyTreeDecorator = Symbol('MyTreeDecorator');
89 *
90 * @injectable()
91 * export class MyDecorationService extends AbstractTreeDecoratorService {
92 * constructor(@inject(ContributionProvider) @named(MyTreeDecorator) protected readonly contributions: ContributionProvider<TreeDecorator>) {
93 * super(contributions.getContributions());
94 * }
95 * }
96 * ```
97 */
98export interface TreeDecoratorService extends Disposable {
99
100 /**
101 * Fired when any of the available tree decorators has changes.
102 */
103 readonly onDidChangeDecorations: Event<void>;
104
105 /**
106 * Computes the decorations for the tree based on the actual state of this decorator service.
107 *
108 * @param tree the tree to decorate
109 *
110 * @returns (a promise of) the computed `decoration data`
111 */
112 getDecorations(tree: Tree): MaybePromise<Map<string, TreeDecoration.Data[]>>;
113
114 /**
115 * Transforms the `decoration data` into an object, so that it can be safely serialized into JSON.
116 * @param decorations the `decoration data` that should be deflated
117 *
118 * @returns the `decoration data` as serializable JSON object
119 */
120 deflateDecorators(decorations: Map<string, TreeDecoration.Data[]>): object;
121
122 /**
123 * Counterpart of the [deflateDecorators](#deflateDecorators) method. Restores the argument into a Map
124 * of tree node IDs and the corresponding decorations data array (`decoration data`).
125 *
126 * @returns the deserialized `decoration data
127 */
128 // eslint-disable-next-line @typescript-eslint/no-explicit-any
129 inflateDecorators(state: any): Map<string, TreeDecoration.Data[]>;
130
131}
132
133/**
134 * The default tree decorator service. Does nothing at all. One has to rebind to a concrete implementation
135 * if decorators have to be supported in the tree widget.
136 */
137@injectable()
138export class NoopTreeDecoratorService implements TreeDecoratorService {
139
140 protected readonly emitter = new Emitter<void>();
141 readonly onDidChangeDecorations = this.emitter.event;
142
143 dispose(): void {
144 this.emitter.dispose();
145 }
146
147 // eslint-disable-next-line @typescript-eslint/no-explicit-any
148 getDecorations(): Map<any, any> {
149 return new Map();
150 }
151
152 deflateDecorators(): object {
153 return {};
154 }
155
156 inflateDecorators(): Map<string, TreeDecoration.Data[]> {
157 return new Map();
158 }
159
160}
161
162/**
163 * Abstract decorator service implementation which emits events from all known tree decorators and caches the current state.
164 */
165@injectable()
166export abstract class AbstractTreeDecoratorService implements TreeDecoratorService {
167
168 protected readonly onDidChangeDecorationsEmitter = new Emitter<void>();
169 readonly onDidChangeDecorations = this.onDidChangeDecorationsEmitter.event;
170
171 protected readonly toDispose = new DisposableCollection();
172
173 constructor(protected readonly decorators: ReadonlyArray<TreeDecorator>) {
174 this.toDispose.push(this.onDidChangeDecorationsEmitter);
175 this.toDispose.pushAll(this.decorators.map(decorator =>
176 decorator.onDidChangeDecorations(data =>
177 this.onDidChangeDecorationsEmitter.fire(undefined)
178 ))
179 );
180 }
181
182 dispose(): void {
183 this.toDispose.dispose();
184 }
185
186 async getDecorations(tree: Tree): Promise<Map<string, TreeDecoration.Data[]>> {
187 const changes = new Map();
188 for (const decorator of this.decorators) {
189 for (const [id, data] of (await decorator.decorations(tree)).entries()) {
190 if (changes.has(id)) {
191 changes.get(id)!.push(data);
192 } else {
193 changes.set(id, [data]);
194 }
195 }
196 }
197 return changes;
198 }
199
200 deflateDecorators(decorations: Map<string, TreeDecoration.Data[]>): object {
201 // eslint-disable-next-line no-null/no-null
202 const state = Object.create(null);
203 for (const [id, data] of decorations) {
204 state[id] = data;
205 }
206 return state;
207 }
208
209 // eslint-disable-next-line @typescript-eslint/no-explicit-any
210 inflateDecorators(state: any): Map<string, TreeDecoration.Data[]> {
211 const decorators = new Map<string, TreeDecoration.Data[]>();
212 for (const id of Object.keys(state)) {
213 decorators.set(id, state[id]);
214 }
215 return decorators;
216 }
217
218}
219
220/**
221 * @deprecated import from `@theia/core/lib/browser/widget-decoration` instead.
222 */
223export import TreeDecoration = WidgetDecoration;
224
225export interface DecoratedTreeNode extends TreeNode {
226 /**
227 * The additional tree decoration data attached to the tree node itself.
228 */
229 readonly decorationData: TreeDecoration.Data;
230}
231export namespace DecoratedTreeNode {
232 /**
233 * Type-guard for decorated tree nodes.
234 */
235 export function is(node: TreeNode | undefined): node is DecoratedTreeNode {
236 return !!node && 'decorationData' in node;
237 }
238}