mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 099a7622e6e90dbcc226e428d4e35a72cb19ecbc (#9646)
* Merge from vscode 099a7622e6e90dbcc226e428d4e35a72cb19ecbc * fix strict
This commit is contained in:
@@ -1754,6 +1754,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
if (ranges.length > 0) {
|
||||
this._emitModelTokensChangedEvent({
|
||||
tokenizationSupportChanged: false,
|
||||
semanticTokensApplied: false,
|
||||
ranges: ranges
|
||||
});
|
||||
}
|
||||
@@ -1764,6 +1765,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
this._emitModelTokensChangedEvent({
|
||||
tokenizationSupportChanged: false,
|
||||
semanticTokensApplied: tokens !== null,
|
||||
ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }]
|
||||
});
|
||||
}
|
||||
@@ -1778,6 +1780,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._tokens.flush();
|
||||
this._emitModelTokensChangedEvent({
|
||||
tokenizationSupportChanged: true,
|
||||
semanticTokensApplied: false,
|
||||
ranges: [{
|
||||
fromLineNumber: 1,
|
||||
toLineNumber: this._buffer.getLineCount()
|
||||
@@ -1790,6 +1793,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
this._emitModelTokensChangedEvent({
|
||||
tokenizationSupportChanged: false,
|
||||
semanticTokensApplied: false,
|
||||
ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ export interface IModelDecorationsChangedEvent {
|
||||
*/
|
||||
export interface IModelTokensChangedEvent {
|
||||
readonly tokenizationSupportChanged: boolean;
|
||||
readonly semanticTokensApplied: boolean;
|
||||
readonly ranges: {
|
||||
/**
|
||||
* The start of the range (inclusive)
|
||||
|
||||
@@ -466,15 +466,16 @@ class SemanticColoringFeature extends Disposable {
|
||||
|
||||
private _watchers: Record<string, ModelSemanticColoring>;
|
||||
private _semanticStyling: SemanticStyling;
|
||||
private _configurationService: IConfigurationService;
|
||||
|
||||
constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) {
|
||||
super();
|
||||
this._configurationService = configurationService;
|
||||
this._watchers = Object.create(null);
|
||||
this._semanticStyling = this._register(new SemanticStyling(themeService, logService));
|
||||
|
||||
const isSemanticColoringEnabled = (model: ITextModel) => {
|
||||
if (!themeService.getColorTheme().semanticHighlighting) {
|
||||
return false;
|
||||
}
|
||||
const options = configurationService.getValue<IEditorSemanticHighlightingOptions>(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri });
|
||||
return options && options.enabled;
|
||||
};
|
||||
@@ -485,6 +486,20 @@ class SemanticColoringFeature extends Disposable {
|
||||
modelSemanticColoring.dispose();
|
||||
delete this._watchers[model.uri.toString()];
|
||||
};
|
||||
const handleSettingOrThemeChange = () => {
|
||||
for (let model of modelService.getModels()) {
|
||||
const curr = this._watchers[model.uri.toString()];
|
||||
if (isSemanticColoringEnabled(model)) {
|
||||
if (!curr) {
|
||||
register(model);
|
||||
}
|
||||
} else {
|
||||
if (curr) {
|
||||
deregister(model, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
this._register(modelService.onModelAdded((model) => {
|
||||
if (isSemanticColoringEnabled(model)) {
|
||||
register(model);
|
||||
@@ -496,22 +511,12 @@ class SemanticColoringFeature extends Disposable {
|
||||
deregister(model, curr);
|
||||
}
|
||||
}));
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
this._register(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(SemanticColoringFeature.SETTING_ID)) {
|
||||
for (let model of modelService.getModels()) {
|
||||
const curr = this._watchers[model.uri.toString()];
|
||||
if (isSemanticColoringEnabled(model)) {
|
||||
if (!curr) {
|
||||
register(model);
|
||||
}
|
||||
} else {
|
||||
if (curr) {
|
||||
deregister(model, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleSettingOrThemeChange();
|
||||
}
|
||||
});
|
||||
}));
|
||||
this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,12 +530,9 @@ class SemanticStyling extends Disposable {
|
||||
) {
|
||||
super();
|
||||
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
|
||||
if (this._themeService) {
|
||||
// workaround for tests which use undefined... :/
|
||||
this._register(this._themeService.onDidColorThemeChange(() => {
|
||||
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
|
||||
}));
|
||||
}
|
||||
this._register(this._themeService.onDidColorThemeChange(() => {
|
||||
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
|
||||
}));
|
||||
}
|
||||
|
||||
public get(provider: DocumentSemanticTokensProvider): SemanticColoringProviderStyling {
|
||||
@@ -768,14 +770,12 @@ class ModelSemanticColoring extends Disposable {
|
||||
this._fetchSemanticTokens.schedule();
|
||||
}));
|
||||
|
||||
if (themeService) {
|
||||
// workaround for tests which use undefined... :/
|
||||
this._register(themeService.onDidColorThemeChange(_ => {
|
||||
// clear out existing tokens
|
||||
this._setSemanticTokens(null, null, null, []);
|
||||
this._fetchSemanticTokens.schedule();
|
||||
}));
|
||||
}
|
||||
this._register(themeService.onDidColorThemeChange(_ => {
|
||||
// clear out existing tokens
|
||||
this._setSemanticTokens(null, null, null, []);
|
||||
this._fetchSemanticTokens.schedule();
|
||||
}));
|
||||
|
||||
this._fetchSemanticTokens.schedule(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,12 +36,6 @@ export namespace InspectTokensNLS {
|
||||
}
|
||||
|
||||
export namespace GoToLineNLS {
|
||||
export const gotoLineLabelValidLineAndColumn = nls.localize('gotoLineLabelValidLineAndColumn', "Go to line {0} and character {1}");
|
||||
export const gotoLineLabelValidLine = nls.localize('gotoLineLabelValidLine', "Go to line {0}");
|
||||
export const gotoLineLabelEmptyWithLineLimit = nls.localize('gotoLineLabelEmptyWithLineLimit', "Type a line number between 1 and {0} to navigate to");
|
||||
export const gotoLineLabelEmptyWithLineAndColumnLimit = nls.localize('gotoLineLabelEmptyWithLineAndColumnLimit', "Type a character between 1 and {0} to navigate to");
|
||||
export const gotoLineAriaLabel = nls.localize('gotoLineAriaLabel', "Current Line: {0}. Go to line {1}.");
|
||||
export const gotoLineActionInput = nls.localize('gotoLineActionInput', "Type a line number, followed by an optional colon and a character number to navigate to");
|
||||
export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line...");
|
||||
}
|
||||
|
||||
@@ -50,28 +44,13 @@ export namespace QuickHelpNLS {
|
||||
}
|
||||
|
||||
export namespace QuickCommandNLS {
|
||||
export const ariaLabelEntryWithKey = nls.localize('ariaLabelEntryWithKey', "{0}, {1}, commands");
|
||||
export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands");
|
||||
export const quickCommandActionInput = nls.localize('quickCommandActionInput', "Type the name of an action you want to execute");
|
||||
export const quickCommandActionLabel = nls.localize('quickCommandActionLabel', "Command Palette");
|
||||
export const quickCommandHelp = nls.localize('quickCommandActionHelp', "Show And Run Commands");
|
||||
}
|
||||
|
||||
export namespace QuickOutlineNLS {
|
||||
export const entryAriaLabel = nls.localize('entryAriaLabel', "{0}, symbols");
|
||||
export const quickOutlineActionInput = nls.localize('quickOutlineActionInput', "Type the name of an identifier you wish to navigate to");
|
||||
export const quickOutlineActionLabel = nls.localize('quickOutlineActionLabel', "Go to Symbol...");
|
||||
export const _symbols_ = nls.localize('symbols', "symbols ({0})");
|
||||
export const _modules_ = nls.localize('modules', "modules ({0})");
|
||||
export const _class_ = nls.localize('class', "classes ({0})");
|
||||
export const _interface_ = nls.localize('interface', "interfaces ({0})");
|
||||
export const _method_ = nls.localize('method', "methods ({0})");
|
||||
export const _function_ = nls.localize('function', "functions ({0})");
|
||||
export const _property_ = nls.localize('property', "properties ({0})");
|
||||
export const _variable_ = nls.localize('variable', "variables ({0})");
|
||||
export const _variable2_ = nls.localize('variable2', "variables ({0})");
|
||||
export const _constructor_ = nls.localize('_constructor', "constructors ({0})");
|
||||
export const _call_ = nls.localize('call', "calls ({0})");
|
||||
export const quickOutlineByCategoryActionLabel = nls.localize('quickOutlineByCategoryActionLabel', "Go to Symbol by Category...");
|
||||
}
|
||||
|
||||
export namespace StandaloneCodeEditorNLS {
|
||||
|
||||
@@ -27,7 +27,7 @@ export abstract class AbstractEditorCommandsQuickAccessProvider extends Abstract
|
||||
/**
|
||||
* Subclasses to provide the current active editor control.
|
||||
*/
|
||||
abstract activeTextEditorControl: IEditor | undefined;
|
||||
protected abstract activeTextEditorControl: IEditor | undefined;
|
||||
|
||||
protected getCodeEditorCommandPicks(): ICommandQuickPick[] {
|
||||
const activeTextEditorControl = this.activeTextEditorControl;
|
||||
|
||||
192
src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts
Normal file
192
src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
||||
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, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
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;
|
||||
overviewRulerDecorationId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider {
|
||||
|
||||
//#region Provider methods
|
||||
|
||||
provide(picker: IQuickPick<IQuickPickItem>, 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<IQuickPickItem>, 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 implement if they can operate on the text editor.
|
||||
*/
|
||||
protected canProvideWithTextEditor(editor: IEditor): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses to implement to provide picks for the picker when an editor is active.
|
||||
*/
|
||||
protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
|
||||
|
||||
/**
|
||||
* Subclasses to implement to provide picks for the picker when no editor is active.
|
||||
*/
|
||||
protected abstract provideWithoutTextEditor(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
|
||||
|
||||
protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): 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.
|
||||
*/
|
||||
protected abstract readonly onDidActiveTextEditorControlChange: Event<void>;
|
||||
|
||||
/**
|
||||
* Subclasses to provide the current active editor control.
|
||||
*/
|
||||
protected abstract activeTextEditorControl: IEditor | undefined;
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Decorations Utils
|
||||
|
||||
private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined;
|
||||
|
||||
protected addDecorations(editor: IEditor, range: IRange): void {
|
||||
editor.changeDecorations(changeAccessor => {
|
||||
|
||||
// Reset old decorations if any
|
||||
const deleteDecorations: string[] = [];
|
||||
if (this.rangeHighlightDecorationId) {
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId);
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);
|
||||
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
|
||||
// Add new decorations for the range
|
||||
const newDecorations: IModelDeltaDecoration[] = [
|
||||
|
||||
// highlight the entire line on the range
|
||||
{
|
||||
range,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
},
|
||||
|
||||
// also add overview ruler highlight
|
||||
{
|
||||
range,
|
||||
options: {
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(overviewRulerRangeHighlight),
|
||||
position: OverviewRulerLane.Full
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);
|
||||
|
||||
this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId };
|
||||
});
|
||||
}
|
||||
|
||||
protected clearDecorations(editor: IEditor): void {
|
||||
const rangeHighlightDecorationId = this.rangeHighlightDecorationId;
|
||||
if (rangeHighlightDecorationId) {
|
||||
editor.changeDecorations(changeAccessor => {
|
||||
changeAccessor.deltaDecorations([
|
||||
rangeHighlightDecorationId.overviewRulerDecorationId,
|
||||
rangeHighlightDecorationId.rangeHighlightId
|
||||
], []);
|
||||
});
|
||||
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@@ -4,74 +4,32 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 {
|
||||
|
||||
static PREFIX = ':';
|
||||
|
||||
provide(picker: IQuickPick<IGotoLineQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
protected provideWithoutTextEditor(picker: IQuickPick<IGotoLineQuickPickItem>): IDisposable {
|
||||
const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line.");
|
||||
|
||||
// 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 {
|
||||
const label = localize('cannotRunGotoLine', "Open a text file first to go to a line.");
|
||||
picker.items = [{ label }];
|
||||
picker.ariaLabel = label;
|
||||
|
||||
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 +38,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 +75,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 +146,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();
|
||||
}
|
||||
}
|
||||
|
||||
420
src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts
Normal file
420
src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts
Normal file
@@ -0,0 +1,420 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
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 { IRange, Range } from 'vs/editor/common/core/range';
|
||||
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';
|
||||
import { trim, format } from 'vs/base/common/strings';
|
||||
import { fuzzyScore, FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
|
||||
interface IGotoSymbolQuickPickItem extends IQuickPickItem {
|
||||
kind: SymbolKind,
|
||||
index: number,
|
||||
score?: FuzzyScore;
|
||||
range?: { decoration: IRange, selection: IRange },
|
||||
}
|
||||
|
||||
export interface IGotoSymbolQuickAccessProviderOptions {
|
||||
openSideBySideDirection: () => undefined | 'right' | 'down'
|
||||
}
|
||||
|
||||
export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
|
||||
|
||||
static PREFIX = '@';
|
||||
static SCOPE_PREFIX = ':';
|
||||
static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`;
|
||||
|
||||
constructor(private options?: IGotoSymbolQuickAccessProviderOptions) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
|
||||
const label = localize('cannotRunGotoSymbolWithoutEditor', "Open a text editor first to go to a symbol.");
|
||||
|
||||
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
|
||||
picker.ariaLabel = label;
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
protected provideWithTextEditor(editor: IEditor, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
const model = this.getModel(editor);
|
||||
if (!model) {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
// Provide symbols from model if available in registry
|
||||
if (DocumentSymbolProviderRegistry.has(model)) {
|
||||
return this.doProvideWithEditorSymbols(editor, model, picker, token);
|
||||
}
|
||||
|
||||
// Otherwise show an entry for a model without registry
|
||||
// But give a chance to resolve the symbols at a later
|
||||
// point if possible
|
||||
return this.doProvideWithoutEditorSymbols(editor, model, picker, token);
|
||||
}
|
||||
|
||||
private doProvideWithoutEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Generic pick for not having any symbol information
|
||||
const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "Open a text editor with symbol information first to go to a symbol.");
|
||||
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
|
||||
picker.ariaLabel = label;
|
||||
|
||||
// Listen to changes to the registry and see if eventually
|
||||
// we do get symbols. This can happen if the picker is opened
|
||||
// very early after the model has loaded but before the
|
||||
// language registry is ready.
|
||||
// https://github.com/microsoft/vscode/issues/70607
|
||||
const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => {
|
||||
if (DocumentSymbolProviderRegistry.has(model)) {
|
||||
symbolProviderListener.dispose();
|
||||
|
||||
disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token));
|
||||
}
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
private doProvideWithEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
// Goto symbol once picked
|
||||
disposables.add(picker.onDidAccept(() => {
|
||||
const [item] = picker.selectedItems;
|
||||
if (item && item.range) {
|
||||
this.gotoLocation(editor, item.range.selection, picker.keyMods);
|
||||
|
||||
picker.hide();
|
||||
}
|
||||
}));
|
||||
|
||||
// Goto symbol side by side if enabled
|
||||
disposables.add(picker.onDidTriggerItemButton(({ item }) => {
|
||||
if (item && item.range) {
|
||||
this.gotoLocation(editor, item.range.selection, picker.keyMods, true);
|
||||
|
||||
picker.hide();
|
||||
}
|
||||
}));
|
||||
|
||||
// Resolve symbols from document once and reuse this
|
||||
// request for all filtering and typing then on
|
||||
const symbolsPromise = this.getDocumentSymbols(model, true, token);
|
||||
|
||||
// Set initial picks and update on type
|
||||
let picksCts: CancellationTokenSource | undefined = undefined;
|
||||
const updatePickerItems = async () => {
|
||||
|
||||
// Cancel any previous ask for picks and busy
|
||||
picksCts?.dispose(true);
|
||||
picker.busy = false;
|
||||
|
||||
// Create new cancellation source for this run
|
||||
picksCts = new CancellationTokenSource(token);
|
||||
|
||||
// Collect symbol picks
|
||||
picker.busy = true;
|
||||
try {
|
||||
const items = await this.getSymbolPicks(symbolsPromise, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token);
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
picker.items = items;
|
||||
} finally {
|
||||
if (!token.isCancellationRequested) {
|
||||
picker.busy = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
|
||||
updatePickerItems();
|
||||
|
||||
// Reveal and decorate when active item changes
|
||||
// However, ignore the very first event so that
|
||||
// opening the picker is not immediately revealing
|
||||
// and decorating the first entry.
|
||||
let ignoreFirstActiveEvent = true;
|
||||
disposables.add(picker.onDidChangeActive(() => {
|
||||
const [item] = picker.activeItems;
|
||||
if (item && item.range) {
|
||||
if (ignoreFirstActiveEvent) {
|
||||
ignoreFirstActiveEvent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reveal
|
||||
editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth);
|
||||
|
||||
// Decorate
|
||||
this.addDecorations(editor, item.range.decoration);
|
||||
}
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
private async getSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, filter: string, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
|
||||
const symbols = await symbolsPromise;
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Normalize filter
|
||||
const filterBySymbolKind = filter.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0;
|
||||
const filterPos = filterBySymbolKind ? 1 : 0;
|
||||
const [symbolFilter, containerFilter] = filter.split(' ') as [string, string | undefined];
|
||||
const symbolFilterLow = symbolFilter.toLowerCase();
|
||||
const containerFilterLow = containerFilter?.toLowerCase();
|
||||
|
||||
// Convert to symbol picks and apply filtering
|
||||
const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = [];
|
||||
for (let index = 0; index < symbols.length; index++) {
|
||||
const symbol = symbols[index];
|
||||
|
||||
const symbolLabel = trim(symbol.name);
|
||||
const containerLabel = symbol.containerName;
|
||||
|
||||
let symbolScore: FuzzyScore | undefined = undefined;
|
||||
let containerScore: FuzzyScore | undefined = undefined;
|
||||
|
||||
let includeSymbol = true;
|
||||
if (filter.length > filterPos) {
|
||||
|
||||
// Score by symbol
|
||||
symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, filterPos, symbolLabel, symbolLabel.toLowerCase(), 0, true);
|
||||
includeSymbol = !!symbolScore;
|
||||
|
||||
// Score by container if specified
|
||||
if (includeSymbol && containerFilter && containerFilterLow) {
|
||||
if (containerLabel) {
|
||||
containerScore = fuzzyScore(containerFilter, containerFilterLow, filterPos, containerLabel, containerLabel.toLowerCase(), 0, true);
|
||||
}
|
||||
|
||||
includeSymbol = !!containerScore;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeSymbol) {
|
||||
const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`;
|
||||
const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0;
|
||||
|
||||
filteredSymbolPicks.push({
|
||||
index,
|
||||
kind: symbol.kind,
|
||||
score: symbolScore,
|
||||
label: symbolLabelWithIcon,
|
||||
ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", symbolLabel),
|
||||
description: containerLabel,
|
||||
highlights: deprecated ? undefined : {
|
||||
label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */),
|
||||
description: createMatches(containerScore)
|
||||
},
|
||||
range: {
|
||||
selection: Range.collapseToStart(symbol.selectionRange),
|
||||
decoration: symbol.range
|
||||
},
|
||||
strikethrough: deprecated,
|
||||
buttons: (() => {
|
||||
const openSideBySideDirection = this.options?.openSideBySideDirection();
|
||||
if (!openSideBySideDirection) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical',
|
||||
tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom")
|
||||
}
|
||||
];
|
||||
})()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by score
|
||||
const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ?
|
||||
this.compareByKindAndScore(symbolA, symbolB) :
|
||||
this.compareByScore(symbolA, symbolB)
|
||||
);
|
||||
|
||||
// Add separator for types
|
||||
// - @ only total number of symbols
|
||||
// - @: grouped by symbol kind
|
||||
let symbolPicks: Array<IGotoSymbolQuickPickItem | IQuickPickSeparator> = [];
|
||||
if (filterBySymbolKind) {
|
||||
let lastSymbolKind: SymbolKind | undefined = undefined;
|
||||
let lastSeparator: IQuickPickSeparator | undefined = undefined;
|
||||
let lastSymbolKindCounter = 0;
|
||||
|
||||
function updateLastSeparatorLabel(): void {
|
||||
if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) {
|
||||
lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter);
|
||||
}
|
||||
}
|
||||
|
||||
for (const symbolPick of sortedFilteredSymbolPicks) {
|
||||
|
||||
// Found new kind
|
||||
if (lastSymbolKind !== symbolPick.kind) {
|
||||
|
||||
// Update last separator with number of symbols we found for kind
|
||||
updateLastSeparatorLabel();
|
||||
|
||||
lastSymbolKind = symbolPick.kind;
|
||||
lastSymbolKindCounter = 1;
|
||||
|
||||
// Add new separator for new kind
|
||||
lastSeparator = { type: 'separator' };
|
||||
symbolPicks.push(lastSeparator);
|
||||
}
|
||||
|
||||
// Existing kind, keep counting
|
||||
else {
|
||||
lastSymbolKindCounter++;
|
||||
}
|
||||
|
||||
// Add to final result
|
||||
symbolPicks.push(symbolPick);
|
||||
}
|
||||
|
||||
// Update last separator with number of symbols we found for kind
|
||||
updateLastSeparatorLabel();
|
||||
} else {
|
||||
symbolPicks = [
|
||||
{ label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' },
|
||||
...sortedFilteredSymbolPicks
|
||||
];
|
||||
}
|
||||
|
||||
return symbolPicks;
|
||||
}
|
||||
|
||||
private compareByScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number {
|
||||
if (!symbolA.score && symbolB.score) {
|
||||
return 1;
|
||||
} else if (symbolA.score && !symbolB.score) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (symbolA.score && symbolB.score) {
|
||||
if (symbolA.score[0] > symbolB.score[0]) {
|
||||
return -1;
|
||||
} else if (symbolA.score[0] < symbolB.score[0]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (symbolA.index < symbolB.index) {
|
||||
return -1;
|
||||
} else if (symbolA.index > symbolB.index) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private compareByKindAndScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number {
|
||||
const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND;
|
||||
const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND;
|
||||
|
||||
// Sort by type first if scoped search
|
||||
const result = kindA.localeCompare(kindB);
|
||||
if (result === 0) {
|
||||
return this.compareByScore(symbolA, symbolB);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
|
||||
const model = await OutlineModel.create(document, token);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const roots: DocumentSymbol[] = [];
|
||||
for (const child of values(model.children)) {
|
||||
if (child instanceof OutlineElement) {
|
||||
roots.push(child.symbol);
|
||||
} else {
|
||||
roots.push(...values(child.children).map(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #region NLS Helpers
|
||||
|
||||
const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})");
|
||||
const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = {
|
||||
[SymbolKind.Method]: localize('method', "methods ({0})"),
|
||||
[SymbolKind.Function]: localize('function', "functions ({0})"),
|
||||
[SymbolKind.Constructor]: localize('_constructor', "constructors ({0})"),
|
||||
[SymbolKind.Variable]: localize('variable', "variables ({0})"),
|
||||
[SymbolKind.Class]: localize('class', "classes ({0})"),
|
||||
[SymbolKind.Struct]: localize('struct', "structs ({0})"),
|
||||
[SymbolKind.Event]: localize('event', "events ({0})"),
|
||||
[SymbolKind.Operator]: localize('operator', "operators ({0})"),
|
||||
[SymbolKind.Interface]: localize('interface', "interfaces ({0})"),
|
||||
[SymbolKind.Namespace]: localize('namespace', "namespaces ({0})"),
|
||||
[SymbolKind.Package]: localize('package', "packages ({0})"),
|
||||
[SymbolKind.TypeParameter]: localize('typeParameter', "type parameters ({0})"),
|
||||
[SymbolKind.Module]: localize('modules', "modules ({0})"),
|
||||
[SymbolKind.Property]: localize('property', "properties ({0})"),
|
||||
[SymbolKind.Enum]: localize('enum', "enumerations ({0})"),
|
||||
[SymbolKind.EnumMember]: localize('enumMember', "enumeration members ({0})"),
|
||||
[SymbolKind.String]: localize('string', "strings ({0})"),
|
||||
[SymbolKind.File]: localize('file', "files ({0})"),
|
||||
[SymbolKind.Array]: localize('array', "arrays ({0})"),
|
||||
[SymbolKind.Number]: localize('number', "numbers ({0})"),
|
||||
[SymbolKind.Boolean]: localize('boolean', "booleans ({0})"),
|
||||
[SymbolKind.Object]: localize('object', "objects ({0})"),
|
||||
[SymbolKind.Key]: localize('key', "keys ({0})"),
|
||||
[SymbolKind.Field]: localize('field', "fields ({0})"),
|
||||
[SymbolKind.Constant]: localize('constant', "constants ({0})")
|
||||
};
|
||||
|
||||
//#endregion
|
||||
@@ -1,104 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDeltaDecoration, OverviewRulerLane } 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 { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
interface IEditorLineDecoration {
|
||||
rangeHighlightId: string;
|
||||
overviewRulerDecorationId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable quick access provider for the editor with support for adding decorations.
|
||||
*/
|
||||
export abstract class AbstractEditorQuickAccessProvider implements IQuickAccessProvider {
|
||||
|
||||
/**
|
||||
* Subclasses to provide an event when the active editor control changes.
|
||||
*/
|
||||
abstract readonly onDidActiveTextEditorControlChange: Event<void>;
|
||||
|
||||
/**
|
||||
* Subclasses to provide the current active editor control.
|
||||
*/
|
||||
abstract activeTextEditorControl: IEditor | undefined;
|
||||
|
||||
/**
|
||||
* Subclasses to implement the quick access picker.
|
||||
*/
|
||||
abstract provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
|
||||
|
||||
|
||||
//#region Decorations Utils
|
||||
|
||||
private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined;
|
||||
|
||||
protected addDecorations(editor: IEditor, range: IRange): void {
|
||||
editor.changeDecorations(changeAccessor => {
|
||||
|
||||
// Reset old decorations if any
|
||||
const deleteDecorations: string[] = [];
|
||||
if (this.rangeHighlightDecorationId) {
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId);
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);
|
||||
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
|
||||
// Add new decorations for the range
|
||||
const newDecorations: IModelDeltaDecoration[] = [
|
||||
|
||||
// highlight the entire line on the range
|
||||
{
|
||||
range,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
},
|
||||
|
||||
// also add overview ruler highlight
|
||||
{
|
||||
range,
|
||||
options: {
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(overviewRulerRangeHighlight),
|
||||
position: OverviewRulerLane.Full
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);
|
||||
|
||||
this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId };
|
||||
});
|
||||
}
|
||||
|
||||
protected clearDecorations(editor: IEditor): void {
|
||||
const rangeHighlightDecorationId = this.rangeHighlightDecorationId;
|
||||
if (rangeHighlightDecorationId) {
|
||||
editor.changeDecorations(changeAccessor => {
|
||||
changeAccessor.deltaDecorations([
|
||||
rangeHighlightDecorationId.overviewRulerDecorationId,
|
||||
rangeHighlightDecorationId.rangeHighlightId
|
||||
], []);
|
||||
});
|
||||
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@@ -7,11 +7,9 @@ import 'vs/editor/editor.all';
|
||||
import 'vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp';
|
||||
import 'vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard';
|
||||
import 'vs/editor/standalone/browser/inspectTokens/inspectTokens';
|
||||
import 'vs/editor/standalone/browser/quickOpen/gotoLine';
|
||||
import 'vs/editor/standalone/browser/quickOpen/quickCommand';
|
||||
import 'vs/editor/standalone/browser/quickOpen/quickOutline';
|
||||
import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess';
|
||||
import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess';
|
||||
import 'vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess';
|
||||
import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess';
|
||||
import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch';
|
||||
import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast';
|
||||
|
||||
@@ -11,15 +11,20 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
|
||||
import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess';
|
||||
import { IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider {
|
||||
|
||||
get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); }
|
||||
protected get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); }
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -42,3 +47,30 @@ Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessPro
|
||||
prefix: StandaloneCommandsQuickAccessProvider.PREFIX,
|
||||
helpEntries: [{ description: QuickCommandNLS.quickCommandHelp, needsEditor: true }]
|
||||
});
|
||||
|
||||
export class GotoLineAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.quickCommand',
|
||||
label: QuickCommandNLS.quickCommandActionLabel,
|
||||
alias: 'Command Palette',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyCode.F1,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'z_commands',
|
||||
order: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
accessor.get(IQuickInputService).quickAccess.show(StandaloneCommandsQuickAccessProvider.PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(GotoLineAction);
|
||||
|
||||
@@ -10,22 +10,51 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { GoToLineNLS } from 'vs/editor/common/standaloneStrings';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
|
||||
ctor: StandaloneGotoLineQuickAccessProvider,
|
||||
prefix: AbstractGotoLineQuickAccessProvider.PREFIX,
|
||||
prefix: StandaloneGotoLineQuickAccessProvider.PREFIX,
|
||||
helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }]
|
||||
});
|
||||
|
||||
export class GotoLineAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.gotoLine',
|
||||
label: GoToLineNLS.gotoLineActionLabel,
|
||||
alias: 'Go to Line...',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_G,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
accessor.get(IQuickInputService).quickAccess.show(StandaloneGotoLineQuickAccessProvider.PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(GotoLineAction);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
|
||||
|
||||
protected readonly onDidActiveTextEditorControlChange = Event.None;
|
||||
|
||||
constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected get activeTextEditorControl() {
|
||||
return withNullAsUndefined(this.editorService.getFocusedCodeEditor());
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
|
||||
ctor: StandaloneGotoSymbolQuickAccessProvider,
|
||||
prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX,
|
||||
helpEntries: [
|
||||
{ description: QuickOutlineNLS.quickOutlineActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true },
|
||||
{ description: QuickOutlineNLS.quickOutlineByCategoryActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true }
|
||||
]
|
||||
});
|
||||
|
||||
export class GotoLineAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.quickOutline',
|
||||
label: QuickOutlineNLS.quickOutlineActionLabel,
|
||||
alias: 'Go to Symbol...',
|
||||
precondition: EditorContextKeys.hasDocumentSymbolProvider,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 3
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
accessor.get(IQuickInputService).quickAccess.show(AbstractGotoSymbolQuickAccessProvider.PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(GotoLineAction);
|
||||
@@ -8,8 +8,8 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/
|
||||
import { QuickHelpNLS } from 'vs/editor/common/standaloneStrings';
|
||||
import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess';
|
||||
|
||||
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).defaultProvider = {
|
||||
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
|
||||
ctor: HelpQuickAccessProvider,
|
||||
prefix: '',
|
||||
helpEntries: [{ description: QuickHelpNLS.helpQuickAccessActionLabel, needsEditor: true }]
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight,
|
||||
.monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
|
||||
color: #0066BF;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight,
|
||||
.vs-dark .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
|
||||
color: #0097fb;
|
||||
}
|
||||
|
||||
.hc-black .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight,
|
||||
.hc-black .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight {
|
||||
color: #F38518;
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./editorQuickOpen';
|
||||
import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, IActionOptions, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IEditorContribution, ScrollType, IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDeltaDecoration } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { QuickOpenEditorWidget } from 'vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface IQuickOpenControllerOpts {
|
||||
inputAriaLabel: string;
|
||||
getModel(value: string): QuickOpenModel;
|
||||
getAutoFocus(searchValue: string): IAutoFocus;
|
||||
}
|
||||
|
||||
export class QuickOpenController implements IEditorContribution, IDecorator {
|
||||
|
||||
public static readonly ID = 'editor.controller.quickOpenController';
|
||||
|
||||
public static get(editor: ICodeEditor): QuickOpenController {
|
||||
return editor.getContribution<QuickOpenController>(QuickOpenController.ID);
|
||||
}
|
||||
|
||||
private readonly editor: ICodeEditor;
|
||||
private widget: QuickOpenEditorWidget | null = null;
|
||||
private rangeHighlightDecorationId: string | null = null;
|
||||
private lastKnownEditorSelection: Selection | null = null;
|
||||
|
||||
constructor(editor: ICodeEditor, @IThemeService private readonly themeService: IThemeService) {
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
// Dispose widget
|
||||
if (this.widget) {
|
||||
this.widget.destroy();
|
||||
this.widget = null;
|
||||
}
|
||||
}
|
||||
|
||||
public run(opts: IQuickOpenControllerOpts): void {
|
||||
if (this.widget) {
|
||||
this.widget.destroy();
|
||||
this.widget = null;
|
||||
}
|
||||
|
||||
// Create goto line widget
|
||||
let onClose = (canceled: boolean) => {
|
||||
// Clear Highlight Decorations if present
|
||||
this.clearDecorations();
|
||||
|
||||
// Restore selection if canceled
|
||||
if (canceled && this.lastKnownEditorSelection) {
|
||||
this.editor.setSelection(this.lastKnownEditorSelection);
|
||||
this.editor.revealRangeInCenterIfOutsideViewport(this.lastKnownEditorSelection, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
this.lastKnownEditorSelection = null;
|
||||
|
||||
// Return focus to the editor if
|
||||
// - focus is back on the <body> element because no other focusable element was clicked
|
||||
// - a command was picked from the picker which indicates the editor should get focused
|
||||
if (document.activeElement === document.body || !canceled) {
|
||||
this.editor.focus();
|
||||
}
|
||||
};
|
||||
|
||||
this.widget = new QuickOpenEditorWidget(
|
||||
this.editor,
|
||||
() => onClose(false),
|
||||
() => onClose(true),
|
||||
(value: string) => {
|
||||
this.widget!.setInput(opts.getModel(value), opts.getAutoFocus(value));
|
||||
},
|
||||
{
|
||||
inputAriaLabel: opts.inputAriaLabel
|
||||
},
|
||||
this.themeService
|
||||
);
|
||||
|
||||
// Remember selection to be able to restore on cancel
|
||||
if (!this.lastKnownEditorSelection) {
|
||||
this.lastKnownEditorSelection = this.editor.getSelection();
|
||||
}
|
||||
|
||||
// Show
|
||||
this.widget.show('');
|
||||
}
|
||||
|
||||
private static readonly _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
});
|
||||
|
||||
public decorateLine(range: Range, editor: ICodeEditor): void {
|
||||
const oldDecorations: string[] = [];
|
||||
if (this.rangeHighlightDecorationId) {
|
||||
oldDecorations.push(this.rangeHighlightDecorationId);
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
const newDecorations: IModelDeltaDecoration[] = [
|
||||
{
|
||||
range: range,
|
||||
options: QuickOpenController._RANGE_HIGHLIGHT_DECORATION
|
||||
}
|
||||
];
|
||||
|
||||
const decorations = editor.deltaDecorations(oldDecorations, newDecorations);
|
||||
this.rangeHighlightDecorationId = decorations[0];
|
||||
}
|
||||
|
||||
public clearDecorations(): void {
|
||||
if (this.rangeHighlightDecorationId) {
|
||||
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IQuickOpenOpts {
|
||||
/**
|
||||
* provide the quick open model for the given search value.
|
||||
*/
|
||||
getModel(value: string): QuickOpenModel;
|
||||
|
||||
/**
|
||||
* provide the quick open auto focus mode for the given search value.
|
||||
*/
|
||||
getAutoFocus(searchValue: string): IAutoFocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for providing quick open in the editor.
|
||||
*/
|
||||
export abstract class BaseEditorQuickOpenAction extends EditorAction {
|
||||
|
||||
private readonly _inputAriaLabel: string;
|
||||
|
||||
constructor(inputAriaLabel: string, opts: IActionOptions) {
|
||||
super(opts);
|
||||
this._inputAriaLabel = inputAriaLabel;
|
||||
}
|
||||
|
||||
protected getController(editor: ICodeEditor): QuickOpenController {
|
||||
return QuickOpenController.get(editor);
|
||||
}
|
||||
|
||||
protected _show(controller: QuickOpenController, opts: IQuickOpenOpts): void {
|
||||
controller.run({
|
||||
inputAriaLabel: this._inputAriaLabel,
|
||||
getModel: (value: string): QuickOpenModel => opts.getModel(value),
|
||||
getAutoFocus: (searchValue: string): IAutoFocus => opts.getAutoFocus(searchValue)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDecorator {
|
||||
decorateLine(range: Range, editor: IEditor): void;
|
||||
clearDecorations(): void;
|
||||
}
|
||||
|
||||
registerEditorContribution(QuickOpenController.ID, QuickOpenController);
|
||||
@@ -1,8 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-quick-open-widget {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./gotoLine';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IEditor, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { GoToLineNLS } from 'vs/editor/common/standaloneStrings';
|
||||
|
||||
interface ParseResult {
|
||||
position: Position;
|
||||
isValid: boolean;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export class GotoLineEntry extends QuickOpenEntry {
|
||||
private readonly parseResult: ParseResult;
|
||||
private readonly decorator: IDecorator;
|
||||
private readonly editor: IEditor;
|
||||
|
||||
constructor(line: string, editor: IEditor, decorator: IDecorator) {
|
||||
super();
|
||||
|
||||
this.editor = editor;
|
||||
this.decorator = decorator;
|
||||
this.parseResult = this.parseInput(line);
|
||||
}
|
||||
|
||||
private parseInput(line: string): ParseResult {
|
||||
const numbers = line.split(',').map(part => parseInt(part, 10)).filter(part => !isNaN(part));
|
||||
let position: Position;
|
||||
|
||||
if (numbers.length === 0) {
|
||||
position = new Position(-1, -1);
|
||||
} else if (numbers.length === 1) {
|
||||
position = new Position(numbers[0], 1);
|
||||
} else {
|
||||
position = new Position(numbers[0], numbers[1]);
|
||||
}
|
||||
|
||||
let model: ITextModel | null;
|
||||
if (isCodeEditor(this.editor)) {
|
||||
model = this.editor.getModel();
|
||||
} else {
|
||||
const diffModel = (<IDiffEditor>this.editor).getModel();
|
||||
model = diffModel ? diffModel.modified : null;
|
||||
}
|
||||
|
||||
const isValid = model ? model.validatePosition(position).equals(position) : false;
|
||||
let label: string;
|
||||
|
||||
if (isValid) {
|
||||
if (position.column && position.column > 1) {
|
||||
label = strings.format(GoToLineNLS.gotoLineLabelValidLineAndColumn, position.lineNumber, position.column);
|
||||
} else {
|
||||
label = strings.format(GoToLineNLS.gotoLineLabelValidLine, position.lineNumber);
|
||||
}
|
||||
} else if (position.lineNumber < 1 || position.lineNumber > (model ? model.getLineCount() : 0)) {
|
||||
label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineLimit, model ? model.getLineCount() : 0);
|
||||
} else {
|
||||
label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineAndColumnLimit, model ? model.getLineMaxColumn(position.lineNumber) : 0);
|
||||
}
|
||||
|
||||
return {
|
||||
position: position,
|
||||
isValid: isValid,
|
||||
label: label
|
||||
};
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
return this.parseResult.label;
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
const position = this.editor.getPosition();
|
||||
const currentLine = position ? position.lineNumber : 0;
|
||||
return strings.format(GoToLineNLS.gotoLineAriaLabel, currentLine, this.parseResult.label);
|
||||
}
|
||||
|
||||
run(mode: Mode, _context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen();
|
||||
}
|
||||
|
||||
return this.runPreview();
|
||||
}
|
||||
|
||||
runOpen(): boolean {
|
||||
|
||||
// No-op if range is not valid
|
||||
if (!this.parseResult.isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply selection and focus
|
||||
const range = this.toSelection();
|
||||
(<ICodeEditor>this.editor).setSelection(range);
|
||||
(<ICodeEditor>this.editor).revealRangeInCenter(range, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
runPreview(): boolean {
|
||||
|
||||
// No-op if range is not valid
|
||||
if (!this.parseResult.isValid) {
|
||||
this.decorator.clearDecorations();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select Line Position
|
||||
const range = this.toSelection();
|
||||
this.editor.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
|
||||
// Decorate if possible
|
||||
this.decorator.decorateLine(range, this.editor);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private toSelection(): Range {
|
||||
return new Range(
|
||||
this.parseResult.position.lineNumber,
|
||||
this.parseResult.position.column,
|
||||
this.parseResult.position.lineNumber,
|
||||
this.parseResult.position.column
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class GotoLineAction extends BaseEditorQuickOpenAction {
|
||||
|
||||
constructor() {
|
||||
super(GoToLineNLS.gotoLineActionInput, {
|
||||
id: 'editor.action.gotoLine',
|
||||
label: GoToLineNLS.gotoLineActionLabel,
|
||||
alias: 'Go to Line...',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_G,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G },
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
this._show(this.getController(editor), {
|
||||
getModel: (value: string): QuickOpenModel => {
|
||||
return new QuickOpenModel([new GotoLineEntry(value, editor, this.getController(editor))]);
|
||||
},
|
||||
|
||||
getAutoFocus: (searchValue: string): IAutoFocus => {
|
||||
return {
|
||||
autoFocusFirstEntry: searchValue.length > 0
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(GotoLineAction);
|
||||
@@ -1,144 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { matchesFuzzy } from 'vs/base/common/filters';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { IEditor, IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { BaseEditorQuickOpenAction } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings';
|
||||
|
||||
export class EditorActionCommandEntry extends QuickOpenEntryGroup {
|
||||
private readonly key: string;
|
||||
private readonly action: IEditorAction;
|
||||
private readonly editor: IEditor;
|
||||
private readonly keyAriaLabel: string;
|
||||
|
||||
constructor(key: string, keyAriaLabel: string, highlights: IHighlight[], action: IEditorAction, editor: IEditor) {
|
||||
super();
|
||||
|
||||
this.key = key;
|
||||
this.keyAriaLabel = keyAriaLabel;
|
||||
this.setHighlights(highlights);
|
||||
this.action = action;
|
||||
this.editor = editor;
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.action.label;
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
if (this.keyAriaLabel) {
|
||||
return strings.format(QuickCommandNLS.ariaLabelEntryWithKey, this.getLabel(), this.keyAriaLabel);
|
||||
}
|
||||
|
||||
return strings.format(QuickCommandNLS.ariaLabelEntry, this.getLabel());
|
||||
}
|
||||
|
||||
public getGroupLabel(): string {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
|
||||
// Use a timeout to give the quick open widget a chance to close itself first
|
||||
setTimeout(() => {
|
||||
|
||||
// Some actions are enabled only when editor has focus
|
||||
this.editor.focus();
|
||||
|
||||
try {
|
||||
let promise = this.action.run() || Promise.resolve();
|
||||
promise.then(undefined, onUnexpectedError);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}, 50);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickCommandAction extends BaseEditorQuickOpenAction {
|
||||
|
||||
constructor() {
|
||||
super(QuickCommandNLS.quickCommandActionInput, {
|
||||
id: 'editor.action.quickCommand',
|
||||
label: QuickCommandNLS.quickCommandActionLabel,
|
||||
alias: 'Command Palette',
|
||||
precondition: undefined,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyCode.F1,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'z_commands',
|
||||
order: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
const keybindingService = accessor.get(IKeybindingService);
|
||||
|
||||
this._show(this.getController(editor), {
|
||||
getModel: (value: string): QuickOpenModel => {
|
||||
return new QuickOpenModel(this._editorActionsToEntries(keybindingService, editor, value));
|
||||
},
|
||||
|
||||
getAutoFocus: (searchValue: string): IAutoFocus => {
|
||||
return {
|
||||
autoFocusFirstEntry: true,
|
||||
autoFocusPrefixMatch: searchValue
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _sort(elementA: QuickOpenEntryGroup, elementB: QuickOpenEntryGroup): number {
|
||||
let elementAName = (elementA.getLabel() || '').toLowerCase();
|
||||
let elementBName = (elementB.getLabel() || '').toLowerCase();
|
||||
|
||||
return elementAName.localeCompare(elementBName);
|
||||
}
|
||||
|
||||
private _editorActionsToEntries(keybindingService: IKeybindingService, editor: ICodeEditor, searchValue: string): EditorActionCommandEntry[] {
|
||||
let actions: IEditorAction[] = editor.getSupportedActions();
|
||||
let entries: EditorActionCommandEntry[] = [];
|
||||
|
||||
for (const action of actions) {
|
||||
|
||||
let keybinding = keybindingService.lookupKeybinding(action.id);
|
||||
|
||||
if (action.label) {
|
||||
let highlights = matchesFuzzy(searchValue, action.label);
|
||||
if (highlights) {
|
||||
entries.push(new EditorActionCommandEntry(keybinding ? keybinding.getLabel() || '' : '', keybinding ? keybinding.getAriaLabel() || '' : '', highlights, action, editor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
entries = entries.sort(this._sort);
|
||||
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(QuickCommandAction);
|
||||
@@ -1,107 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { QuickOpenWidget } from 'vs/base/parts/quickopen/browser/quickOpenWidget';
|
||||
import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface IQuickOpenEditorWidgetOptions {
|
||||
inputAriaLabel: string;
|
||||
}
|
||||
|
||||
export class QuickOpenEditorWidget implements IOverlayWidget {
|
||||
|
||||
private static readonly ID = 'editor.contrib.quickOpenEditorWidget';
|
||||
|
||||
private readonly codeEditor: ICodeEditor;
|
||||
private readonly themeService: IThemeService;
|
||||
private visible: boolean | undefined;
|
||||
private quickOpenWidget: QuickOpenWidget;
|
||||
private domNode: HTMLElement;
|
||||
private styler: IDisposable;
|
||||
|
||||
constructor(codeEditor: ICodeEditor, onOk: () => void, onCancel: () => void, onType: (value: string) => void, configuration: IQuickOpenEditorWidgetOptions, themeService: IThemeService) {
|
||||
this.codeEditor = codeEditor;
|
||||
this.themeService = themeService;
|
||||
this.visible = false;
|
||||
|
||||
this.domNode = document.createElement('div');
|
||||
|
||||
this.quickOpenWidget = new QuickOpenWidget(
|
||||
this.domNode,
|
||||
{
|
||||
onOk: onOk,
|
||||
onCancel: onCancel,
|
||||
onType: onType
|
||||
}, {
|
||||
inputPlaceHolder: undefined,
|
||||
inputAriaLabel: configuration.inputAriaLabel,
|
||||
keyboardSupport: true
|
||||
}
|
||||
);
|
||||
this.styler = attachQuickOpenStyler(this.quickOpenWidget, this.themeService, {
|
||||
pickerGroupForeground: foreground
|
||||
});
|
||||
|
||||
this.quickOpenWidget.create();
|
||||
this.codeEditor.addOverlayWidget(this);
|
||||
}
|
||||
|
||||
setInput(model: QuickOpenModel, focus: IAutoFocus): void {
|
||||
this.quickOpenWidget.setInput(model, focus);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return QuickOpenEditorWidget.ID;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.codeEditor.removeOverlayWidget(this);
|
||||
this.quickOpenWidget.dispose();
|
||||
this.styler.dispose();
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return !!this.visible;
|
||||
}
|
||||
|
||||
show(value: string): void {
|
||||
this.visible = true;
|
||||
|
||||
const editorLayout = this.codeEditor.getLayoutInfo();
|
||||
if (editorLayout) {
|
||||
this.quickOpenWidget.layout(new Dimension(editorLayout.width, editorLayout.height));
|
||||
}
|
||||
|
||||
this.quickOpenWidget.show(value);
|
||||
this.codeEditor.layoutOverlayWidget(this);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.visible = false;
|
||||
this.quickOpenWidget.hide();
|
||||
this.codeEditor.layoutOverlayWidget(this);
|
||||
}
|
||||
|
||||
getPosition(): IOverlayWidgetPosition | null {
|
||||
if (this.visible) {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.TOP_CENTER
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-quick-open-widget {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./quickOutline';
|
||||
import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded
|
||||
import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { matchesFuzzy } from 'vs/base/common/filters';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { DocumentSymbol, DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes';
|
||||
import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen';
|
||||
import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings';
|
||||
|
||||
let SCOPE_PREFIX = ':';
|
||||
|
||||
export class SymbolEntry extends QuickOpenEntryGroup {
|
||||
private readonly name: string;
|
||||
private readonly type: string;
|
||||
private readonly description: string | undefined;
|
||||
private readonly range: Range;
|
||||
private readonly editor: ICodeEditor;
|
||||
private readonly decorator: IDecorator;
|
||||
|
||||
constructor(name: string, type: string, description: string | undefined, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.range = range;
|
||||
this.setHighlights(highlights);
|
||||
this.editor = editor;
|
||||
this.decorator = decorator;
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return strings.format(QuickOutlineNLS.entryAriaLabel, this.name);
|
||||
}
|
||||
|
||||
public getIcon(): string {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public getDescription(): string | undefined {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public getType(): string {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public getRange(): Range {
|
||||
return this.range;
|
||||
}
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen(context);
|
||||
}
|
||||
|
||||
return this.runPreview();
|
||||
}
|
||||
|
||||
private runOpen(_context: IEntryRunContext): boolean {
|
||||
|
||||
// Apply selection and focus
|
||||
let range = this.toSelection();
|
||||
this.editor.setSelection(range);
|
||||
this.editor.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
this.editor.focus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private runPreview(): boolean {
|
||||
|
||||
// Select Outline Position
|
||||
let range = this.toSelection();
|
||||
this.editor.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
|
||||
// Decorate if possible
|
||||
this.decorator.decorateLine(this.range, this.editor);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private toSelection(): Range {
|
||||
return new Range(
|
||||
this.range.startLineNumber,
|
||||
this.range.startColumn || 1,
|
||||
this.range.startLineNumber,
|
||||
this.range.startColumn || 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickOutlineAction extends BaseEditorQuickOpenAction {
|
||||
|
||||
constructor() {
|
||||
super(QuickOutlineNLS.quickOutlineActionInput, {
|
||||
id: 'editor.action.quickOutline',
|
||||
label: QuickOutlineNLS.quickOutlineActionLabel,
|
||||
alias: 'Go to Symbol...',
|
||||
precondition: EditorContextKeys.hasDocumentSymbolProvider,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
},
|
||||
contextMenuOpts: {
|
||||
group: 'navigation',
|
||||
order: 3
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor) {
|
||||
if (!editor.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const model = editor.getModel();
|
||||
|
||||
if (!DocumentSymbolProviderRegistry.has(model)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Resolve outline
|
||||
return getDocumentSymbols(model, true, CancellationToken.None).then((result: DocumentSymbol[]) => {
|
||||
if (result.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._run(editor, result);
|
||||
});
|
||||
}
|
||||
|
||||
private _run(editor: ICodeEditor, result: DocumentSymbol[]): void {
|
||||
this._show(this.getController(editor), {
|
||||
getModel: (value: string): QuickOpenModel => {
|
||||
return new QuickOpenModel(this.toQuickOpenEntries(editor, result, value));
|
||||
},
|
||||
|
||||
getAutoFocus: (searchValue: string): IAutoFocus => {
|
||||
// Remove any type pattern (:) from search value as needed
|
||||
if (searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
searchValue = searchValue.substr(SCOPE_PREFIX.length);
|
||||
}
|
||||
|
||||
return {
|
||||
autoFocusPrefixMatch: searchValue,
|
||||
autoFocusFirstEntry: !!searchValue
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private symbolEntry(name: string, type: string, description: string | undefined, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry {
|
||||
return new SymbolEntry(name, type, description, Range.lift(range), highlights, editor, decorator);
|
||||
}
|
||||
|
||||
private toQuickOpenEntries(editor: ICodeEditor, flattened: DocumentSymbol[], searchValue: string): SymbolEntry[] {
|
||||
const controller = this.getController(editor);
|
||||
|
||||
let results: SymbolEntry[] = [];
|
||||
|
||||
// Convert to Entries
|
||||
let normalizedSearchValue = searchValue;
|
||||
if (searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
normalizedSearchValue = normalizedSearchValue.substr(SCOPE_PREFIX.length);
|
||||
}
|
||||
|
||||
for (const element of flattened) {
|
||||
let label = strings.trim(element.name);
|
||||
|
||||
// Check for meatch
|
||||
let highlights = matchesFuzzy(normalizedSearchValue, label);
|
||||
if (highlights) {
|
||||
|
||||
// Show parent scope as description
|
||||
let description: string | undefined = undefined;
|
||||
if (element.containerName) {
|
||||
description = element.containerName;
|
||||
}
|
||||
|
||||
// Add
|
||||
results.push(this.symbolEntry(label, SymbolKinds.toCssClassName(element.kind), description, element.range, highlights, editor, controller));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort properly if actually searching
|
||||
if (searchValue) {
|
||||
if (searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
results = results.sort(this.sortScoped.bind(this, searchValue.toLowerCase()));
|
||||
} else {
|
||||
results = results.sort(this.sortNormal.bind(this, searchValue.toLowerCase()));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all type groups
|
||||
if (results.length > 0 && searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
let currentType: string | null = null;
|
||||
let currentResult: SymbolEntry | null = null;
|
||||
let typeCounter = 0;
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let result = results[i];
|
||||
|
||||
// Found new type
|
||||
if (currentType !== result.getType()) {
|
||||
|
||||
// Update previous result with count
|
||||
if (currentResult) {
|
||||
currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter));
|
||||
}
|
||||
|
||||
currentType = result.getType();
|
||||
currentResult = result;
|
||||
typeCounter = 1;
|
||||
|
||||
result.setShowBorder(i > 0);
|
||||
}
|
||||
|
||||
// Existing type, keep counting
|
||||
else {
|
||||
typeCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update previous result with count
|
||||
if (currentResult) {
|
||||
currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter));
|
||||
}
|
||||
}
|
||||
|
||||
// Mark first entry as outline
|
||||
else if (results.length > 0) {
|
||||
results[0].setGroupLabel(strings.format(QuickOutlineNLS._symbols_, results.length));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private typeToLabel(type: string, count: number): string {
|
||||
switch (type) {
|
||||
case 'module': return strings.format(QuickOutlineNLS._modules_, count);
|
||||
case 'class': return strings.format(QuickOutlineNLS._class_, count);
|
||||
case 'interface': return strings.format(QuickOutlineNLS._interface_, count);
|
||||
case 'method': return strings.format(QuickOutlineNLS._method_, count);
|
||||
case 'function': return strings.format(QuickOutlineNLS._function_, count);
|
||||
case 'property': return strings.format(QuickOutlineNLS._property_, count);
|
||||
case 'variable': return strings.format(QuickOutlineNLS._variable_, count);
|
||||
case 'var': return strings.format(QuickOutlineNLS._variable2_, count);
|
||||
case 'constructor': return strings.format(QuickOutlineNLS._constructor_, count);
|
||||
case 'call': return strings.format(QuickOutlineNLS._call_, count);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private sortNormal(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number {
|
||||
let elementAName = elementA.getLabel().toLowerCase();
|
||||
let elementBName = elementB.getLabel().toLowerCase();
|
||||
|
||||
// Compare by name
|
||||
let r = elementAName.localeCompare(elementBName);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// If name identical sort by range instead
|
||||
let elementARange = elementA.getRange();
|
||||
let elementBRange = elementB.getRange();
|
||||
return elementARange.startLineNumber - elementBRange.startLineNumber;
|
||||
}
|
||||
|
||||
private sortScoped(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number {
|
||||
|
||||
// Remove scope char
|
||||
searchValue = searchValue.substr(SCOPE_PREFIX.length);
|
||||
|
||||
// Sort by type first if scoped search
|
||||
let elementAType = elementA.getType();
|
||||
let elementBType = elementB.getType();
|
||||
let r = elementAType.localeCompare(elementBType);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Special sort when searching in scoped mode
|
||||
if (searchValue) {
|
||||
let elementAName = elementA.getLabel().toLowerCase();
|
||||
let elementBName = elementB.getLabel().toLowerCase();
|
||||
|
||||
// Compare by name
|
||||
let r = elementAName.localeCompare(elementBName);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to sort by range
|
||||
let elementARange = elementA.getRange();
|
||||
let elementBRange = elementB.getRange();
|
||||
return elementARange.startLineNumber - elementBRange.startLineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(QuickOutlineAction);
|
||||
@@ -138,6 +138,8 @@ class StandaloneTheme implements IStandaloneTheme {
|
||||
public get tokenColorMap(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
public readonly semanticHighlighting = false;
|
||||
}
|
||||
|
||||
function isBuiltinTheme(themeName: string): themeName is BuiltinTheme {
|
||||
|
||||
@@ -60,6 +60,8 @@ suite('TokenizationSupport2Adapter', () => {
|
||||
return undefined;
|
||||
},
|
||||
|
||||
semanticHighlighting: false,
|
||||
|
||||
tokenColorMap: []
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user