提交 eac4cc22 编写于 作者: B Benjamin Pasero

quick access - introduce and reuse editor navigation base type

上级 798481c7
......@@ -4,15 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { IEditor } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration, OverviewRulerLane } from 'vs/editor/common/model';
import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon';
import { IModelDeltaDecoration, OverviewRulerLane, ITextModel } from 'vs/editor/common/model';
import { IRange } from 'vs/editor/common/core/range';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry';
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { withNullAsUndefined } from 'vs/base/common/types';
import { once } from 'vs/base/common/functional';
interface IEditorLineDecoration {
rangeHighlightId: string;
......@@ -20,24 +23,109 @@ interface IEditorLineDecoration {
}
/**
* A reusable quick access provider for the editor with support for adding decorations.
* A reusable quick access provider for the editor with support
* for adding decorations for navigating in the currently active file
* (for example "Go to line", "Go to symbol").
*/
export abstract class AbstractEditorQuickAccessProvider implements IQuickAccessProvider {
export abstract class AbstractEditorNavigationQuickAccessProvider<T extends IQuickPickItem> implements IQuickAccessProvider {
//#region Provider methods
provide(picker: IQuickPick<T>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Provide based on current active editor
let pickerDisposable = this.doProvide(picker, token);
disposables.add(toDisposable(() => pickerDisposable.dispose()));
// Re-create whenever the active editor changes
disposables.add(this.onDidActiveTextEditorControlChange(() => {
pickerDisposable.dispose();
pickerDisposable = this.doProvide(picker, token);
}));
return disposables;
}
private doProvide(picker: IQuickPick<T>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// With text control
const editor = this.activeTextEditorControl;
if (editor && this.canProvideWithTextEditor(editor)) {
// Restore any view state if this picker was closed
// without actually going to a line
const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState());
once(token.onCancellationRequested)(() => {
if (lastKnownEditorViewState) {
editor.restoreViewState(lastKnownEditorViewState);
}
});
// Clean up decorations on dispose
disposables.add(toDisposable(() => this.clearDecorations(editor)));
// Ask subclass for entries
disposables.add(this.provideWithTextEditor(editor, picker, token));
}
// Without text control
else {
disposables.add(this.provideWithoutTextEditor(picker, token));
}
return disposables;
}
/**
* Subclasses to provide an event when the active editor control changes.
* Subclasses to implement if they can operate on the text editor.
*/
abstract readonly onDidActiveTextEditorControlChange: Event<void>;
protected canProvideWithTextEditor(editor: IEditor): boolean {
return true;
}
/**
* Subclasses to provide the current active editor control.
* Subclasses to implement to provide picks for the picker when an editor is active.
*/
protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick<T>, token: CancellationToken): IDisposable;
/**
* Subclasses to implement to provide picks for the picker when no editor is active.
*/
protected abstract provideWithoutTextEditor(picker: IQuickPick<T>, token: CancellationToken): IDisposable;
protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods): void {
editor.setSelection(range);
editor.revealRangeInCenter(range, ScrollType.Smooth);
editor.focus();
}
protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {
return isDiffEditor(editor) ?
editor.getModel()?.modified :
editor.getModel() as ITextModel;
}
//#endregion
//#region Editor access
/**
* Subclasses to provide an event when the active editor control changes.
*/
abstract activeTextEditorControl: IEditor | undefined;
protected abstract readonly onDidActiveTextEditorControlChange: Event<void>;
/**
* Subclasses to implement the quick access picker.
* Subclasses to provide the current active editor control.
*/
abstract provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
protected abstract activeTextEditorControl: IEditor | undefined;
//#endregion
//#region Decorations Utils
......
......@@ -4,55 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/functional';
import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IEditor, ScrollType } from 'vs/editor/common/editorCommon';
import { IRange } from 'vs/editor/common/core/range';
import { withNullAsUndefined } from 'vs/base/common/types';
import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess';
import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
import { IPosition } from 'vs/editor/common/core/position';
interface IGotoLineQuickPickItem extends IQuickPickItem, Partial<IPosition> { }
export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorQuickAccessProvider {
export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider<IGotoLineQuickPickItem> {
static PREFIX = ':';
provide(picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Provide based on current active editor
let pickerDisposable = this.doProvide(picker, token);
disposables.add(toDisposable(() => pickerDisposable.dispose()));
// Re-create whenever the active editor changes
disposables.add(this.onDidActiveTextEditorControlChange(() => {
pickerDisposable.dispose();
pickerDisposable = this.doProvide(picker, token);
}));
return disposables;
}
private doProvide(picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
// With text control
if (this.activeTextEditorControl) {
return this.doProvideWithTextEditor(this.activeTextEditorControl, picker, token);
}
// Without text control
return this.doProvideWithoutTextEditor(picker);
}
private doProvideWithoutTextEditor(picker: IQuickPick<IGotoLineQuickPickItem>): IDisposable {
protected provideWithoutTextEditor(picker: IQuickPick<IGotoLineQuickPickItem>): IDisposable {
const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line.");
picker.items = [{ label }];
picker.ariaLabel = label;
......@@ -60,18 +26,9 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
return Disposable.None;
}
private doProvideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
protected provideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Restore any view state if this picker was closed
// without actually going to a line
const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState());
once(token.onCancellationRequested)(() => {
if (lastKnownEditorViewState) {
editor.restoreViewState(lastKnownEditorViewState);
}
});
// Goto line once picked
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
......@@ -80,7 +37,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
return;
}
this.gotoLine(editor, this.toRange(item.lineNumber, item.column), picker.keyMods);
this.gotoLocation(editor, this.toRange(item.lineNumber, item.column), picker.keyMods);
picker.hide();
}
......@@ -117,9 +74,6 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
updatePickerAndEditor();
disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor()));
// Clean up decorations on dispose
disposables.add(toDisposable(() => this.clearDecorations(editor)));
return disposables;
}
......@@ -191,16 +145,4 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
private lineCount(editor: IEditor): number {
return this.getModel(editor)?.getLineCount() ?? 0;
}
private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {
return isDiffEditor(editor) ?
editor.getModel()?.modified :
editor.getModel() as ITextModel;
}
protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void {
editor.setSelection(range);
editor.revealRangeInCenter(range, ScrollType.Smooth);
editor.focus();
}
}
......@@ -4,16 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickItem, IKeyMods, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/functional';
import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon';
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { IEditor, ScrollType } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { isDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IRange, Range } from 'vs/editor/common/core/range';
import { withNullAsUndefined } from 'vs/base/common/types';
import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess';
import { AbstractEditorNavigationQuickAccessProvider } 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 { values } from 'vs/base/common/collections';
......@@ -27,47 +24,19 @@ interface IGotoSymbolQuickPickItem extends IQuickPickItem {
range?: { decoration: IRange, selection: IRange },
}
export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorQuickAccessProvider {
export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider<IGotoSymbolQuickPickItem> {
static PREFIX = '@';
static SCOPE_PREFIX = ':';
static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`;
provide(picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Provide based on current active editor
let pickerDisposable = this.doProvide(picker, token);
disposables.add(toDisposable(() => pickerDisposable.dispose()));
protected canProvideWithTextEditor(editor: IEditor): boolean {
const model = this.getModel(editor);
// Re-create whenever the active editor changes
disposables.add(this.onDidActiveTextEditorControlChange(() => {
pickerDisposable.dispose();
pickerDisposable = this.doProvide(picker, token);
}));
return disposables;
return !!model && DocumentSymbolProviderRegistry.has(model);
}
private doProvide(picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
const activeTextEditorControl = this.activeTextEditorControl;
// With text control
if (activeTextEditorControl) {
const model = this.getModel(activeTextEditorControl);
if (model && DocumentSymbolProviderRegistry.has(model)) {
return this.doProvideWithSymbols(activeTextEditorControl, model, picker, token);
}
}
// Without text control
return this.doProvideWithoutSymbols(picker);
}
private doProvideWithoutSymbols(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
const label = localize('cannotRunGotoSymbol', "Open a text editor with symbol information first to go to a symbol.");
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
......@@ -75,23 +44,19 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
return Disposable.None;
}
private doProvideWithSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
protected provideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
const model = this.getModel(editor);
if (!model) {
return Disposable.None;
}
// Restore any view state if this picker was closed
// without actually going to a symbol
const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState());
once(token.onCancellationRequested)(() => {
if (lastKnownEditorViewState) {
editor.restoreViewState(lastKnownEditorViewState);
}
});
const disposables = new DisposableStore();
// Goto symbol once picked
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item && item.range) {
this.gotoSymbol(editor, item.range.selection, picker.keyMods);
this.gotoLocation(editor, item.range.selection, picker.keyMods);
picker.hide();
}
......@@ -139,9 +104,6 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
}));
// Clean up decorations on dispose
disposables.add(toDisposable(() => this.clearDecorations(editor)));
return disposables;
}
......@@ -342,18 +304,6 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
}
}
private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {
return isDiffEditor(editor) ?
editor.getModel()?.modified :
editor.getModel() as ITextModel;
}
protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void {
editor.setSelection(range);
editor.revealRangeInCenter(range, ScrollType.Smooth);
editor.focus();
}
}
// #region NLS Helpers
......
......@@ -13,13 +13,13 @@ import { Event } from 'vs/base/common/event';
export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider {
readonly onDidActiveTextEditorControlChange = Event.None;
protected readonly onDidActiveTextEditorControlChange = Event.None;
constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) {
super();
}
get activeTextEditorControl() {
protected get activeTextEditorControl() {
return withNullAsUndefined(this.editorService.getFocusedCodeEditor());
}
}
......
......@@ -13,13 +13,13 @@ import { Event } from 'vs/base/common/event';
export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
readonly onDidActiveTextEditorControlChange = Event.None;
protected readonly onDidActiveTextEditorControlChange = Event.None;
constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) {
super();
}
get activeTextEditorControl() {
protected get activeTextEditorControl() {
return withNullAsUndefined(this.editorService.getFocusedCodeEditor());
}
}
......
......@@ -14,13 +14,13 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/
export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider {
readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange;
protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange;
constructor(@IEditorService private readonly editorService: IEditorService) {
super();
}
get activeTextEditorControl() {
protected get activeTextEditorControl() {
return this.editorService.activeTextEditorControl;
}
......@@ -33,7 +33,7 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv
// Otherwise let parent handle it
else {
super.gotoLine(editor, range, keyMods);
super.gotoLocation(editor, range, keyMods);
}
}
}
......
......@@ -14,13 +14,13 @@ import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAc
export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange;
protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange;
constructor(@IEditorService private readonly editorService: IEditorService) {
super();
}
get activeTextEditorControl() {
protected get activeTextEditorControl() {
return this.editorService.activeTextEditorControl;
}
......@@ -33,7 +33,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
// Otherwise let parent handle it
else {
super.gotoSymbol(editor, range, keyMods);
super.gotoLocation(editor, range, keyMods);
}
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册