outlinePane.ts 24.1 KB
Newer Older
J
Johannes Rieken 已提交
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
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
7
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
8
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
J
Johannes Rieken 已提交
9
import { Action, IAction, RadioGroup } from 'vs/base/common/actions';
10
import { createCancelablePromise, TimeoutTimer } from 'vs/base/common/async';
11
import { isPromiseCanceledError } from 'vs/base/common/errors';
J
Johannes Rieken 已提交
12
import { Emitter } from 'vs/base/common/event';
J
Johannes Rieken 已提交
13
import { defaultGenerator } from 'vs/base/common/idGenerator';
J
Johannes Rieken 已提交
14
import { dispose, IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
15 16
import { LRUCache } from 'vs/base/common/map';
import { escape } from 'vs/base/common/strings';
17
import 'vs/css!./outlinePane';
J
Johannes Rieken 已提交
18
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
J
Johannes Rieken 已提交
19
import { Range } from 'vs/editor/common/core/range';
20
import { Selection } from 'vs/editor/common/core/selection';
J
Johannes Rieken 已提交
21
import { ITextModel } from 'vs/editor/common/model';
22
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
J
Johannes Rieken 已提交
23
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
A
Alex Dima 已提交
24
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
J
Johannes Rieken 已提交
25
import { OutlineElement, OutlineModel, TreeElement, IOutlineMarker } from 'vs/editor/contrib/documentSymbols/outlineModel';
J
Johannes Rieken 已提交
26
import { localize } from 'vs/nls';
J
Johannes Rieken 已提交
27
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
28
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
J
Johannes Rieken 已提交
29
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
30
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
J
Johannes Rieken 已提交
31
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Johannes Rieken 已提交
32
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
33
import { WorkbenchDataTree } from 'vs/platform/list/browser/listService';
J
Johannes Rieken 已提交
34
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
35
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
J
Johannes Rieken 已提交
36
import { IThemeService } from 'vs/platform/theme/common/themeService';
S
SteVen Batten 已提交
37
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
38
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
B
Benjamin Pasero 已提交
39
import { CollapseAction } from 'vs/workbench/browser/viewlet';
40
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
B
Benjamin Pasero 已提交
41
import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline';
42
import { FuzzyScore } from 'vs/base/common/filters';
43
import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree';
44 45
import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
46
import { basename } from 'vs/base/common/resources';
J
Johannes Rieken 已提交
47
import { IDataSource } from 'vs/base/browser/ui/tree/tree';
J
Johannes Rieken 已提交
48
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
J
Johannes Rieken 已提交
49
import { MarkerSeverity } from 'vs/platform/markers/common/markers';
50
import { IViewDescriptorService } from 'vs/workbench/common/views';
51
import { IOpenerService } from 'vs/platform/opener/common/opener';
52
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
53
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
J
Johannes Rieken 已提交
54

J
Johannes Rieken 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
class RequestState {

	constructor(
		private _editorId: string,
		private _modelId: string,
		private _modelVersion: number,
		private _providerCount: number
	) {
		//
	}

	equals(other: RequestState): boolean {
		return other
			&& this._editorId === other._editorId
			&& this._modelId === other._modelId
			&& this._modelVersion === other._modelVersion
			&& this._providerCount === other._providerCount;
	}
}

75
class RequestOracle {
J
Johannes Rieken 已提交
76

M
Matt Bierner 已提交
77
	private readonly _disposables = new DisposableStore();
J
Johannes Rieken 已提交
78
	private _sessionDisposable = new MutableDisposable();
M
Matt Bierner 已提交
79
	private _lastState?: RequestState;
J
Johannes Rieken 已提交
80 81

	constructor(
M
Matt Bierner 已提交
82
		private readonly _callback: (editor: ICodeEditor | undefined, change: IModelContentChangedEvent | undefined) => any,
J
Johannes Rieken 已提交
83
		private readonly _featureRegistry: LanguageFeatureRegistry<any>,
84
		@IEditorService private readonly _editorService: IEditorService,
J
Johannes Rieken 已提交
85
	) {
B
Benjamin Pasero 已提交
86
		_editorService.onDidActiveEditorChange(this._update, this, this._disposables);
J
Johannes Rieken 已提交
87
		_featureRegistry.onDidChange(this._update, this, this._disposables);
88
		this._update();
J
Johannes Rieken 已提交
89 90 91
	}

	dispose(): void {
J
Johannes Rieken 已提交
92 93
		this._disposables.dispose();
		this._sessionDisposable.dispose();
J
Johannes Rieken 已提交
94 95 96
	}

	private _update(): void {
97

98
		let control = this._editorService.activeTextEditorControl;
99
		let codeEditor: ICodeEditor | undefined = undefined;
100 101 102 103
		if (isCodeEditor(control)) {
			codeEditor = control;
		} else if (isDiffEditor(control)) {
			codeEditor = control.getModifiedEditor();
J
Johannes Rieken 已提交
104
		}
105

M
Matt Bierner 已提交
106
		if (!codeEditor || !codeEditor.hasModel()) {
J
Johannes Rieken 已提交
107
			this._lastState = undefined;
108
			this._callback(undefined, undefined);
J
Johannes Rieken 已提交
109 110
			return;
		}
111

J
Johannes Rieken 已提交
112 113 114 115 116 117 118
		let thisState = new RequestState(
			codeEditor.getId(),
			codeEditor.getModel().id,
			codeEditor.getModel().getVersionId(),
			this._featureRegistry.all(codeEditor.getModel()).length
		);

M
Matt Bierner 已提交
119
		if (this._lastState && thisState.equals(this._lastState)) {
H
Howard Hung 已提交
120
			// prevent unnecessary changes...
J
Johannes Rieken 已提交
121
			return;
122
		}
J
Johannes Rieken 已提交
123
		this._lastState = thisState;
124
		this._callback(codeEditor, undefined);
J
Johannes Rieken 已提交
125

126
		let handle: any;
J
Johannes Rieken 已提交
127
		let contentListener = codeEditor.onDidChangeModelContent(event => {
J
Johannes Rieken 已提交
128
			clearTimeout(handle);
129 130
			const timeout = OutlineModel.getRequestDelay(codeEditor!.getModel());
			handle = setTimeout(() => this._callback(codeEditor!, event), timeout);
J
Johannes Rieken 已提交
131
		});
J
Johannes Rieken 已提交
132
		let modeListener = codeEditor.onDidChangeModelLanguage(_ => {
M
Matt Bierner 已提交
133
			this._callback(codeEditor!, undefined);
J
Johannes Rieken 已提交
134
		});
J
Johannes Rieken 已提交
135 136 137
		let disposeListener = codeEditor.onDidDispose(() => {
			this._callback(undefined, undefined);
		});
J
Johannes Rieken 已提交
138
		this._sessionDisposable.value = {
J
Johannes Rieken 已提交
139
			dispose() {
J
Johannes Rieken 已提交
140
				contentListener.dispose();
J
Johannes Rieken 已提交
141
				clearTimeout(handle);
J
Johannes Rieken 已提交
142
				modeListener.dispose();
J
Johannes Rieken 已提交
143
				disposeListener.dispose();
J
Johannes Rieken 已提交
144 145
			}
		};
J
Johannes Rieken 已提交
146 147 148
	}
}

J
Johannes Rieken 已提交
149
class SimpleToggleAction extends Action {
150

J
Johannes Rieken 已提交
151 152 153
	private readonly _listener: IDisposable;

	constructor(state: OutlineViewState, label: string, isChecked: () => boolean, callback: (action: SimpleToggleAction) => any, className?: string) {
M
Matt Bierner 已提交
154
		super(`simple` + defaultGenerator.nextId(), label, className, true, () => {
J
Johannes Rieken 已提交
155 156
			this.checked = !this.checked;
			callback(this);
M
Matt Bierner 已提交
157
			return Promise.resolve();
158
		});
J
Johannes Rieken 已提交
159 160 161 162 163 164 165
		this.checked = isChecked();
		this._listener = state.onDidChange(() => this.checked = isChecked());
	}

	dispose(): void {
		this._listener.dispose();
		super.dispose();
166 167 168
	}
}

169 170

class OutlineViewState {
171 172

	private _followCursor = false;
173
	private _filterOnType = true;
J
Johannes Rieken 已提交
174
	private _sortBy = OutlineSortOrder.ByKind;
175

176
	private readonly _onDidChange = new Emitter<{ followCursor?: boolean, sortBy?: boolean, filterOnType?: boolean }>();
177 178 179 180 181 182 183 184 185 186 187 188 189
	readonly onDidChange = this._onDidChange.event;

	set followCursor(value: boolean) {
		if (value !== this._followCursor) {
			this._followCursor = value;
			this._onDidChange.fire({ followCursor: true });
		}
	}

	get followCursor(): boolean {
		return this._followCursor;
	}

190 191 192 193 194 195 196 197 198 199 200
	get filterOnType() {
		return this._filterOnType;
	}

	set filterOnType(value) {
		if (value !== this._filterOnType) {
			this._filterOnType = value;
			this._onDidChange.fire({ filterOnType: true });
		}
	}

J
Johannes Rieken 已提交
201
	set sortBy(value: OutlineSortOrder) {
202 203 204 205 206 207
		if (value !== this._sortBy) {
			this._sortBy = value;
			this._onDidChange.fire({ sortBy: true });
		}
	}

J
Johannes Rieken 已提交
208
	get sortBy(): OutlineSortOrder {
209 210 211
		return this._sortBy;
	}

212 213
	persist(storageService: IStorageService): void {
		storageService.store('outline/state', JSON.stringify({
J
Johannes Rieken 已提交
214 215 216 217
			followCursor: this.followCursor,
			sortBy: this.sortBy,
			filterOnType: this.filterOnType,
		}), StorageScope.WORKSPACE);
218 219
	}

220 221
	restore(storageService: IStorageService): void {
		let raw = storageService.get('outline/state', StorageScope.WORKSPACE);
222 223 224 225 226 227 228 229 230 231 232
		if (!raw) {
			return;
		}
		let data: any;
		try {
			data = JSON.parse(raw);
		} catch (e) {
			return;
		}
		this.followCursor = data.followCursor;
		this.sortBy = data.sortBy;
J
Johannes Rieken 已提交
233 234 235
		if (typeof data.filterOnType === 'boolean') {
			this.filterOnType = data.filterOnType;
		}
236 237 238
	}
}

S
SteVen Batten 已提交
239
export class OutlinePane extends ViewPane {
J
Johannes Rieken 已提交
240

241
	private _disposables = new Array<IDisposable>();
J
Johannes Rieken 已提交
242

J
Johannes Rieken 已提交
243
	private _editorDisposables = new DisposableStore();
244
	private _outlineViewState = new OutlineViewState();
M
Matt Bierner 已提交
245
	private _requestOracle?: RequestOracle;
J
Johannes Rieken 已提交
246 247 248 249 250 251 252 253
	private _domNode!: HTMLElement;
	private _message!: HTMLDivElement;
	private _inputContainer!: HTMLDivElement;
	private _progressBar!: ProgressBar;
	private _tree!: WorkbenchDataTree<OutlineModel, OutlineItem, FuzzyScore>;
	private _treeDataSource!: OutlineDataSource;
	private _treeRenderer!: OutlineElementRenderer;
	private _treeComparator!: OutlineItemComparator;
254
	private _treeFilter!: OutlineFilter;
255 256
	private _treeStates = new LRUCache<string, IDataTreeViewState>(10);

257 258 259
	private readonly _contextKeyFocused: IContextKey<boolean>;
	private readonly _contextKeyFiltered: IContextKey<boolean>;

J
Johannes Rieken 已提交
260
	constructor(
261
		options: IViewletViewOptions,
J
Johannes Rieken 已提交
262
		@IInstantiationService private readonly _instantiationService: IInstantiationService,
263
		@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
J
Johannes Rieken 已提交
264
		@IThemeService private readonly _themeService: IThemeService,
265
		@IStorageService private readonly _storageService: IStorageService,
266
		@ICodeEditorService private readonly _editorService: ICodeEditorService,
J
Johannes Rieken 已提交
267
		@IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService,
268
		@IConfigurationService private readonly _configurationService: IConfigurationService,
269
		@IKeybindingService keybindingService: IKeybindingService,
270
		@IContextKeyService contextKeyService: IContextKeyService,
J
Johannes Rieken 已提交
271
		@IContextMenuService contextMenuService: IContextMenuService,
272 273
		@IOpenerService openerService: IOpenerService,
		@IThemeService themeService: IThemeService,
274
		@ITelemetryService telemetryService: ITelemetryService,
J
Johannes Rieken 已提交
275
	) {
276
		super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService);
277
		this._outlineViewState.restore(this._storageService);
278 279 280 281
		this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService);
		this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService);
		this._disposables.push(this.onDidFocus(_ => this._contextKeyFocused.set(true)));
		this._disposables.push(this.onDidBlur(_ => this._contextKeyFocused.set(false)));
J
Johannes Rieken 已提交
282 283 284 285
	}

	dispose(): void {
		dispose(this._disposables);
J
Johannes Rieken 已提交
286
		dispose(this._requestOracle);
J
Johannes Rieken 已提交
287
		dispose(this._editorDisposables);
J
Johannes Rieken 已提交
288 289 290
		super.dispose();
	}

291 292
	focus(): void {
		if (this._tree) {
J
Johannes Rieken 已提交
293 294 295
			// focus on tree and fallback to root
			// dom node when the tree cannot take focus,
			// e.g. when hidden
296
			this._tree.domFocus();
J
Johannes Rieken 已提交
297 298 299
			if (!this._tree.isDOMFocused()) {
				this._domNode.focus();
			}
300 301 302
		}
	}

J
Johannes Rieken 已提交
303
	protected renderBody(container: HTMLElement): void {
J
Joao Moreno 已提交
304 305
		super.renderBody(container);

306
		this._domNode = container;
J
Johannes Rieken 已提交
307
		this._domNode.tabIndex = 0;
308
		container.classList.add('outline-pane');
J
Johannes Rieken 已提交
309

310
		let progressContainer = dom.$('.outline-progress');
311
		this._message = dom.$('.outline-message');
312
		this._inputContainer = dom.$('.outline-input');
J
Johannes Rieken 已提交
313 314

		this._progressBar = new ProgressBar(progressContainer);
M
Matt Bierner 已提交
315
		this._register(attachProgressBarStyler(this._progressBar, this._themeService));
J
Johannes Rieken 已提交
316

J
Johannes Rieken 已提交
317
		let treeContainer = dom.$('.outline-tree');
J
Johannes Rieken 已提交
318 319
		dom.append(
			container,
320
			progressContainer, this._message, this._inputContainer, treeContainer
J
Johannes Rieken 已提交
321
		);
J
Johannes Rieken 已提交
322

J
Johannes Rieken 已提交
323 324 325
		this._treeRenderer = this._instantiationService.createInstance(OutlineElementRenderer);
		this._treeDataSource = new OutlineDataSource();
		this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy);
326
		this._treeFilter = this._instantiationService.createInstance(OutlineFilter, 'outline');
327
		this._tree = <WorkbenchDataTree<OutlineModel, OutlineItem, FuzzyScore>>this._instantiationService.createInstance(
328
			WorkbenchDataTree,
329
			'OutlinePane',
330
			treeContainer,
J
Johannes Rieken 已提交
331 332
			new OutlineVirtualDelegate(),
			[new OutlineGroupRenderer(), this._treeRenderer],
333
			// https://github.com/microsoft/TypeScript/issues/32526
J
Johannes Rieken 已提交
334
			this._treeDataSource as IDataSource<OutlineModel, OutlineItem>,
335
			{
336
				expandOnlyOnTwistieClick: true,
J
Johannes Rieken 已提交
337
				multipleSelectionSupport: false,
338
				filterOnType: this._outlineViewState.filterOnType,
339
				sorter: this._treeComparator,
340
				filter: this._treeFilter,
J
Johannes Rieken 已提交
341
				identityProvider: new OutlineIdentityProvider(),
J
Joao Moreno 已提交
342
				keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(),
343
				accessibilityProvider: new OutlineAccessibilityProvider(localize('outline', "Outline")),
J
Johannes Rieken 已提交
344 345
				hideTwistiesOfChildlessElements: true,
				overrideStyles: {
S
SteVen Batten 已提交
346
					listBackground: this.getBackgroundColor()
J
Johannes Rieken 已提交
347
				}
J
Johannes Rieken 已提交
348
			}
349
		);
350

S
SteVen Batten 已提交
351

352
		this._disposables.push(this._tree);
353
		this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this));
354
		this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views }) => {
S
SteVen Batten 已提交
355 356 357 358
			if (views.some(v => v.id === this.id)) {
				this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } });
			}
		}));
359

J
Johannes Rieken 已提交
360
		// override the globally defined behaviour
361 362 363 364
		this._tree.updateOptions({
			filterOnType: this._outlineViewState.filterOnType
		});

365
		// feature: filter on type - keep tree and menu in sync
M
Matt Bierner 已提交
366
		this._register(this._tree.onDidUpdateOptions(e => {
J
Johannes Rieken 已提交
367
			this._outlineViewState.filterOnType = Boolean(e.filterOnType);
368 369 370 371
		}));

		// feature: expand all nodes when filtering (not when finding)
		let viewState: IDataTreeViewState | undefined;
M
Matt Bierner 已提交
372
		this._register(this._tree.onDidChangeTypeFilterPattern(pattern => {
373 374 375 376 377 378 379
			if (!this._tree.options.filterOnType) {
				return;
			}
			if (!viewState && pattern) {
				viewState = this._tree.getViewState();
				this._tree.expandAll();
			} else if (!pattern && viewState) {
J
Johannes Rieken 已提交
380
				this._tree.setInput(this._tree.getInput()!, viewState);
381 382 383
				viewState = undefined;
			}
		}));
J
Johannes Rieken 已提交
384

385
		// feature: toggle icons
M
Matt Bierner 已提交
386
		this._register(this._configurationService.onDidChangeConfiguration(e => {
387
			if (e.affectsConfiguration(OutlineConfigKeys.icons)) {
388
				this._tree.updateChildren();
389
			}
390
			if (e.affectsConfiguration('outline')) {
391 392
				this._tree.refilter();
			}
393
		}));
B
Benjamin Pasero 已提交
394

M
Matt Bierner 已提交
395
		this._register(this.onDidChangeBodyVisibility(visible => {
B
Benjamin Pasero 已提交
396
			if (visible && !this._requestOracle) {
J
Johannes Rieken 已提交
397
				this._requestOracle = this._instantiationService.createInstance(RequestOracle, (editor, event) => this._doUpdate(editor, event), DocumentSymbolProviderRegistry);
B
Benjamin Pasero 已提交
398 399 400 401 402 403
			} else if (!visible) {
				dispose(this._requestOracle);
				this._requestOracle = undefined;
				this._doUpdate(undefined, undefined);
			}
		}));
J
Johannes Rieken 已提交
404 405
	}

J
Johannes Rieken 已提交
406
	protected layoutBody(height: number, width: number): void {
J
João Moreno 已提交
407
		super.layoutBody(height, width);
J
Johannes Rieken 已提交
408
		this._tree.layout(height, width);
J
Johannes Rieken 已提交
409 410
	}

J
Johannes Rieken 已提交
411 412
	getActions(): IAction[] {
		return [
413
			new Action('collapse', localize('collapse', "Collapse All"), 'explorer-action codicon-collapse-all', true, () => {
I
isidor 已提交
414
				return new CollapseAction(() => this._tree, true, undefined).run();
J
Johannes Rieken 已提交
415 416 417 418
			})
		];
	}

419
	getSecondaryActions(): IAction[] {
M
Matt Bierner 已提交
420
		const group = this._register(new RadioGroup([
J
Johannes Rieken 已提交
421 422
			new SimpleToggleAction(this._outlineViewState, localize('sortByPosition', "Sort By: Position"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByPosition, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByPosition),
			new SimpleToggleAction(this._outlineViewState, localize('sortByName', "Sort By: Name"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByName, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByName),
423
			new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Category"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind),
M
Matt Bierner 已提交
424 425
		]));
		const result = [
J
Johannes Rieken 已提交
426 427
			new SimpleToggleAction(this._outlineViewState, localize('followCur', "Follow Cursor"), () => this._outlineViewState.followCursor, action => this._outlineViewState.followCursor = action.checked),
			new SimpleToggleAction(this._outlineViewState, localize('filterOnType', "Filter on Type"), () => this._outlineViewState.filterOnType, action => this._outlineViewState.filterOnType = action.checked),
J
Johannes Rieken 已提交
428 429 430
			new Separator(),
			...group.actions,
		];
M
Matt Bierner 已提交
431 432 433
		for (const r of result) {
			this._register(r);
		}
J
Johannes Rieken 已提交
434 435

		return result;
436 437
	}

438
	private _onDidChangeUserState(e: { followCursor?: boolean, sortBy?: boolean, filterOnType?: boolean }) {
439
		this._outlineViewState.persist(this._storageService);
440 441 442 443 444
		if (e.followCursor) {
			// todo@joh update immediately
		}
		if (e.sortBy) {
			this._treeComparator.type = this._outlineViewState.sortBy;
J
Joao Moreno 已提交
445
			this._tree.resort();
446
		}
447
		if (e.filterOnType) {
448 449 450
			this._tree.updateOptions({
				filterOnType: this._outlineViewState.filterOnType
			});
451
		}
452 453
	}

454
	private _showMessage(message: string) {
455
		this._domNode.classList.add('message');
J
Johannes Rieken 已提交
456
		this._tree.setInput(undefined!);
J
Johannes Rieken 已提交
457
		this._progressBar.stop().hide();
458 459 460
		this._message.innerText = escape(message);
	}

J
Johannes Rieken 已提交
461
	private static _createOutlineModel(model: ITextModel, disposables: DisposableStore): Promise<OutlineModel | undefined> {
462
		let promise = createCancelablePromise(token => OutlineModel.create(model, token));
J
Johannes Rieken 已提交
463
		disposables.add({ dispose() { promise.cancel(); } });
464 465 466 467 468 469 470 471
		return promise.catch(err => {
			if (!isPromiseCanceledError(err)) {
				throw err;
			}
			return undefined;
		});
	}

M
Matt Bierner 已提交
472
	private async _doUpdate(editor: ICodeEditor | undefined, event: IModelContentChangedEvent | undefined): Promise<void> {
J
Johannes Rieken 已提交
473
		this._editorDisposables.clear();
474

J
Johannes Rieken 已提交
475

J
Johannes Rieken 已提交
476
		const oldModel = this._tree.getInput();
477 478 479

		// persist state
		if (oldModel) {
480
			this._treeStates.set(oldModel.uri.toString(), this._tree.getViewState());
481 482
		}

M
Matt Bierner 已提交
483
		if (!editor || !editor.hasModel() || !DocumentSymbolProviderRegistry.has(editor.getModel())) {
J
Johannes Rieken 已提交
484
			return this._showMessage(localize('no-editor', "The active editor cannot provide outline information."));
J
Johannes Rieken 已提交
485 486
		}

487
		const textModel = editor.getModel();
J
Johannes Rieken 已提交
488 489 490 491 492 493 494 495

		let loadingMessage: IDisposable | undefined;
		if (!oldModel) {
			loadingMessage = new TimeoutTimer(
				() => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))),
				100
			);
		}
496 497 498

		const requestDelay = OutlineModel.getRequestDelay(textModel);
		this._progressBar.infinite().show(requestDelay);
499

500
		const createdModel = await OutlinePane._createOutlineModel(textModel, this._editorDisposables);
501
		dispose(loadingMessage);
M
Matt Bierner 已提交
502
		if (!createdModel) {
J
Johannes Rieken 已提交
503 504 505
			return;
		}

M
Matt Bierner 已提交
506
		let newModel = createdModel;
507
		if (TreeElement.empty(newModel)) {
508
			return this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", basename(textModel.uri)));
509 510
		}

511
		this._domNode.classList.remove('message');
512

513
		if (event && oldModel && textModel.getLineCount() >= 25) {
514 515
			// heuristic: when the symbols-to-lines ratio changes by 50% between edits
			// wait a little (and hope that the next change isn't as drastic).
J
Johannes Rieken 已提交
516
			let newSize = TreeElement.size(newModel);
517 518 519 520 521 522
			let newLength = textModel.getValueLength();
			let newRatio = newSize / newLength;
			let oldSize = TreeElement.size(oldModel);
			let oldLength = newLength - event.changes.reduce((prev, value) => prev + value.rangeLength, 0);
			let oldRatio = oldSize / oldLength;
			if (newRatio <= oldRatio * 0.5 || newRatio >= oldRatio * 1.5) {
J
Johannes Rieken 已提交
523 524

				let waitPromise = new Promise<boolean>(resolve => {
M
Matt Bierner 已提交
525
					let handle: any = setTimeout(() => {
J
Johannes Rieken 已提交
526 527 528 529 530 531 532 533 534 535 536 537
						handle = undefined;
						resolve(true);
					}, 2000);
					this._disposables.push({
						dispose() {
							clearTimeout(handle);
							resolve(false);
						}
					});
				});

				if (!await waitPromise) {
538 539 540 541 542
					return;
				}
			}
		}

543 544
		this._progressBar.stop().hide();

545
		if (oldModel && oldModel.merge(newModel)) {
546
			this._tree.updateChildren();
547
			newModel = oldModel;
548
		} else {
549
			let state = this._treeStates.get(newModel.uri.toString());
550
			this._tree.setInput(newModel, state);
551
		}
J
Johannes Rieken 已提交
552

J
Johannes Rieken 已提交
553 554 555 556 557
		// transfer focus from domNode to the tree
		if (this._domNode === document.activeElement) {
			this._tree.domFocus();
		}

J
Johannes Rieken 已提交
558
		this._editorDisposables.add(toDisposable(() => this._contextKeyFiltered.reset()));
559

560 561
		// feature: reveal outline selection in editor
		// on change -> reveal/select defining range
J
Johannes Rieken 已提交
562 563
		this._editorDisposables.add(this._tree.onDidOpen(e => {

564
			let [first] = e.elements;
565 566 567 568 569 570
			if (!(first instanceof OutlineElement)) {
				return;
			}

			let focus = false;
			let aside = false;
571 572 573
			// todo@Joh
			if (e.browserEvent) {
				if (e.browserEvent.type === 'keydown') {
574
					focus = true;
575 576 577 578 579
				} else if (e.browserEvent.type === 'click') {
					const event = new StandardMouseEvent(e.browserEvent as MouseEvent);
					focus = e.browserEvent.detail === 2;
					aside = (!this._tree.useAltAsMultipleSelectionModifier && event.altKey)
						|| (this._tree.useAltAsMultipleSelectionModifier && (event.ctrlKey || event.metaKey));
580
				}
581
			}
582
			this._revealTreeSelection(newModel, first, focus, aside);
583 584 585
		}));

		// feature: reveal editor selection in outline
586
		this._revealEditorSelection(newModel, editor.getSelection());
587
		const versionIdThen = textModel.getVersionId();
J
Johannes Rieken 已提交
588
		this._editorDisposables.add(editor.onDidChangeCursorSelection(e => {
J
Johannes Rieken 已提交
589 590 591
			// first check if the document has changed and stop revealing the
			// cursor position iff it has -> we will update/recompute the
			// outline view then anyways
592
			if (!textModel.isDisposed() && textModel.getVersionId() === versionIdThen) {
593
				this._revealEditorSelection(newModel, e.selection);
J
Johannes Rieken 已提交
594 595
			}
		}));
596 597

		// feature: show markers in outline
J
Johannes Rieken 已提交
598
		const updateMarker = (model: ITextModel, ignoreEmpty?: boolean) => {
599 600 601
			if (!this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) {
				return;
			}
J
Johannes Rieken 已提交
602
			if (model !== textModel) {
603 604
				return;
			}
J
Johannes Rieken 已提交
605 606 607 608 609 610 611 612
			const markers: IOutlineMarker[] = [];
			for (const [range, marker] of this._markerDecorationService.getLiveMarkers(textModel)) {
				if (marker.severity === MarkerSeverity.Error || marker.severity === MarkerSeverity.Warning) {
					markers.push({ ...range, severity: marker.severity });
				}
			}
			if (markers.length > 0 || !ignoreEmpty) {
				newModel.updateMarker(markers);
613
				this._tree.updateChildren();
614 615
			}
		};
J
Johannes Rieken 已提交
616
		updateMarker(textModel, true);
J
Johannes Rieken 已提交
617
		this._editorDisposables.add(this._markerDecorationService.onDidChangeMarker(updateMarker));
618

J
Johannes Rieken 已提交
619
		this._editorDisposables.add(this.configurationService.onDidChangeConfiguration(e => {
620
			if (e.affectsConfiguration(OutlineConfigKeys.problemsBadges) || e.affectsConfiguration(OutlineConfigKeys.problemsColors)) {
621
				this._tree.updateChildren();
622 623 624 625 626 627
				return;
			}
			if (!e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) {
				return;
			}
			if (!this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) {
628
				newModel.updateMarker([]);
629
				this._tree.updateChildren();
630
			} else {
J
Johannes Rieken 已提交
631
				updateMarker(textModel, true);
632 633
			}
		}));
634 635
	}

636
	private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise<void> {
637 638
		await this._editorService.openCodeEditor(
			{
639
				resource: model.uri,
640 641 642
				options: {
					preserveFocus: !focus,
					selection: Range.collapseToStart(element.symbol.selectionRange),
643
					selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport,
644 645 646 647 648
				}
			},
			this._editorService.getActiveCodeEditor(),
			aside
		);
649 650
	}

J
Johannes Rieken 已提交
651
	private _revealEditorSelection(model: OutlineModel, selection: Selection): void {
J
Johannes Rieken 已提交
652
		if (!this._outlineViewState.followCursor || !this._tree.getInput() || !selection) {
653 654
			return;
		}
655
		let [first] = this._tree.getSelection();
J
Johannes Rieken 已提交
656
		let item = model.getItemEnclosingPosition({
657 658
			lineNumber: selection.selectionStartLineNumber,
			column: selection.selectionStartColumn
659
		}, first instanceof OutlineElement ? first : undefined);
J
Johannes Rieken 已提交
660 661 662 663 664
		if (!item) {
			// nothing to reveal
			return;
		}
		let top = this._tree.getRelativeTop(item);
J
Johannes Rieken 已提交
665 666
		if (top === null) {
			this._tree.reveal(item, 0.5);
667
		}
J
Johannes Rieken 已提交
668 669
		this._tree.setFocus([item]);
		this._tree.setSelection([item]);
670 671
	}
}