Path: blob/main/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationTokenSource } from '../../../../base/common/cancellation.js';6import { onUnexpectedError } from '../../../../base/common/errors.js';7import { Emitter, Event } from '../../../../base/common/event.js';8import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';9import { Schemas, matchesSomeScheme } from '../../../../base/common/network.js';10import { dirname, isEqual } from '../../../../base/common/resources.js';11import { URI } from '../../../../base/common/uri.js';12import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';13import { FileKind } from '../../../../platform/files/common/files.js';14import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from '../../../../platform/workspace/common/workspace.js';15import { BreadcrumbsConfig } from './breadcrumbs.js';16import { IEditorPane } from '../../../common/editor.js';17import { IOutline, IOutlineService, OutlineTarget } from '../../../services/outline/browser/outline.js';1819export class FileElement {20constructor(21readonly uri: URI,22readonly kind: FileKind23) { }24}2526type FileInfo = { path: FileElement[]; folder?: IWorkspaceFolder };2728export class OutlineElement2 {29constructor(30readonly element: IOutline<any> | any,31readonly outline: IOutline<any>32) { }33}3435export class BreadcrumbsModel {3637private readonly _disposables = new DisposableStore();38private _fileInfo: FileInfo;3940private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;41private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;4243private readonly _currentOutline = new MutableDisposable<IOutline<any>>();44private readonly _outlineDisposables = new DisposableStore();4546private readonly _onDidUpdate = new Emitter<this>();47readonly onDidUpdate: Event<this> = this._onDidUpdate.event;4849constructor(50readonly resource: URI,51readonly editor: IEditorPane | undefined,52@IConfigurationService configurationService: IConfigurationService,53@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,54@IOutlineService private readonly _outlineService: IOutlineService,55) {56this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);57this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);5859this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));60this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));61this._workspaceService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspaceFolders, this, this._disposables);62this._fileInfo = this._initFilePathInfo(resource);6364if (editor) {65this._bindToEditor(editor);66this._disposables.add(_outlineService.onDidChange(() => this._bindToEditor(editor)));67this._disposables.add(editor.onDidChangeControl(() => this._bindToEditor(editor)));68}69this._onDidUpdate.fire(this);70}7172dispose(): void {73this._disposables.dispose();74this._cfgFilePath.dispose();75this._cfgSymbolPath.dispose();76this._currentOutline.dispose();77this._outlineDisposables.dispose();78this._onDidUpdate.dispose();79}8081isRelative(): boolean {82return Boolean(this._fileInfo.folder);83}8485getElements(): ReadonlyArray<FileElement | OutlineElement2> {86let result: (FileElement | OutlineElement2)[] = [];8788// file path elements89if (this._cfgFilePath.getValue() === 'on') {90result = result.concat(this._fileInfo.path);91} else if (this._cfgFilePath.getValue() === 'last' && this._fileInfo.path.length > 0) {92result = result.concat(this._fileInfo.path.slice(-1));93}9495if (this._cfgSymbolPath.getValue() === 'off') {96return result;97}9899if (!this._currentOutline.value) {100return result;101}102103const breadcrumbsElements = this._currentOutline.value.config.breadcrumbsDataSource.getBreadcrumbElements();104for (let i = this._cfgSymbolPath.getValue() === 'last' && breadcrumbsElements.length > 0 ? breadcrumbsElements.length - 1 : 0; i < breadcrumbsElements.length; i++) {105result.push(new OutlineElement2(breadcrumbsElements[i], this._currentOutline.value));106}107108if (breadcrumbsElements.length === 0 && !this._currentOutline.value.isEmpty) {109result.push(new OutlineElement2(this._currentOutline.value, this._currentOutline.value));110}111112return result;113}114115private _initFilePathInfo(uri: URI): FileInfo {116117if (matchesSomeScheme(uri, Schemas.untitled, Schemas.data)) {118return {119folder: undefined,120path: []121};122}123124const info: FileInfo = {125folder: this._workspaceService.getWorkspaceFolder(uri) ?? undefined,126path: []127};128129let uriPrefix: URI | null = uri;130while (uriPrefix && uriPrefix.path !== '/') {131if (info.folder && isEqual(info.folder.uri, uriPrefix)) {132break;133}134info.path.unshift(new FileElement(uriPrefix, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));135const prevPathLength = uriPrefix.path.length;136uriPrefix = dirname(uriPrefix);137if (uriPrefix.path.length === prevPathLength) {138break;139}140}141142if (info.folder && this._workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {143info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));144}145return info;146}147148private _onDidChangeWorkspaceFolders() {149this._fileInfo = this._initFilePathInfo(this.resource);150this._onDidUpdate.fire(this);151}152153private _bindToEditor(editor: IEditorPane): void {154const newCts = new CancellationTokenSource();155this._currentOutline.clear();156this._outlineDisposables.clear();157this._outlineDisposables.add(toDisposable(() => newCts.dispose(true)));158159this._outlineService.createOutline(editor, OutlineTarget.Breadcrumbs, newCts.token).then(outline => {160if (newCts.token.isCancellationRequested) {161// cancelled: dispose new outline and reset162outline?.dispose();163outline = undefined;164}165this._currentOutline.value = outline;166this._onDidUpdate.fire(this);167if (outline) {168this._outlineDisposables.add(outline.onDidChange(() => this._onDidUpdate.fire(this)));169}170171}).catch(err => {172this._onDidUpdate.fire(this);173onUnexpectedError(err);174});175}176}177178179