composite.ts 8.8 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Johannes Rieken 已提交
6 7
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
B
Benjamin Pasero 已提交
8
import { Component } from 'vs/workbench/common/component';
J
Johannes Rieken 已提交
9
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
B
Benjamin Pasero 已提交
10
import { IComposite, ICompositeControl } from 'vs/workbench/common/composite';
M
Matt Bierner 已提交
11
import { Event, Emitter } from 'vs/base/common/event';
12
import { IThemeService } from 'vs/platform/theme/common/themeService';
13
import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
B
Benjamin Pasero 已提交
14
import { trackFocus, Dimension } from 'vs/base/browser/dom';
B
Benjamin Pasero 已提交
15
import { IStorageService } from 'vs/platform/storage/common/storage';
B
Benjamin Pasero 已提交
16
import { Disposable } from 'vs/base/common/lifecycle';
17 18

/**
I
isidor 已提交
19 20
 * Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
 * can be open in the sidebar, and only one composite can be open in the panel.
B
Benjamin Pasero 已提交
21
 *
I
isidor 已提交
22
 * Each composite has a minimized representation that is good enough to provide some
23
 * information about the state of the composite data.
24
 *
25 26 27 28 29
 * The workbench will keep a composite alive after it has been created and show/hide it based on
 * user interaction. The lifecycle of a composite goes in the order create(), setVisible(true|false),
 * layout(), focus(), dispose(). During use of the workbench, a composite will often receive a setVisible,
 * layout and focus call, but only one create and dispose call.
 */
B
Benjamin Pasero 已提交
30
export abstract class Composite extends Component implements IComposite {
M
Matt Bierner 已提交
31

B
Benjamin Pasero 已提交
32 33
	private readonly _onTitleAreaUpdate: Emitter<void> = this._register(new Emitter<void>());
	get onTitleAreaUpdate(): Event<void> { return this._onTitleAreaUpdate.event; }
34

35 36 37
	private readonly _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
	get onDidChangeVisibility(): Event<boolean> { return this._onDidChangeVisibility.event; }

B
Benjamin Pasero 已提交
38 39 40
	private _onDidFocus: Emitter<void>;
	get onDidFocus(): Event<void> {
		if (!this._onDidFocus) {
41
			this.registerFocusTrackEvents();
S
Sandeep Somavarapu 已提交
42
		}
B
Benjamin Pasero 已提交
43

S
Sandeep Somavarapu 已提交
44 45
		return this._onDidFocus.event;
	}
B
Benjamin Pasero 已提交
46

S
Sandeep Somavarapu 已提交
47 48 49
	private _onDidBlur: Emitter<void>;
	get onDidBlur(): Event<void> {
		if (!this._onDidBlur) {
50
			this.registerFocusTrackEvents();
B
Benjamin Pasero 已提交
51
		}
B
Benjamin Pasero 已提交
52

S
Sandeep Somavarapu 已提交
53 54
		return this._onDidBlur.event;
	}
B
Benjamin Pasero 已提交
55

56
	private registerFocusTrackEvents(): void {
S
Sandeep Somavarapu 已提交
57 58 59 60 61 62
		this._onDidFocus = this._register(new Emitter<void>());
		this._onDidBlur = this._register(new Emitter<void>());

		const focusTracker = this._register(trackFocus(this.getContainer()));
		this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));
		this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));
B
Benjamin Pasero 已提交
63
	}
64 65 66

	protected actionRunner: IActionRunner;

B
Benjamin Pasero 已提交
67 68 69
	private visible: boolean;
	private parent: HTMLElement;

70 71 72
	constructor(
		id: string,
		private _telemetryService: ITelemetryService,
B
Benjamin Pasero 已提交
73
		themeService: IThemeService,
74
		storageService: IStorageService
75
	) {
76
		super(id, themeService, storageService);
77 78 79 80

		this.visible = false;
	}

M
Matt Bierner 已提交
81
	getTitle(): string | null {
82 83 84
		return null;
	}

B
Benjamin Pasero 已提交
85
	protected get telemetryService(): ITelemetryService {
86 87 88 89
		return this._telemetryService;
	}

	/**
B
Benjamin Pasero 已提交
90
	 * Note: Clients should not call this method, the workbench calls this
91 92
	 * method. Calling it otherwise may result in unexpected behavior.
	 *
93
	 * Called to create this composite on the provided parent. This method is only
94 95 96 97
	 * called once during the lifetime of the workbench.
	 * Note that DOM-dependent calculations should be performed from the setVisible()
	 * call. Only then the composite will be part of the DOM.
	 */
I
isidor 已提交
98
	create(parent: HTMLElement): void {
99 100 101
		this.parent = parent;
	}

B
Benjamin Pasero 已提交
102
	updateStyles(): void {
B
Benjamin Pasero 已提交
103 104 105
		super.updateStyles();
	}

106 107 108
	/**
	 * Returns the container this composite is being build in.
	 */
B
Benjamin Pasero 已提交
109
	getContainer(): HTMLElement {
110 111 112 113
		return this.parent;
	}

	/**
B
Benjamin Pasero 已提交
114
	 * Note: Clients should not call this method, the workbench calls this
115 116 117 118 119 120
	 * method. Calling it otherwise may result in unexpected behavior.
	 *
	 * Called to indicate that the composite has become visible or hidden. This method
	 * is called more than once during workbench lifecycle depending on the user interaction.
	 * The composite will be on-DOM if visible is set to true and off-DOM otherwise.
	 *
121 122
	 * Typically this operation should be fast though because setVisible might be called many times during a session.
	 * If there is a long running opertaion it is fine to have it running in the background asyncly and return before.
123
	 */
124
	setVisible(visible: boolean): void {
125 126 127 128 129
		if (this.visible !== !!visible) {
			this.visible = visible;

			this._onDidChangeVisibility.fire(visible);
		}
130 131 132 133 134
	}

	/**
	 * Called when this composite should receive keyboard focus.
	 */
B
Benjamin Pasero 已提交
135
	focus(): void {
136 137 138 139 140 141
		// Subclasses can implement
	}

	/**
	 * Layout the contents of this composite using the provided dimensions.
	 */
B
Benjamin Pasero 已提交
142
	abstract layout(dimension: Dimension): void;
143 144 145 146

	/**
	 * Returns an array of actions to show in the action bar of the composite.
	 */
B
Benjamin Pasero 已提交
147
	getActions(): IAction[] {
148 149 150 151 152 153 154
		return [];
	}

	/**
	 * Returns an array of actions to show in the action bar of the composite
	 * in a less prominent way then action from getActions.
	 */
B
Benjamin Pasero 已提交
155
	getSecondaryActions(): IAction[] {
156 157 158
		return [];
	}

159 160 161
	/**
	 * Returns an array of actions to show in the context menu of the composite
	 */
B
Benjamin Pasero 已提交
162
	getContextMenuActions(): IAction[] {
163 164 165
		return [];
	}

166 167 168
	/**
	 * For any of the actions returned by this composite, provide an IActionItem in
	 * cases where the implementor of the composite wants to override the presentation
169
	 * of an action. Returns undefined to indicate that the action is not rendered through
170 171
	 * an action item.
	 */
172 173
	getActionItem(action: IAction): IActionItem | undefined {
		return undefined;
174 175
	}

J
Joao Moreno 已提交
176 177 178
	/**
	 * Provide a context to be passed to the toolbar.
	 */
179
	getActionsContext(): unknown {
J
Joao Moreno 已提交
180 181 182
		return null;
	}

183 184 185 186
	/**
	 * Returns the instance of IActionRunner to use with this composite for the
	 * composite tool bar.
	 */
B
Benjamin Pasero 已提交
187
	getActionRunner(): IActionRunner {
188 189 190 191 192 193 194 195 196 197 198 199 200 201
		if (!this.actionRunner) {
			this.actionRunner = new ActionRunner();
		}

		return this.actionRunner;
	}

	/**
	 * Method for composite implementors to indicate to the composite container that the title or the actions
	 * of the composite have changed. Calling this method will cause the container to ask for title (getTitle())
	 * and actions (getActions(), getSecondaryActions()) if the composite is visible or the next time the composite
	 * gets visible.
	 */
	protected updateTitleArea(): void {
202
		this._onTitleAreaUpdate.fire();
203 204 205 206 207
	}

	/**
	 * Returns true if this composite is currently visible and false otherwise.
	 */
B
Benjamin Pasero 已提交
208
	isVisible(): boolean {
209 210 211 212 213 214
		return this.visible;
	}

	/**
	 * Returns the underlying composite control or null if it is not accessible.
	 */
M
Matt Bierner 已提交
215
	getControl(): ICompositeControl | null {
216 217 218
		return null;
	}
}
I
isidor 已提交
219 220

/**
B
Benjamin Pasero 已提交
221
 * A composite descriptor is a leightweight descriptor of a composite in the workbench.
I
isidor 已提交
222
 */
223
export abstract class CompositeDescriptor<T extends Composite> {
M
Matt Bierner 已提交
224 225 226

	constructor(
		private readonly ctor: IConstructorSignature0<T>,
227 228 229 230 231
		readonly id: string,
		readonly name: string,
		readonly cssClass?: string,
		readonly order?: number,
		readonly keybindingId?: string,
M
Matt Bierner 已提交
232
	) { }
233

B
Benjamin Pasero 已提交
234
	instantiate(instantiationService: IInstantiationService): T {
235 236
		return instantiationService.createInstance(this.ctor);
	}
I
isidor 已提交
237
}
I
isidor 已提交
238

B
Benjamin Pasero 已提交
239
export abstract class CompositeRegistry<T extends Composite> extends Disposable {
240

B
Benjamin Pasero 已提交
241
	private readonly _onDidRegister: Emitter<CompositeDescriptor<T>> = this._register(new Emitter<CompositeDescriptor<T>>());
B
Benjamin Pasero 已提交
242
	get onDidRegister(): Event<CompositeDescriptor<T>> { return this._onDidRegister.event; }
I
isidor 已提交
243

244 245 246
	private readonly _onDidDeregister: Emitter<CompositeDescriptor<T>> = this._register(new Emitter<CompositeDescriptor<T>>());
	get onDidDeregister(): Event<CompositeDescriptor<T>> { return this._onDidDeregister.event; }

B
Benjamin Pasero 已提交
247
	private composites: CompositeDescriptor<T>[] = [];
I
isidor 已提交
248 249

	protected registerComposite(descriptor: CompositeDescriptor<T>): void {
I
isidor 已提交
250
		if (this.compositeById(descriptor.id) !== null) {
I
isidor 已提交
251 252 253
			return;
		}

B
Benjamin Pasero 已提交
254
		this.composites.push(descriptor);
255
		this._onDidRegister.fire(descriptor);
I
isidor 已提交
256 257
	}

258 259 260 261 262 263 264 265 266 267
	protected deregisterComposite(id: string): void {
		const descriptor = this.compositeById(id);
		if (descriptor === null) {
			return;
		}

		this.composites.splice(this.composites.indexOf(descriptor), 1);
		this._onDidDeregister.fire(descriptor);
	}

M
Matt Bierner 已提交
268
	getComposite(id: string): CompositeDescriptor<T> | null {
I
isidor 已提交
269
		return this.compositeById(id);
I
isidor 已提交
270 271
	}

B
Benjamin Pasero 已提交
272 273
	protected getComposites(): CompositeDescriptor<T>[] {
		return this.composites.slice(0);
I
isidor 已提交
274 275
	}

M
Matt Bierner 已提交
276
	private compositeById(id: string): CompositeDescriptor<T> | null {
277 278 279
		for (const composite of this.composites) {
			if (composite.id === id) {
				return composite;
I
isidor 已提交
280 281 282 283 284
			}
		}

		return null;
	}
285
}