Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
7
import { onUnexpectedError } from '../../../../base/common/errors.js';
8
import { Emitter, Event } from '../../../../base/common/event.js';
9
import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
10
import { Schemas, matchesSomeScheme } from '../../../../base/common/network.js';
11
import { dirname, isEqual } from '../../../../base/common/resources.js';
12
import { URI } from '../../../../base/common/uri.js';
13
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14
import { FileKind } from '../../../../platform/files/common/files.js';
15
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';
16
import { BreadcrumbsConfig } from './breadcrumbs.js';
17
import { IEditorPane } from '../../../common/editor.js';
18
import { IOutline, IOutlineService, OutlineTarget } from '../../../services/outline/browser/outline.js';
19
20
export class FileElement {
21
constructor(
22
readonly uri: URI,
23
readonly kind: FileKind
24
) { }
25
}
26
27
type FileInfo = { path: FileElement[]; folder?: IWorkspaceFolder };
28
29
export class OutlineElement2 {
30
constructor(
31
readonly element: IOutline<any> | any,
32
readonly outline: IOutline<any>
33
) { }
34
}
35
36
export class BreadcrumbsModel {
37
38
private readonly _disposables = new DisposableStore();
39
private _fileInfo: FileInfo;
40
41
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
42
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
43
44
private readonly _currentOutline = new MutableDisposable<IOutline<any>>();
45
private readonly _outlineDisposables = new DisposableStore();
46
47
private readonly _onDidUpdate = new Emitter<this>();
48
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
49
50
constructor(
51
readonly resource: URI,
52
readonly editor: IEditorPane | undefined,
53
@IConfigurationService configurationService: IConfigurationService,
54
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
55
@IOutlineService private readonly _outlineService: IOutlineService,
56
) {
57
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
58
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);
59
60
this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
61
this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
62
this._workspaceService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspaceFolders, this, this._disposables);
63
this._fileInfo = this._initFilePathInfo(resource);
64
65
if (editor) {
66
this._bindToEditor(editor);
67
this._disposables.add(_outlineService.onDidChange(() => this._bindToEditor(editor)));
68
this._disposables.add(editor.onDidChangeControl(() => this._bindToEditor(editor)));
69
}
70
this._onDidUpdate.fire(this);
71
}
72
73
dispose(): void {
74
this._disposables.dispose();
75
this._cfgFilePath.dispose();
76
this._cfgSymbolPath.dispose();
77
this._currentOutline.dispose();
78
this._outlineDisposables.dispose();
79
this._onDidUpdate.dispose();
80
}
81
82
isRelative(): boolean {
83
return Boolean(this._fileInfo.folder);
84
}
85
86
getElements(): ReadonlyArray<FileElement | OutlineElement2> {
87
let result: (FileElement | OutlineElement2)[] = [];
88
89
// file path elements
90
if (this._cfgFilePath.getValue() === 'on') {
91
result = result.concat(this._fileInfo.path);
92
} else if (this._cfgFilePath.getValue() === 'last' && this._fileInfo.path.length > 0) {
93
result = result.concat(this._fileInfo.path.slice(-1));
94
}
95
96
if (this._cfgSymbolPath.getValue() === 'off') {
97
return result;
98
}
99
100
if (!this._currentOutline.value) {
101
return result;
102
}
103
104
const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements();
105
for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {
106
result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value));
107
}
108
109
if (breadcrumbsElements.length === 0 && !this._currentOutline.value.isEmpty) {
110
result.push(new OutlineElement2(this._currentOutline.value, this._currentOutline.value));
111
}
112
113
return result;
114
}
115
116
private _initFilePathInfo(uri: URI): FileInfo {
117
118
if (matchesSomeScheme(uri, Schemas.untitled, Schemas.data)) {
119
return {
120
folder: undefined,
121
path: []
122
};
123
}
124
125
const info: FileInfo = {
126
folder: this._workspaceService.getWorkspaceFolder(uri) ?? undefined,
127
path: []
128
};
129
130
let uriPrefix: URI | null = uri;
131
while (uriPrefix && uriPrefix.path !== '/') {
132
if (info.folder && isEqual(info.folder.uri, uriPrefix)) {
133
break;
134
}
135
info.path.unshift(new FileElement(uriPrefix, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
136
const prevPathLength = uriPrefix.path.length;
137
uriPrefix = dirname(uriPrefix);
138
if (uriPrefix.path.length === prevPathLength) {
139
break;
140
}
141
}
142
143
if (info.folder && this._workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
144
info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
145
}
146
return info;
147
}
148
149
private _onDidChangeWorkspaceFolders() {
150
this._fileInfo = this._initFilePathInfo(this.resource);
151
this._onDidUpdate.fire(this);
152
}
153
154
private _bindToEditor(editor: IEditorPane): void {
155
const newCts = new CancellationTokenSource();
156
this._currentOutline.clear();
157
this._outlineDisposables.clear();
158
this._outlineDisposables.add(toDisposable(() => newCts.dispose(true)));
159
160
this._outlineService.createOutline(editor, OutlineTarget.Breadcrumbs, newCts.token).then(outline => {
161
if (newCts.token.isCancellationRequested) {
162
// cancelled: dispose new outline and reset
163
outline?.dispose();
164
outline = undefined;
165
}
166
this._currentOutline.value = outline;
167
this._onDidUpdate.fire(this);
168
if (outline) {
169
this._outlineDisposables.add(outline.onDidChange(() => this._onDidUpdate.fire(this)));
170
}
171
172
}).catch(err => {
173
this._onDidUpdate.fire(this);
174
onUnexpectedError(err);
175
});
176
}
177
}
178
179