breadcrumbsModel.ts 5.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';
J
Johannes Rieken 已提交
21 22
import { OutlineElement, OutlineGroup, OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { IWorkspaceContextService } 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 34 35 36 37

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

export type BreadcrumbElement = FileElement | OutlineGroup | OutlineElement;

export class EditorBreadcrumbsModel {

	private readonly _disposables: IDisposable[] = [];
	private readonly _fileElements: FileElement[] = [];
J
Johannes Rieken 已提交
38 39

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

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

	getElements(): ReadonlyArray<BreadcrumbElement> {
J
Johannes Rieken 已提交
60
		return [].concat(this._fileElements, this._outlineElements);
J
Johannes Rieken 已提交
61 62 63
	}

	private static _getFileElements(uri: URI, workspaceService: IWorkspaceContextService): FileElement[] {
J
Johannes Rieken 已提交
64 65 66 67 68

		if (uri.scheme === Schemas.untitled) {
			return [];
		}

J
Johannes Rieken 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
		let result: FileElement[] = [];
		let workspace = workspaceService.getWorkspaceFolder(uri);
		let path = uri.path;
		while (path !== '/') {
			if (workspace && isEqual(workspace.uri, uri)) {
				break;
			}
			result.push(new FileElement(uri, result.length === 0));
			path = paths.dirname(path);
			uri = uri.with({ path });
		}
		return result.reverse();
	}

	private _bindToEditor(): void {
		if (!this._editor) {
			return;
		}
J
Johannes Rieken 已提交
87
		// update as model changes
J
Johannes Rieken 已提交
88 89 90
		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 已提交
91
		this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true)));
J
Johannes Rieken 已提交
92 93 94 95
		this._updateOutline();

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

J
Johannes Rieken 已提交
98
	private _updateOutline(didChangeContent?: boolean): void {
J
Johannes Rieken 已提交
99 100

		this._outlineDisposables = dispose(this._outlineDisposables);
J
Johannes Rieken 已提交
101 102 103
		if (!didChangeContent) {
			this._updateOutlineElements([]);
		}
J
Johannes Rieken 已提交
104

J
Johannes Rieken 已提交
105 106
		const buffer = this._editor.getModel();
		if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
J
Johannes Rieken 已提交
107 108 109 110
			return;
		}

		const source = new CancellationTokenSource();
J
Johannes Rieken 已提交
111 112
		const versionIdThen = buffer.getVersionId();
		const timeout = new TimeoutTimer();
J
Johannes Rieken 已提交
113 114 115 116 117

		this._outlineDisposables.push({
			dispose: () => {
				source.cancel();
				source.dispose();
J
Johannes Rieken 已提交
118
				timeout.dispose();
J
Johannes Rieken 已提交
119 120
			}
		});
J
Johannes Rieken 已提交
121

J
Johannes Rieken 已提交
122
		OutlineModel.create(buffer, source.token).then(model => {
123 124 125 126

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

J
Johannes Rieken 已提交
127 128 129
			this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
			this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
				timeout.cancelAndSet(() => {
J
Johannes Rieken 已提交
130
					if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
J
Johannes Rieken 已提交
131 132 133 134
						this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
					}
				}, 150);
			}));
J
Johannes Rieken 已提交
135
		}).catch(err => {
J
Johannes Rieken 已提交
136
			this._updateOutlineElements([]);
J
Johannes Rieken 已提交
137 138 139
			onUnexpectedError(err);
		});
	}
J
Johannes Rieken 已提交
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157

	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 已提交
158
		return chain.reverse();
J
Johannes Rieken 已提交
159 160 161
	}

	private _updateOutlineElements(elements: (OutlineGroup | OutlineElement)[]): void {
J
Johannes Rieken 已提交
162
		if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
J
Johannes Rieken 已提交
163 164 165 166 167
			this._outlineElements = elements;
			this._onDidUpdate.fire(this);
		}
	}

J
Johannes Rieken 已提交
168
	private static _outlineElementEquals(a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): boolean {
J
Johannes Rieken 已提交
169 170 171 172 173 174 175 176
		if (a === b) {
			return true;
		} else if (!a || !b) {
			return false;
		} else {
			return a.id === b.id;
		}
	}
J
Johannes Rieken 已提交
177
}