breadcrumbsModel.ts 7.7 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 7 8 9 10
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';
J
Joao Moreno 已提交
11
import { Emitter, Event } from 'vs/base/common/event';
J
Johannes Rieken 已提交
12
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
13
import { isEqual, dirname } from 'vs/base/common/resources';
14
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
15 16
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IPosition } from 'vs/editor/common/core/position';
J
Johannes Rieken 已提交
17
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
18
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
19
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
J
Johannes Rieken 已提交
20
import { Schemas } from 'vs/base/common/network';
21 22
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
23
import { FileKind } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
24 25 26 27

export class FileElement {
	constructor(
		readonly uri: URI,
28
		readonly kind: FileKind
J
Johannes Rieken 已提交
29 30 31
	) { }
}

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

34
type FileInfo = { path: FileElement[], folder: IWorkspaceFolder };
35

J
Johannes Rieken 已提交
36 37 38
export class EditorBreadcrumbsModel {

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

41 42 43
	private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
	private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;

44
	private _outlineElements: (OutlineModel | OutlineGroup | OutlineElement)[] = [];
J
Johannes Rieken 已提交
45 46 47 48 49 50 51 52 53
	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,
54
		@IConfigurationService configurationService: IConfigurationService,
J
Johannes Rieken 已提交
55
	) {
56 57 58 59 60 61 62

		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)));

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

	dispose(): void {
69 70
		this._cfgFilePath.dispose();
		this._cfgSymbolPath.dispose();
J
Johannes Rieken 已提交
71 72 73
		dispose(this._disposables);
	}

74 75 76 77
	isRelative(): boolean {
		return Boolean(this._fileInfo.folder);
	}

J
Johannes Rieken 已提交
78
	getElements(): ReadonlyArray<BreadcrumbElement> {
79 80 81
		let result: BreadcrumbElement[] = [];

		// file path elements
J
Johannes Rieken 已提交
82
		if (this._cfgFilePath.getValue() === 'on') {
83
			result = result.concat(this._fileInfo.path);
J
Johannes Rieken 已提交
84
		} else if (this._cfgFilePath.getValue() === 'last' && this._fileInfo.path.length > 0) {
85 86 87 88
			result = result.concat(this._fileInfo.path.slice(-1));
		}

		// symbol path elements
J
Johannes Rieken 已提交
89
		if (this._cfgSymbolPath.getValue() === 'on') {
90
			result = result.concat(this._outlineElements);
J
Johannes Rieken 已提交
91
		} else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) {
92 93 94 95
			result = result.concat(this._outlineElements.slice(-1));
		}

		return result;
J
Johannes Rieken 已提交
96 97
	}

98
	private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
J
Johannes Rieken 已提交
99 100

		if (uri.scheme === Schemas.untitled) {
101 102 103 104
			return {
				folder: undefined,
				path: []
			};
J
Johannes Rieken 已提交
105 106
		}

107 108 109 110 111 112 113
		let info: FileInfo = {
			folder: workspaceService.getWorkspaceFolder(uri),
			path: []
		};

		while (uri.path !== '/') {
			if (info.folder && isEqual(info.folder.uri, uri)) {
J
Johannes Rieken 已提交
114 115
				break;
			}
116
			info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
117
			uri = dirname(uri);
J
Johannes Rieken 已提交
118
		}
119 120 121 122

		if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
			info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
		}
123
		return info;
J
Johannes Rieken 已提交
124 125 126 127 128 129
	}

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

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

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

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

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

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

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

J
Johannes Rieken 已提交
165
		OutlineModel.create(buffer, source.token).then(model => {
166 167 168 169 170 171 172 173 174 175 176
			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(() => {
J
Johannes Rieken 已提交
177
						if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && this._editor.getModel()) {
178 179 180 181 182
							this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
						}
					}, 150);
				}));
			}
J
Johannes Rieken 已提交
183
		}).catch(err => {
J
Johannes Rieken 已提交
184
			this._updateOutlineElements([]);
J
Johannes Rieken 已提交
185 186 187
			onUnexpectedError(err);
		});
	}
J
Johannes Rieken 已提交
188

189
	private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineModel | OutlineGroup | OutlineElement)[] {
J
Johannes Rieken 已提交
190 191 192 193
		if (!model) {
			return [];
		}
		let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position);
194 195 196
		if (!item) {
			return [model];
		}
J
Johannes Rieken 已提交
197 198 199 200 201 202 203 204 205 206 207 208
		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 已提交
209
		return chain.reverse();
J
Johannes Rieken 已提交
210 211
	}

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

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