breadcrumbsModel.ts 6.0 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';
J
Johannes Rieken 已提交
24 25 26 27 28 29 30 31 32 33

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

export type BreadcrumbElement = FileElement | OutlineGroup | OutlineElement;

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

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

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

	private _outlineElements: (OutlineGroup | OutlineElement)[] = [];
J
Johannes Rieken 已提交
42 43 44 45 46 47 48 49 50 51
	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,
	) {
52
		this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService);
J
Johannes Rieken 已提交
53 54 55 56 57 58 59 60
		this._bindToEditor();
		this._onDidUpdate.fire(this);
	}

	dispose(): void {
		dispose(this._disposables);
	}

61 62 63 64
	isRelative(): boolean {
		return Boolean(this._fileInfo.folder);
	}

J
Johannes Rieken 已提交
65
	getElements(): ReadonlyArray<BreadcrumbElement> {
66
		return [].concat(this._fileInfo.path, this._outlineElements);
J
Johannes Rieken 已提交
67 68
	}

69
	private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
J
Johannes Rieken 已提交
70 71

		if (uri.scheme === Schemas.untitled) {
72 73 74 75 76
			return {
				showFolder: false,
				folder: undefined,
				path: []
			};
J
Johannes Rieken 已提交
77 78
		}

79 80 81 82 83 84 85 86
		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 已提交
87 88
				break;
			}
J
Johannes Rieken 已提交
89
			info.path.unshift(new FileElement(uri, info.path.length === 0));
90
			uri = uri.with({ path: paths.dirname(uri.path) });
J
Johannes Rieken 已提交
91
		}
92
		return info;
J
Johannes Rieken 已提交
93 94 95 96 97 98
	}

	private _bindToEditor(): void {
		if (!this._editor) {
			return;
		}
J
Johannes Rieken 已提交
99
		// update as model changes
J
Johannes Rieken 已提交
100 101 102
		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 已提交
103
		this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true)));
J
Johannes Rieken 已提交
104 105 106 107
		this._updateOutline();

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

J
Johannes Rieken 已提交
110
	private _updateOutline(didChangeContent?: boolean): void {
J
Johannes Rieken 已提交
111 112

		this._outlineDisposables = dispose(this._outlineDisposables);
J
Johannes Rieken 已提交
113 114 115
		if (!didChangeContent) {
			this._updateOutlineElements([]);
		}
J
Johannes Rieken 已提交
116

J
Johannes Rieken 已提交
117 118
		const buffer = this._editor.getModel();
		if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
J
Johannes Rieken 已提交
119 120 121 122
			return;
		}

		const source = new CancellationTokenSource();
J
Johannes Rieken 已提交
123 124
		const versionIdThen = buffer.getVersionId();
		const timeout = new TimeoutTimer();
J
Johannes Rieken 已提交
125 126 127 128 129

		this._outlineDisposables.push({
			dispose: () => {
				source.cancel();
				source.dispose();
J
Johannes Rieken 已提交
130
				timeout.dispose();
J
Johannes Rieken 已提交
131 132
			}
		});
J
Johannes Rieken 已提交
133

J
Johannes Rieken 已提交
134
		OutlineModel.create(buffer, source.token).then(model => {
135 136 137 138

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

J
Johannes Rieken 已提交
139 140 141
			this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
			this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
				timeout.cancelAndSet(() => {
J
Johannes Rieken 已提交
142
					if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
J
Johannes Rieken 已提交
143 144 145 146
						this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
					}
				}, 150);
			}));
J
Johannes Rieken 已提交
147
		}).catch(err => {
J
Johannes Rieken 已提交
148
			this._updateOutlineElements([]);
J
Johannes Rieken 已提交
149 150 151
			onUnexpectedError(err);
		});
	}
J
Johannes Rieken 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

	private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineGroup | OutlineElement)[] {
		if (!model) {
			return [];
		}
		let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position);
		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 已提交
170
		return chain.reverse();
J
Johannes Rieken 已提交
171 172 173
	}

	private _updateOutlineElements(elements: (OutlineGroup | OutlineElement)[]): void {
J
Johannes Rieken 已提交
174
		if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
J
Johannes Rieken 已提交
175 176 177 178 179
			this._outlineElements = elements;
			this._onDidUpdate.fire(this);
		}
	}

J
Johannes Rieken 已提交
180
	private static _outlineElementEquals(a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): boolean {
J
Johannes Rieken 已提交
181 182 183 184 185 186 187 188
		if (a === b) {
			return true;
		} else if (!a || !b) {
			return false;
		} else {
			return a.id === b.id;
		}
	}
J
Johannes Rieken 已提交
189
}