breadcrumbsModel.ts 7.7 KB
Newer Older
J
Johannes Rieken 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Johannes Rieken 已提交
8 9 10 11 12 13 14
import { equals } from 'vs/base/common/arrays';
import { TimeoutTimer } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { size } from 'vs/base/common/collections';
import { onUnexpectedError } from 'vs/base/common/errors';
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
15 16
import * as paths from 'vs/base/common/paths';
import { isEqual } from 'vs/base/common/resources';
J
Johannes Rieken 已提交
17 18 19
import URI from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition } from 'vs/editor/common/core/position';
J
Johannes Rieken 已提交
20
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
21
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
22
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
J
Johannes Rieken 已提交
23
import { Schemas } from 'vs/base/common/network';
24 25
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
26
import { FileKind } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
27 28 29 30

export class FileElement {
	constructor(
		readonly uri: URI,
31
		readonly kind: FileKind
J
Johannes Rieken 已提交
32 33 34
	) { }
}

35
export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement;
J
Johannes Rieken 已提交
36

37 38
type FileInfo = { path: FileElement[], folder: IWorkspaceFolder, showFolder: boolean };

J
Johannes Rieken 已提交
39 40 41
export class EditorBreadcrumbsModel {

	private readonly _disposables: IDisposable[] = [];
42
	private readonly _fileInfo: FileInfo;
J
Johannes Rieken 已提交
43

44 45 46
	private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
	private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;

47
	private _outlineElements: (OutlineModel | OutlineGroup | OutlineElement)[] = [];
J
Johannes Rieken 已提交
48 49 50 51 52 53 54 55 56
	private _outlineDisposables: IDisposable[] = [];

	private _onDidUpdate = new Emitter<this>();
	readonly onDidUpdate: Event<this> = this._onDidUpdate.event;

	constructor(
		private readonly _uri: URI,
		private readonly _editor: ICodeEditor | undefined,
		@IWorkspaceContextService workspaceService: IWorkspaceContextService,
57
		@IConfigurationService configurationService: IConfigurationService,
J
Johannes Rieken 已提交
58
	) {
59 60 61 62 63 64 65

		this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
		this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);

		this._disposables.push(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
		this._disposables.push(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));

66
		this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService);
J
Johannes Rieken 已提交
67 68 69 70 71
		this._bindToEditor();
		this._onDidUpdate.fire(this);
	}

	dispose(): void {
72 73
		this._cfgFilePath.dispose();
		this._cfgSymbolPath.dispose();
J
Johannes Rieken 已提交
74 75 76
		dispose(this._disposables);
	}

77 78 79 80
	isRelative(): boolean {
		return Boolean(this._fileInfo.folder);
	}

J
Johannes Rieken 已提交
81
	getElements(): ReadonlyArray<BreadcrumbElement> {
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
		let result: BreadcrumbElement[] = [];

		// file path elements
		if (this._cfgFilePath.value === 'on') {
			result = result.concat(this._fileInfo.path);
		} else if (this._cfgFilePath.value === 'last' && this._fileInfo.path.length > 0) {
			result = result.concat(this._fileInfo.path.slice(-1));
		}

		// symbol path elements
		if (this._cfgSymbolPath.value === 'on') {
			result = result.concat(this._outlineElements);
		} else if (this._cfgSymbolPath.value === 'last' && this._outlineElements.length > 0) {
			result = result.concat(this._outlineElements.slice(-1));
		}

		return result;
J
Johannes Rieken 已提交
99 100
	}

101
	private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
J
Johannes Rieken 已提交
102 103

		if (uri.scheme === Schemas.untitled) {
104 105 106 107 108
			return {
				showFolder: false,
				folder: undefined,
				path: []
			};
J
Johannes Rieken 已提交
109 110
		}

111 112 113 114 115 116 117 118
		let info: FileInfo = {
			showFolder: workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE,
			folder: workspaceService.getWorkspaceFolder(uri),
			path: []
		};

		while (uri.path !== '/') {
			if (info.folder && isEqual(info.folder.uri, uri)) {
J
Johannes Rieken 已提交
119 120
				break;
			}
121
			info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
122
			uri = uri.with({ path: paths.dirname(uri.path) });
J
Johannes Rieken 已提交
123
		}
124
		return info;
J
Johannes Rieken 已提交
125 126 127 128 129 130
	}

	private _bindToEditor(): void {
		if (!this._editor) {
			return;
		}
J
Johannes Rieken 已提交
131
		// update as model changes
J
Johannes Rieken 已提交
132 133 134
		this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline()));
		this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline()));
		this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline()));
J
Johannes Rieken 已提交
135
		this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true)));
J
Johannes Rieken 已提交
136 137 138 139
		this._updateOutline();

		// stop when editor dies
		this._disposables.push(this._editor.onDidDispose(() => this._outlineDisposables = dispose(this._outlineDisposables)));
J
Johannes Rieken 已提交
140 141
	}

J
Johannes Rieken 已提交
142
	private _updateOutline(didChangeContent?: boolean): void {
J
Johannes Rieken 已提交
143 144

		this._outlineDisposables = dispose(this._outlineDisposables);
J
Johannes Rieken 已提交
145 146 147
		if (!didChangeContent) {
			this._updateOutlineElements([]);
		}
J
Johannes Rieken 已提交
148

J
Johannes Rieken 已提交
149 150
		const buffer = this._editor.getModel();
		if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
J
Johannes Rieken 已提交
151 152 153 154
			return;
		}

		const source = new CancellationTokenSource();
J
Johannes Rieken 已提交
155 156
		const versionIdThen = buffer.getVersionId();
		const timeout = new TimeoutTimer();
J
Johannes Rieken 已提交
157 158 159 160 161

		this._outlineDisposables.push({
			dispose: () => {
				source.cancel();
				source.dispose();
J
Johannes Rieken 已提交
162
				timeout.dispose();
J
Johannes Rieken 已提交
163 164
			}
		});
J
Johannes Rieken 已提交
165

J
Johannes Rieken 已提交
166
		OutlineModel.create(buffer, source.token).then(model => {
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
			if (TreeElement.empty(model)) {
				// empty -> no outline elements
				this._updateOutlineElements([]);

			} else {
				// copy the model
				model = model.adopt();

				this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
				this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
					timeout.cancelAndSet(() => {
						if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
							this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
						}
					}, 150);
				}));
			}
J
Johannes Rieken 已提交
184
		}).catch(err => {
J
Johannes Rieken 已提交
185
			this._updateOutlineElements([]);
J
Johannes Rieken 已提交
186 187 188
			onUnexpectedError(err);
		});
	}
J
Johannes Rieken 已提交
189

190
	private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineModel | OutlineGroup | OutlineElement)[] {
J
Johannes Rieken 已提交
191 192 193 194
		if (!model) {
			return [];
		}
		let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position);
195 196 197
		if (!item) {
			return [model];
		}
J
Johannes Rieken 已提交
198 199 200 201 202 203 204 205 206 207 208 209
		let chain: (OutlineGroup | OutlineElement)[] = [];
		while (item) {
			chain.push(item);
			let parent = item.parent;
			if (parent instanceof OutlineModel) {
				break;
			}
			if (parent instanceof OutlineGroup && size(parent.parent.children) === 1) {
				break;
			}
			item = parent;
		}
J
Johannes Rieken 已提交
210
		return chain.reverse();
J
Johannes Rieken 已提交
211 212
	}

213
	private _updateOutlineElements(elements: (OutlineModel | OutlineGroup | OutlineElement)[]): void {
J
Johannes Rieken 已提交
214
		if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
J
Johannes Rieken 已提交
215 216 217 218 219
			this._outlineElements = elements;
			this._onDidUpdate.fire(this);
		}
	}

220
	private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
J
Johannes Rieken 已提交
221 222 223 224 225 226 227 228
		if (a === b) {
			return true;
		} else if (!a || !b) {
			return false;
		} else {
			return a.id === b.id;
		}
	}
J
Johannes Rieken 已提交
229
}