From 074f6e0f545fe466d92b5ce4c076633bc38391e4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 27 Apr 2020 14:19:56 +0200 Subject: [PATCH] add TableOfContentsProvider for none text editors, #95234 --- .../quickaccess/gotoSymbolQuickAccess.ts | 129 +++++++++++++++++- .../browser/contrib/toc/tocProvider.ts | 35 +++++ .../notebook/browser/notebook.contribution.ts | 1 + 3 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index ea39adf00d6..0d1a608e249 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IKeyMods, IQuickPickSeparator, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPickSeparator, IQuickInputService, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; @@ -12,16 +12,19 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions as QuickaccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { IWorkbenchEditorConfiguration, IEditorPane } from 'vs/workbench/common/editor'; import { ITextModel } from 'vs/editor/common/model'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { prepareQuery } from 'vs/base/common/fuzzyScorer'; +import { SymbolKind } from 'vs/editor/common/modes'; +import { fuzzyScore, createMatches } from 'vs/base/common/filters'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -36,6 +39,8 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess }); } + //#region DocumentSymbols (text editor required) + private get configuration() { const editorConfig = this.configurationService.getValue().workbench.editor; @@ -66,6 +71,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess } } + //#endregion //#region public methods to use this picker from other pickers @@ -98,6 +104,81 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess } //#endregion + + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + const pane = this.editorService.activeEditorPane; + if (!pane || !TableOfContentsProviderRegistry.has(pane.getId())) { + // + return super.provideWithoutTextEditor(picker); + } + + const provider = TableOfContentsProviderRegistry.get(pane.getId())!; + const cts = new CancellationTokenSource(); + + const disposables = new DisposableStore(); + disposables.add(toDisposable(() => cts.dispose(true))); + + picker.busy = true; + + provider.provideTableOfContents(pane, cts.token).then(entries => { + + picker.busy = false; + + if (cts.token.isCancellationRequested || !entries || entries.length === 0) { + return; + } + + const items: IGotoSymbolQuickPickItem[] = entries.map((entry, idx) => { + return { + kind: SymbolKind.File, + index: idx, + score: 0, + label: entry.label, + detail: entry.detail, + description: entry.description, + }; + }); + + disposables.add(picker.onDidAccept(() => { + picker.hide(); + const [entry] = picker.selectedItems; + entries[entry.index]?.reveal(); + })); + + const updatePickerItems = () => { + const filteredItems = items.filter(item => { + if (picker.value === '@') { + // default, no filtering, scoring... + item.score = 0; + item.highlights = undefined; + return true; + } + const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, true); + if (!score) { + return false; + } + item.score = score[1]; + item.highlights = { label: createMatches(score) }; + return true; + }); + if (filteredItems.length === 0) { + const label = localize('empty', 'No matching entries'); + picker.items = [{ label, index: -1, kind: SymbolKind.String }]; + picker.ariaLabel = label; + } else { + picker.items = filteredItems; + } + }; + updatePickerItems(); + disposables.add(picker.onDidChangeValue(updatePickerItems)); + + }).catch(err => { + onUnexpectedError(err); + picker.hide(); + }); + + return disposables; + } } Registry.as(QuickaccessExtensions.Quickaccess).registerQuickAccessProvider({ @@ -132,3 +213,43 @@ export class GotoSymbolAction extends Action { Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.from(GotoSymbolAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O }), 'Go to Symbol in Editor...'); + + +//#region toc definition and logic + +export interface ITableOfContentsEntry { + label: string; + detail?: string; + description?: string; + reveal(): any; +} + +export interface ITableOfContentsProvider { + provideTableOfContents(editor: T, token: CancellationToken): Promise; +} + +class ProviderRegistry { + + private readonly _provider = new Map(); + + register(type: string, provider: ITableOfContentsProvider): IDisposable { + this._provider.set(type, provider); + return toDisposable(() => { + if (this._provider.get(type) === provider) { + this._provider.delete(type); + } + }); + } + + get(type: string): ITableOfContentsProvider | undefined { + return this._provider.get(type); + } + + has(type: string): boolean { + return this._provider.has(type); + } +} + +export const TableOfContentsProviderRegistry = new ProviderRegistry(); + +//#endregion diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts new file mode 100644 index 00000000000..838ffdd7926 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TableOfContentsProviderRegistry, ITableOfContentsProvider, ITableOfContentsEntry } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + + +TableOfContentsProviderRegistry.register(NotebookEditor.ID, new class implements ITableOfContentsProvider { + async provideTableOfContents(editor: NotebookEditor) { + if (!editor.viewModel) { + return undefined; + } + // return an entry per markdown header + const result: ITableOfContentsEntry[] = []; + for (let cell of editor.viewModel.viewCells) { + if (cell.cellKind === CellKind.Code) { + continue; + } + const content = cell.getText(); + const matches = content.match(/^[ \t]*(\#+)(.+)$/gm); + if (matches && matches.length) { + for (let j = 0; j < matches.length; j++) { + result.push({ + label: matches[j].replace(/^[ \t]*(\#+)/, ''), + reveal: () => editor.revealInCenterIfOutsideViewport(cell) + }); + } + } + } + return result; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index cedc85fef1f..96fa760072e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -42,6 +42,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import 'vs/workbench/contrib/notebook/browser/contrib/find/findController'; import 'vs/workbench/contrib/notebook/browser/contrib/fold/folding'; import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; +import 'vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider'; // Output renderers registration -- GitLab