提交 88c271c3 编写于 作者: J Johannes Rieken

remove ITableOfContentsProvider, remove duplicated code, add quick picks to IOutline

上级 4d47c40d
......@@ -445,4 +445,38 @@ export class OutlineModel extends TreeElement {
group.updateMarker(marker.slice(0));
}
}
asListOfDocumentSymbols(): DocumentSymbol[] {
const roots: DocumentSymbol[] = [];
for (const child of this.children.values()) {
if (child instanceof OutlineElement) {
roots.push(child.symbol);
} else {
roots.push(...Iterable.map(child.children.values(), child => child.symbol));
}
}
const bucket: DocumentSymbol[] = [];
OutlineModel._flattenDocumentSymbols(bucket, roots, '');
return bucket;
}
private static _flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void {
for (const entry of entries) {
bucket.push({
kind: entry.kind,
tags: entry.tags,
name: entry.name,
detail: entry.detail,
containerName: entry.containerName || overrideContainerLabel,
range: entry.range,
selectionRange: entry.selectionRange,
children: undefined, // we flatten it...
});
// Recurse over children
if (entry.children) {
OutlineModel._flattenDocumentSymbols(bucket, entry.children, entry.name);
}
}
}
}
......@@ -4,62 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { DocumentSymbol } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { assertType } from 'vs/base/common/types';
import { Iterable } from 'vs/base/common/iterator';
export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
const model = await OutlineModel.create(document, token);
const roots: DocumentSymbol[] = [];
for (const child of model.children.values()) {
if (child instanceof OutlineElement) {
roots.push(child.symbol);
} else {
roots.push(...Iterable.map(child.children.values(), child => child.symbol));
}
}
let flatEntries: DocumentSymbol[] = [];
if (token.isCancellationRequested) {
return flatEntries;
}
if (flat) {
flatten(flatEntries, roots, '');
} else {
flatEntries = roots;
}
return flatEntries.sort(compareEntriesUsingStart);
}
function compareEntriesUsingStart(a: DocumentSymbol, b: DocumentSymbol): number {
return Range.compareRangesUsingStarts(a.range, b.range);
}
function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void {
for (let entry of entries) {
bucket.push({
kind: entry.kind,
tags: entry.tags,
name: entry.name,
detail: entry.detail,
containerName: entry.containerName || overrideContainerLabel,
range: entry.range,
selectionRange: entry.selectionRange,
children: undefined, // we flatten it...
});
if (entry.children) {
flatten(bucket, entry.children, entry.name);
}
}
return model.asListOfDocumentSymbols();
}
CommandsRegistry.registerCommand('_executeDocumentSymbolProvider', async function (accessor, ...args) {
......
......@@ -12,11 +12,10 @@ import { ITextModel } from 'vs/editor/common/model';
import { IRange, Range } from 'vs/editor/common/core/range';
import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions, IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes';
import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { OutlineModel } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { trim, format } from 'vs/base/common/strings';
import { prepareQuery, IPreparedQuery, pieceToQuery, scoreFuzzy2 } from 'vs/base/common/fuzzyScorer';
import { IMatch } from 'vs/base/common/filters';
import { Iterable } from 'vs/base/common/iterator';
import { Codicon } from 'vs/base/common/codicons';
export interface IGotoSymbolQuickPickItem extends IQuickPickItem {
......@@ -144,7 +143,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
// Resolve symbols from document once and reuse this
// request for all filtering and typing then on
const symbolsPromise = this.getDocumentSymbols(model, true, token);
const symbolsPromise = this.getDocumentSymbols(model, token);
// Set initial picks and update on type
let picksCts: CancellationTokenSource | undefined = undefined;
......@@ -418,49 +417,9 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
return result;
}
protected async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
protected async getDocumentSymbols(document: ITextModel, token: CancellationToken): Promise<DocumentSymbol[]> {
const model = await OutlineModel.create(document, token);
if (token.isCancellationRequested) {
return [];
}
const roots: DocumentSymbol[] = [];
for (const child of model.children.values()) {
if (child instanceof OutlineElement) {
roots.push(child.symbol);
} else {
roots.push(...Iterable.map(child.children.values(), child => child.symbol));
}
}
let flatEntries: DocumentSymbol[] = [];
if (flatten) {
this.flattenDocumentSymbols(flatEntries, roots, '');
} else {
flatEntries = roots;
}
return flatEntries.sort((symbolA, symbolB) => Range.compareRangesUsingStarts(symbolA.range, symbolB.range));
}
private flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void {
for (const entry of entries) {
bucket.push({
kind: entry.kind,
tags: entry.tags,
name: entry.name,
detail: entry.detail,
containerName: entry.containerName || overrideContainerLabel,
range: entry.range,
selectionRange: entry.selectionRange,
children: undefined, // we flatten it...
});
// Recurse over children
if (entry.children) {
this.flattenDocumentSymbols(bucket, entry.children, entry.name);
}
}
return token.isCancellationRequested ? [] : model.asListOfDocumentSymbols();
}
}
......
......@@ -105,7 +105,7 @@ export class BreadcrumbsModel {
const { activeEntry } = this._currentOutline;
if (activeEntry) {
for (let element of this._currentOutline.treeConfig.parentChainProvider.getBreadcrumbElements(activeEntry)) {
for (let element of this._currentOutline.treeConfig.breadcrumbsDataSource.getBreadcrumbElements(activeEntry)) {
result.push(new OutlineElement2(element, this._currentOutline));
}
} else if (!this._currentOutline.isEmpty) {
......
......@@ -60,6 +60,9 @@ class DocumentSymbolsOutline implements IOutline<DocumentSymbolItem> {
{
getBreadcrumbElements: () => <DocumentSymbolItem[]>this._outlineElementChain.filter(element => !(element instanceof OutlineModel))
},
{
getQuickPickElements: () => { throw new Error('not implemented'); }
},
{
getChildren: (parent) => {
if (parent instanceof OutlineElement || parent instanceof OutlineGroup) {
......
......@@ -12,9 +12,9 @@ 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, IEditorPane } from 'vs/workbench/common/editor';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { ITextModel } from 'vs/editor/common/model';
import { DisposableStore, IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, toDisposable, Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { timeout } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
......@@ -23,10 +23,11 @@ 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';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickAccessTextEditorContext } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
import { IOutlineService } from 'vs/workbench/services/outline/browser/outline';
import { isCompositeEditor } from 'vs/editor/browser/editorBrowser';
export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
......@@ -34,7 +35,8 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
constructor(
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IOutlineService private readonly outlineService: IOutlineService,
) {
super({
openSideBySideDirection: () => this.configuration.openSideBySideDirection
......@@ -43,14 +45,6 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
//#region DocumentSymbols (text editor required)
protected provideWithTextEditor(context: IQuickAccessTextEditorContext, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
if (this.canPickFromTableOfContents()) {
return this.doGetTableOfContentsPicks(picker);
}
return super.provideWithTextEditor(context, picker, token);
}
private get configuration() {
const editorConfig = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench?.editor;
......@@ -61,6 +55,10 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
}
protected get activeTextEditorControl() {
if (isCompositeEditor(this.editorService.activeEditorPane?.getControl())) {
// TODO@bpasero adopt IOutlineService for "normal" document symbols.
return undefined;
}
return this.editorService.activeTextEditorControl;
}
......@@ -104,7 +102,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
return [];
}
return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), prepareQuery(filter), options, token);
return this.doGetSymbolPicks(this.getDocumentSymbols(model, token), prepareQuery(filter), options, token);
}
addDecorations(editor: IEditor, range: IRange): void {
......@@ -118,22 +116,21 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
//#endregion
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
if (this.canPickFromTableOfContents()) {
return this.doGetTableOfContentsPicks(picker);
if (this.canPickWithOutlineService()) {
return this.doGetOutlinePicks(picker);
}
return super.provideWithoutTextEditor(picker);
}
private canPickFromTableOfContents(): boolean {
return this.editorService.activeEditorPane ? TableOfContentsProviderRegistry.has(this.editorService.activeEditorPane.getId()) : false;
private canPickWithOutlineService(): boolean {
return this.editorService.activeEditorPane ? this.outlineService.canCreateOutline(this.editorService.activeEditorPane) : false;
}
private doGetTableOfContentsPicks(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
private doGetOutlinePicks(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
const pane = this.editorService.activeEditorPane;
if (!pane) {
return Disposable.None;
}
const provider = TableOfContentsProviderRegistry.get(pane.getId())!;
const cts = new CancellationTokenSource();
const disposables = new DisposableStore();
......@@ -141,30 +138,37 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
picker.busy = true;
provider.provideTableOfContents(pane, { disposables }, cts.token).then(entries => {
this.outlineService.createOutline(pane, cts.token).then(outline => {
picker.busy = false;
if (cts.token.isCancellationRequested || !entries || entries.length === 0) {
if (!outline) {
return;
}
if (cts.token.isCancellationRequested) {
outline.dispose();
return;
}
disposables.add(outline);
const entries = Array.from(outline.treeConfig.quickPickDataSource.getQuickPickElements());
const items: IGotoSymbolQuickPickItem[] = entries.map((entry, idx) => {
return {
kind: SymbolKind.File,
index: idx,
score: 0,
label: entry.icon ? `$(${entry.icon.id}) ${entry.label}` : entry.label,
ariaLabel: entry.detail ? `${entry.label}, ${entry.detail}` : entry.label,
detail: entry.detail,
label: entry.label,
description: entry.description,
ariaLabel: entry.ariaLabel,
};
});
disposables.add(picker.onDidAccept(() => {
picker.hide();
const [entry] = picker.selectedItems;
entries[entry.index]?.pick();
if (entry && entries[entry.index]) {
outline.revealInEditor(entries[entry.index].element, {}, false);
}
}));
const updatePickerItems = () => {
......@@ -194,16 +198,23 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
updatePickerItems();
disposables.add(picker.onDidChangeValue(updatePickerItems));
const previewDisposable = new MutableDisposable();
disposables.add(previewDisposable);
disposables.add(picker.onDidChangeActive(() => {
const [entry] = picker.activeItems;
if (entry) {
entries[entry.index]?.preview();
if (entry && entries[entry.index]) {
previewDisposable.value = outline.previewInEditor(entries[entry.index].element);
} else {
previewDisposable.clear();
}
}));
}).catch(err => {
onUnexpectedError(err);
picker.hide();
}).finally(() => {
picker.busy = false;
});
return disposables;
......@@ -243,45 +254,3 @@ registerAction2(class GotoSymbolAction extends Action2 {
accessor.get(IQuickInputService).quickAccess.show(GotoSymbolQuickAccessProvider.PREFIX);
}
});
//#region toc definition and logic
export interface ITableOfContentsEntry {
icon?: ThemeIcon;
label: string;
detail?: string;
description?: string;
pick(): any;
preview(): any;
}
export interface ITableOfContentsProvider<T extends IEditorPane = IEditorPane> {
provideTableOfContents(editor: T, context: { disposables: DisposableStore }, token: CancellationToken): Promise<ITableOfContentsEntry[] | undefined | null>;
}
class ProviderRegistry {
private readonly _provider = new Map<string, ITableOfContentsProvider>();
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
......@@ -133,6 +133,7 @@ class NotebookCellOutline implements IOutline<OutlineEntry> {
this.treeConfig = new OutlineTreeConfiguration<OutlineEntry>(
{ getBreadcrumbElements: (element) => Iterable.single(element) },
{ getQuickPickElements: () => this._entries.map(entry => ({ element: entry, label: `$(${entry.icon.id}) ${entry.label}`, ariaLabel: entry.label })) },
{ getChildren: parent => parent === this ? this._entries : [] },
new NotebookOutlineVirtualDelegate(),
[new NotebookOutlineRenderer()],
......
......@@ -9,6 +9,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { IDisposable } from 'vs/base/common/lifecycle';
import { SymbolKind } from 'vs/editor/common/modes';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchDataTreeOptions } from 'vs/platform/list/browser/listService';
......@@ -19,6 +20,7 @@ export const IOutlineService = createDecorator<IOutlineService>('IOutlineService
export interface IOutlineService {
_serviceBrand: undefined;
onDidChange: Event<void>;
canCreateOutline(editor: IEditorPane): boolean;
createOutline(editor: IEditorPane, token: CancellationToken): Promise<IOutline<any> | undefined>;
registerOutlineCreator(creator: IOutlineCreator<any, any>): IDisposable;
}
......@@ -28,13 +30,18 @@ export interface IOutlineCreator<P extends IEditorPane, E> {
createOutline(editor: P, token: CancellationToken): Promise<IOutline<E> | undefined>;
}
export interface IParentChainProvider<E> {
export interface IBreadcrumbsDataSource<E> {
getBreadcrumbElements(element: E): Iterable<E>;
}
export interface IQuickPickDataSource<E> {
getQuickPickElements(): Iterable<{ element: E, kind?: SymbolKind, label: string, ariaLabel?: string, description?: string }>;
}
export class OutlineTreeConfiguration<E> {
constructor(
readonly parentChainProvider: IParentChainProvider<E>,
readonly breadcrumbsDataSource: IBreadcrumbsDataSource<E>,
readonly quickPickDataSource: IQuickPickDataSource<E>,
readonly treeDataSource: IDataSource<IOutline<E>, E>,
readonly delegate: IListVirtualDelegate<E>,
readonly renderers: ITreeRenderer<E, FuzzyScore, any>[],
......
......@@ -20,6 +20,15 @@ class OutlineService implements IOutlineService {
private readonly _onDidChange = new Emitter<void>();
readonly onDidChange: Event<void> = this._onDidChange.event;
canCreateOutline(pane: IEditorPane): boolean {
for (let factory of this._factories) {
if (factory.matches(pane)) {
return true;
}
}
return false;
}
async createOutline(pane: IEditorPane, token: CancellationToken): Promise<IOutline<any> | undefined> {
for (let factory of this._factories) {
if (factory.matches(pane)) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册