breadcrumbsModel.ts 7.5 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';
J
Johannes Rieken 已提交
21
import { OutlineElement, OutlineGroup, OutlineModel } 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';
J
Johannes Rieken 已提交
26 27 28 29 30 31 32 33

export class FileElement {
	constructor(
		readonly uri: URI,
		readonly isFile: boolean
	) { }
}

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

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

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

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

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

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

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

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

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

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

J
Johannes Rieken 已提交
80
	getElements(): ReadonlyArray<BreadcrumbElement> {
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
		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 已提交
98 99
	}

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

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

110 111 112 113 114 115 116 117
		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 已提交
118 119
				break;
			}
J
Johannes Rieken 已提交
120
			info.path.unshift(new FileElement(uri, info.path.length === 0));
121
			uri = uri.with({ path: paths.dirname(uri.path) });
J
Johannes Rieken 已提交
122
		}
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
Johannes Rieken 已提交
134
		this._disposables.push(debounceEvent(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

			// copy the model
			model = model.adopt();

J
Johannes Rieken 已提交
170 171 172
			this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
			this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
				timeout.cancelAndSet(() => {
J
Johannes Rieken 已提交
173
					if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
J
Johannes Rieken 已提交
174 175 176 177
						this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
					}
				}, 150);
			}));
J
Johannes Rieken 已提交
178
		}).catch(err => {
J
Johannes Rieken 已提交
179
			this._updateOutlineElements([]);
J
Johannes Rieken 已提交
180 181 182
			onUnexpectedError(err);
		});
	}
J
Johannes Rieken 已提交
183

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

207
	private _updateOutlineElements(elements: (OutlineModel | OutlineGroup | OutlineElement)[]): void {
J
Johannes Rieken 已提交
208
		if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
J
Johannes Rieken 已提交
209 210 211 212 213
			this._outlineElements = elements;
			this._onDidUpdate.fire(this);
		}
	}

214
	private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
J
Johannes Rieken 已提交
215 216 217 218 219 220 221 222
		if (a === b) {
			return true;
		} else if (!a || !b) {
			return false;
		} else {
			return a.id === b.id;
		}
	}
J
Johannes Rieken 已提交
223
}