Merge from vscode 073a24de05773f2261f89172987002dc0ae2f1cd (#9711)

This commit is contained in:
Anthony Dresser
2020-03-24 00:24:15 -07:00
committed by GitHub
parent 29741d684e
commit 89ef1b0c2e
226 changed files with 6161 additions and 3288 deletions

View File

@@ -22,6 +22,10 @@ interface IEditorLineDecoration {
overviewRulerDecorationId: string;
}
export interface IEditorNavigationQuickAccessOptions {
canAcceptInBackground?: boolean;
}
/**
* A reusable quick access provider for the editor with support
* for adding decorations for navigating in the currently active file
@@ -29,11 +33,16 @@ interface IEditorLineDecoration {
*/
export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider {
constructor(protected options?: IEditorNavigationQuickAccessOptions) { }
//#region Provider methods
provide(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Apply options if any
picker.canAcceptInBackground = !!this.options?.canAcceptInBackground;
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
@@ -71,11 +80,11 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState());
}));
once(token.onCancellationRequested)(() => {
disposables.add(once(token.onCancellationRequested)(() => {
if (lastKnownEditorViewState) {
editor.restoreViewState(lastKnownEditorViewState);
}
});
}));
}
// Clean up decorations on dispose
@@ -110,10 +119,12 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
*/
protected abstract provideWithoutTextEditor(picker: IQuickPick<IQuickPickItem>, token: CancellationToken): IDisposable;
protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean }): void {
protected gotoLocation(editor: IEditor, options: { range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean, preserveFocus?: boolean }): void {
editor.setSelection(options.range);
editor.revealRangeInCenter(options.range, ScrollType.Smooth);
editor.focus();
if (!options.preserveFocus) {
editor.focus();
}
}
protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined {

View File

@@ -6,11 +6,13 @@
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IEditor, ScrollType } from 'vs/editor/common/editorCommon';
import { IRange } from 'vs/editor/common/core/range';
import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess';
import { IPosition } from 'vs/editor/common/core/position';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
interface IGotoLineQuickPickItem extends IQuickPickItem, Partial<IPosition> { }
@@ -18,6 +20,10 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
static PREFIX = ':';
constructor() {
super({ canAcceptInBackground: true });
}
protected provideWithoutTextEditor(picker: IQuickPick<IGotoLineQuickPickItem>): IDisposable {
const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line.");
@@ -31,16 +37,18 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
const disposables = new DisposableStore();
// Goto line once picked
disposables.add(picker.onDidAccept(() => {
disposables.add(picker.onDidAccept(event => {
const [item] = picker.selectedItems;
if (item) {
if (!this.isValidLineNumber(editor, item.lineNumber)) {
return;
}
this.gotoLocation(editor, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods });
this.gotoLocation(editor, { range: this.toRange(item.lineNumber, item.column), keyMods: picker.keyMods, preserveFocus: event.inBackground });
picker.hide();
if (!event.inBackground) {
picker.hide();
}
}
}));
@@ -75,6 +83,18 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
updatePickerAndEditor();
disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor()));
// Adjust line number visibility as needed
const codeEditor = getCodeEditor(editor);
if (codeEditor) {
const options = codeEditor.getOptions();
const lineNumbers = options.get(EditorOption.lineNumbers);
if (lineNumbers.renderType === RenderLineNumbersType.Relative) {
codeEditor.updateOptions({ lineNumbers: 'on' });
disposables.add(toDisposable(() => codeEditor.updateOptions({ lineNumbers: 'relative' })));
}
}
return disposables;
}

View File

@@ -6,25 +6,26 @@
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 { DisposableStore, IDisposable, Disposable, toDisposable } 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 { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions } 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';
import { assign } from 'vs/base/common/objects';
interface IGotoSymbolQuickPickItem extends IQuickPickItem {
export interface IGotoSymbolQuickPickItem extends IQuickPickItem {
kind: SymbolKind,
index: number,
score?: FuzzyScore;
range?: { decoration: IRange, selection: IRange },
range?: { decoration: IRange, selection: IRange }
}
export interface IGotoSymbolQuickAccessProviderOptions {
export interface IGotoSymbolQuickAccessProviderOptions extends IEditorNavigationQuickAccessOptions {
openSideBySideDirection: () => undefined | 'right' | 'down'
}
@@ -34,8 +35,8 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
static SCOPE_PREFIX = ':';
static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`;
constructor(private options?: IGotoSymbolQuickAccessProviderOptions) {
super();
constructor(protected options?: IGotoSymbolQuickAccessProviderOptions) {
super(assign(options, { canAcceptInBackground: true }));
}
protected provideWithoutTextEditor(picker: IQuickPick<IGotoSymbolQuickPickItem>): IDisposable {
@@ -72,32 +73,58 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
picker.items = [{ label, index: 0, kind: SymbolKind.String }];
picker.ariaLabel = label;
// Listen to changes to the registry and see if eventually
// Wait for 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
(async () => {
const result = await this.waitForLanguageSymbolRegistry(model, disposables);
if (!result || token.isCancellationRequested) {
return;
}
disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token));
})();
return disposables;
}
protected async waitForLanguageSymbolRegistry(model: ITextModel, disposables: DisposableStore): Promise<boolean> {
if (DocumentSymbolProviderRegistry.has(model)) {
return true;
}
let symbolProviderRegistryPromiseResolve: (res: boolean) => void;
const symbolProviderRegistryPromise = new Promise<boolean>(resolve => symbolProviderRegistryPromiseResolve = resolve);
// Resolve promise when registry knows model
const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => {
if (DocumentSymbolProviderRegistry.has(model)) {
symbolProviderListener.dispose();
disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token));
symbolProviderRegistryPromiseResolve(true);
}
}));
return disposables;
// Resolve promise when we get disposed too
disposables.add(toDisposable(() => symbolProviderRegistryPromiseResolve(false)));
return symbolProviderRegistryPromise;
}
private doProvideWithEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick<IGotoSymbolQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Goto symbol once picked
disposables.add(picker.onDidAccept(() => {
disposables.add(picker.onDidAccept(event => {
const [item] = picker.selectedItems;
if (item && item.range) {
this.gotoLocation(editor, { range: item.range.selection, keyMods: picker.keyMods });
this.gotoLocation(editor, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground });
picker.hide();
if (!event.inBackground) {
picker.hide();
}
}
}));
@@ -128,7 +155,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
// Collect symbol picks
picker.busy = true;
try {
const items = await this.getSymbolPicks(symbolsPromise, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token);
const items = await this.doGetSymbolPicks(symbolsPromise, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token);
if (token.isCancellationRequested) {
return;
}
@@ -167,7 +194,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
return disposables;
}
private async getSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, filter: string, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
protected async doGetSymbolPicks(symbolsPromise: Promise<DocumentSymbol[]>, filter: string, token: CancellationToken): Promise<Array<IGotoSymbolQuickPickItem | IQuickPickSeparator>> {
const symbols = await symbolsPromise;
if (token.isCancellationRequested) {
return [];
@@ -340,7 +367,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
return result;
}
private async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
protected async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
const model = await OutlineModel.create(document, token);
if (token.isCancellationRequested) {
return [];

View File

@@ -50,7 +50,8 @@ suite('SmartSelect', () => {
setup(() => {
const configurationService = new TestConfigurationService();
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService()));
const dialogService = new TestDialogService();
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService);
mode = new MockJSMode();
});

View File

@@ -604,6 +604,12 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
useShadows: false,
openController: { shouldOpen: () => false },
mouseSupport: false,
ariaRole: 'listbox',
ariaProvider: {
getRole: () => 'option',
getSetSize: (_: CompletionItem, _index: number, listLength: number) => listLength,
getPosInSet: (_: CompletionItem, index: number) => index,
},
accessibilityProvider: {
getAriaLabel: (item: CompletionItem) => {
const textLabel = typeof item.completion.label === 'string' ? item.completion.label : item.completion.label.name;