提交 72a38169 编写于 作者: B Benjamin Pasero

quick access - fix missing pieces to properly enable for selfhosting

上级 57125742
......@@ -321,6 +321,8 @@ export function prepareQuery(original: string): IPreparedQuery {
let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace
if (isWindows) {
value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash
} else {
value = value.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash
}
const lowercase = value.toLowerCase();
......@@ -451,7 +453,7 @@ function doScoreItem(label: string, description: string | undefined, path: strin
return NO_ITEM_SCORE;
}
export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache, fallbackComparer = fallbackCompare): number {
export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor<T>, cache: ScorerCache): number {
const itemScoreA = scoreItem(itemA, query, fuzzy, accessor, cache);
const itemScoreB = scoreItem(itemB, query, fuzzy, accessor, cache);
......@@ -517,7 +519,16 @@ export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery
return scoreA > scoreB ? -1 : 1;
}
// 6.) scores are identical, prefer more compact matches (label and description)
// 6.) prefer matches in label over non-label matches
const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0;
const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0;
if (itemAHasLabelMatches && !itemBHasLabelMatches) {
return -1;
} else if (itemBHasLabelMatches && !itemAHasLabelMatches) {
return 1;
}
// 7.) scores are identical, prefer more compact matches (label and description)
const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor);
const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor);
if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) {
......@@ -526,7 +537,7 @@ export function compareItemsByScore<T>(itemA: T, itemB: T, query: IPreparedQuery
// 7.) at this point, scores are identical and match compactness as well
// for both items so we start to use the fallback compare
return fallbackComparer(itemA, itemB, query, accessor);
return fallbackCompare(itemA, itemB, query, accessor);
}
function computeLabelAndDescriptionMatchDistance<T>(item: T, score: IItemScore, accessor: IItemAccessor<T>): number {
......
......@@ -55,8 +55,8 @@
margin-bottom: -2px;
}
.quick-input-widget.quick-navigate-mode .quick-input-header {
/* reduce margins and paddings in quick navigate mode */
.quick-input-widget.hidden-input .quick-input-header {
/* reduce margins and paddings when input box hidden */
padding: 0;
margin-bottom: 0;
}
......@@ -132,8 +132,8 @@
margin-top: 6px;
}
.quick-input-widget.quick-navigate-mode .quick-input-list {
margin-top: 0; /* reduce margins in quick navigate mode */
.quick-input-widget.hidden-input .quick-input-list {
margin-top: 0; /* reduce margins when input box hidden */
}
.quick-input-list .monaco-list {
......
......@@ -364,7 +364,7 @@ class QuickInput extends Disposable implements IQuickInput {
readonly onDispose = this.onDisposeEmitter.event;
public dispose(): void {
dispose(): void {
this.hide();
this.onDisposeEmitter.fire();
......@@ -409,6 +409,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _customButtonLabel: string | undefined;
private _customButtonHover: string | undefined;
private _quickNavigate: IQuickNavigateConfiguration | undefined;
private _hideInput: boolean | undefined;
get quickNavigate() {
return this._quickNavigate;
......@@ -461,10 +462,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
set items(items: Array<T | IQuickPickSeparator>) {
this._items = items;
this.itemsUpdated = true;
if (items.length === 0) {
// quick-navigate requires at least 1 item
this._quickNavigate = undefined;
}
this.update();
}
......@@ -622,14 +619,23 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update();
}
public inputHasFocus(): boolean {
inputHasFocus(): boolean {
return this.visible ? this.ui.inputBox.hasFocus() : false;
}
public focusOnInput() {
focusOnInput() {
this.ui.inputBox.setFocus();
}
get hideInput() {
return !!this._hideInput;
}
set hideInput(hideInput: boolean) {
this._hideInput = hideInput;
this.update();
}
onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
......@@ -729,7 +735,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.onDidAcceptEmitter.fire({ inBackground: false });
}));
this.visibleDisposables.add(this.ui.onDidCustom(() => {
this.onDidCustomEmitter.fire(undefined);
this.onDidCustomEmitter.fire();
}));
this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => {
if (this.activeItemsUpdated) {
......@@ -814,10 +820,16 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
return false;
});
if (wasTriggerKeyPressed && this.activeItems[0]) {
this._selectedItems = [this.activeItems[0]];
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
this.onDidAcceptEmitter.fire({ inBackground: false });
if (wasTriggerKeyPressed) {
if (this.activeItems[0]) {
this._selectedItems = [this.activeItems[0]];
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
this.onDidAcceptEmitter.fire({ inBackground: false });
}
// Unset quick navigate after press. It is only valid once
// and should not result in any behaviour change afterwards
// if the picker remains open because there was no active item
this._quickNavigate = undefined;
}
});
}
......@@ -826,12 +838,21 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
if (!this.visible) {
return;
}
const isQuickNavigating = !!this._quickNavigate && this._items.length > 0; // quick nav requires at least 1 item
dom.toggleClass(this.ui.container, 'quick-navigate-mode', isQuickNavigating);
const ok = this.ok === 'default' ? this.canSelectMany : this.ok;
const visibilities: Visibilities = this.canSelectMany ?
{ title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: !isQuickNavigating, progressBar: !isQuickNavigating, visibleCount: true, count: true, ok, list: true, message: !!this.validationMessage, customButton: this.customButton } :
{ title: !!this.title || !!this.step, description: !!this.description, inputBox: !isQuickNavigating, progressBar: !isQuickNavigating, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok };
const hideInput = !!this._hideInput && this._items.length > 0; // do not allow to hide input without items
dom.toggleClass(this.ui.container, 'hidden-input', hideInput);
const visibilities: Visibilities = {
title: !!this.title || !!this.step,
description: !!this.description,
checkAll: this.canSelectMany,
inputBox: !hideInput,
progressBar: !hideInput,
visibleCount: true,
count: this.canSelectMany,
ok: this.ok === 'default' ? this.canSelectMany : this.ok,
list: true,
message: !!this.validationMessage,
customButton: this.customButton
};
this.ui.setVisibilities(visibilities);
super.update();
if (this.ui.inputBox.value !== this.value) {
......@@ -853,15 +874,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.list.sortByLabel = this.sortByLabel;
if (this.itemsUpdated) {
this.itemsUpdated = false;
const previousItemCount = this.ui.list.getElementsCount();
this.ui.list.setElements(this.items);
this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
this.ui.count.setCount(this.ui.list.getCheckedCount());
if (this._autoFocusSecondEntry && previousItemCount === 0) {
if (this._autoFocusSecondEntry) {
this.ui.list.focus(QuickInputListFocus.Second);
this._autoFocusSecondEntry = false;
this._autoFocusSecondEntry = false; // only valid once, then unset
} else {
this.trySelectFirst();
}
......@@ -994,7 +1014,7 @@ class InputBox extends QuickInput implements IInputBox {
this._value = value;
this.onDidValueChangeEmitter.fire(value);
}));
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined)));
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()));
this.valueSelectionUpdated = true;
}
super.show();
......@@ -1048,10 +1068,12 @@ export class QuickInputController extends Disposable {
private parentElement: HTMLElement;
private styles: IQuickInputStyles;
private onShowEmitter = new Emitter<void>();
readonly onShow = this.onShowEmitter.event;
private onHideEmitter = new Emitter<void>();
public onShow = this.onShowEmitter.event;
public onHide = this.onHideEmitter.event;
readonly onHide = this.onHideEmitter.event;
constructor(private options: IQuickInputOptions) {
super();
......@@ -1526,7 +1548,7 @@ export class QuickInputController extends Disposable {
}
}
public hide(focusLost?: boolean) {
hide(focusLost?: boolean) {
const controller = this.controller;
if (controller) {
this.controller = null;
......@@ -1560,7 +1582,14 @@ export class QuickInputController extends Disposable {
}
}
async accept() {
async accept(keyMods: IKeyMods = { alt: false, ctrlCmd: false }) {
// When accepting the item programmatically, it is important that
// we update `keyMods` either from the provided set or unset it
// because the accept did not happen from mouse or keyboard
// interaction on the list itself
this.keyMods.alt = keyMods.alt;
this.keyMods.ctrlCmd = keyMods.ctrlCmd;
this.onDidAcceptEmitter.fire();
}
......@@ -1592,7 +1621,7 @@ export class QuickInputController extends Disposable {
}
}
public applyStyles(styles: IQuickInputStyles) {
applyStyles(styles: IQuickInputStyles) {
this.styles = styles;
this.updateStyles();
}
......
......@@ -317,6 +317,18 @@ export class QuickInputList {
this._onLeave.fire();
}
}));
this.disposables.push(this.list.onContextMenu(e => {
if (typeof e.index === 'number') {
e.browserEvent.preventDefault();
// we want to treat a context menu event as
// a gesture to open the item at the index
// since we do not have any context menu
// this enables for example macOS to Ctrl-
// click on an item to open it.
this.list.setSelection([e.index]);
}
}));
}
@memoize
......@@ -440,7 +452,10 @@ export class QuickInputList {
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)!));
if (items.length > 0) {
this.list.reveal(this.list.getFocus()[0]);
const focused = this.list.getFocus()[0];
if (typeof focused === 'number') {
this.list.reveal(focused);
}
}
}
......@@ -526,7 +541,7 @@ export class QuickInputList {
}
const focused = this.list.getFocus()[0];
if (focused) {
if (typeof focused === 'number') {
this.list.reveal(focused);
}
}
......
......@@ -264,6 +264,13 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
inputHasFocus(): boolean;
focusOnInput(): void;
/**
* Hides the input box from the picker UI. This is typically used
* in combination with quick-navigation where no search UI should
* be presented.
*/
hideInput: boolean;
}
export interface IInputBox extends IQuickInput {
......
......@@ -8,6 +8,7 @@ import * as scorer from 'vs/base/common/fuzzyScorer';
import { URI } from 'vs/base/common/uri';
import { basename, dirname, sep } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
class ResourceAccessorClass implements scorer.IItemAccessor<URI> {
......@@ -49,8 +50,8 @@ function scoreItem<T>(item: T, query: string, fuzzy: boolean, accessor: scorer.I
return scorer.scoreItem(item, scorer.prepareQuery(query), fuzzy, accessor, cache);
}
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache, fallbackComparer?: (itemA: T, itemB: T, query: scorer.IPreparedQuery, accessor: scorer.IItemAccessor<T>) => number): number {
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache, fallbackComparer as any);
function compareItemsByScore<T>(itemA: T, itemB: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor<T>, cache: scorer.ScorerCache): number {
return scorer.compareItemsByScore(itemA, itemB, scorer.prepareQuery(query), fuzzy, accessor, cache);
}
const NullAccessor = new NullAccessorClass();
......@@ -279,6 +280,19 @@ suite('Fuzzy Scorer', () => {
assert.ok(!res.score);
});
test('scoreItem - match if using slash or backslash (local, remote resource)', function () {
const localResource = URI.file('abcde/super/duper');
const remoteResource = URI.from({ scheme: Schemas.vscodeRemote, path: 'abcde/super/duper' });
for (const resource of [localResource, remoteResource]) {
let res = scoreItem(resource, 'abcde\\super\\duper', true, ResourceAccessor, cache);
assert.ok(res.score);
res = scoreItem(resource, 'abcde/super/duper', true, ResourceAccessor, cache);
assert.ok(res.score);
}
});
test('compareItemsByScore - identity', function () {
const resourceA = URI.file('/some/path/fileA.txt');
const resourceB = URI.file('/some/path/other/fileB.txt');
......@@ -509,33 +523,13 @@ suite('Fuzzy Scorer', () => {
assert.equal(res[2], resourceC);
});
test('compareFilesByScore - allow to provide fallback sorter (bug #31591)', function () {
const resourceA = URI.file('virtual/vscode.d.ts');
const resourceB = URI.file('vscode/src/vs/vscode.d.ts');
let query = 'vscode';
test('compareFilesByScore - prefer matches in label over description if scores are otherwise equal', function () {
const resourceA = URI.file('parts/quick/arrow-left-dark.svg');
const resourceB = URI.file('parts/quickopen/quickopen.ts');
let res = [resourceA, resourceB].sort((r1, r2) => {
return compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => {
if (r1 as any /* TS fail */ === resourceA) {
return -1;
}
let query = 'partsquick';
return 1;
});
});
assert.equal(res[0], resourceA);
assert.equal(res[1], resourceB);
res = [resourceB, resourceA].sort((r1, r2) => {
return compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache, (r1, r2, query, ResourceAccessor) => {
if (r1 as any /* TS fail */ === resourceB) {
return -1;
}
return 1;
});
});
let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor, cache));
assert.equal(res[0], resourceB);
assert.equal(res[1], resourceA);
});
......
......@@ -36,7 +36,7 @@ export namespace InspectTokensNLS {
}
export namespace GoToLineNLS {
export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line...");
export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line/Column...");
}
export namespace QuickHelpNLS {
......
......@@ -41,7 +41,7 @@ export class GotoLineAction extends EditorAction {
super({
id: 'editor.action.gotoLine',
label: GoToLineNLS.gotoLineActionLabel,
alias: 'Go to Line...',
alias: 'Go to Line/Column...',
precondition: undefined,
kbOpts: {
kbExpr: EditorContextKeys.focus,
......
......@@ -332,7 +332,11 @@ export class ElectronMainService implements IElectronMainService {
}
async closeWindow(windowId: number | undefined): Promise<void> {
const window = this.windowById(windowId);
this.closeWindowById(windowId, windowId);
}
async closeWindowById(currentWindowId: number | undefined, targetWindowId?: number | undefined): Promise<void> {
const window = this.windowById(targetWindowId);
if (window) {
return window.win.close();
}
......
......@@ -74,6 +74,7 @@ export interface IElectronService {
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise<void>;
reload(options?: { disableExtensions?: boolean }): Promise<void>;
closeWindow(): Promise<void>;
closeWindowById(windowId: number): Promise<void>;
quit(): Promise<void>;
// Development
......
......@@ -25,7 +25,12 @@ export enum TriggerAction {
/**
* Update the results of the picker.
*/
REFRESH_PICKER
REFRESH_PICKER,
/**
* Remove the item from the picker.
*/
REMOVE_ITEM
}
export interface IPickerQuickAccessItem extends IQuickPickItem {
......@@ -211,6 +216,14 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
case TriggerAction.REFRESH_PICKER:
updatePickerItems();
break;
case TriggerAction.REMOVE_ITEM:
const index = picker.items.indexOf(item);
if (index !== -1) {
const items = picker.items.slice();
items.splice(index, 1);
picker.items = items;
}
break;
}
}
}
......
......@@ -5,19 +5,28 @@
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor, IQuickAccessOptions } from 'vs/platform/quickinput/common/quickAccess';
import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor, IQuickAccessOptions, DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess';
import { Registry } from 'vs/platform/registry/common/platform';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { once } from 'vs/base/common/functional';
interface IInternalQuickAccessOptions extends IQuickAccessOptions {
/**
* Internal option to not rewrite the filter value at all but use it as is.
*/
preserveFilterValue?: boolean;
}
export class QuickAccessController extends Disposable implements IQuickAccessController {
private readonly registry = Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
private readonly mapProviderToDescriptor = new Map<IQuickAccessProviderDescriptor, IQuickAccessProvider>();
private lastActivePicker: IQuickPick<IQuickPickItem> | undefined = undefined;
private lastPickerInputs = new Map<IQuickAccessProviderDescriptor, string>();
private readonly lastAcceptedPickerValues = new Map<IQuickAccessProviderDescriptor, string>();
private visibleQuickAccess: { picker: IQuickPick<IQuickPickItem>, descriptor: IQuickAccessProviderDescriptor | undefined, value: string } | undefined = undefined;
constructor(
@IQuickInputService private readonly quickInputService: IQuickInputService,
......@@ -26,84 +35,118 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
super();
}
show(value = '', options?: IQuickAccessOptions): void {
const disposables = new DisposableStore();
// Hide any previous picker if any
this.lastActivePicker?.hide();
show(value = '', options?: IInternalQuickAccessOptions): void {
// Find provider for the value to show
const [provider, descriptor] = this.getOrInstantiateProvider(value);
// Rewrite the value to the last input of the provider if told so
// Also adjust the `inputSelection` so that the user can type over
if (descriptor && options?.inputUseLastValue) {
const lastInput = this.lastPickerInputs.get(descriptor);
if (lastInput) {
value = lastInput;
options = {
...options,
inputSelection: { start: descriptor.prefix.length, end: value.length }
};
// Rewrite the filter value based on certain rules unless disabled
if (descriptor && !options?.preserveFilterValue) {
let newValue: string | undefined = undefined;
// If we have a visible provider with a value, take it's filter value but
// rewrite to new provider prefix in case they differ
if (this.visibleQuickAccess?.descriptor && this.visibleQuickAccess.descriptor !== descriptor) {
const newValueCandidateWithoutPrefix = this.visibleQuickAccess.value.substr(this.visibleQuickAccess.descriptor.prefix.length);
if (newValueCandidateWithoutPrefix) {
newValue = `${descriptor.prefix}${newValueCandidateWithoutPrefix}`;
}
}
// If the new provider wants to preserve the filter, take it's last remembered value
// If the new provider wants to define the filter, take it as is
if (!newValue) {
const defaultFilterValue = provider?.defaultFilterValue;
if (defaultFilterValue === DefaultQuickAccessFilterValue.LAST) {
newValue = this.lastAcceptedPickerValues.get(descriptor);
} else if (typeof defaultFilterValue === 'string') {
newValue = `${descriptor.prefix}${defaultFilterValue}`;
}
}
if (typeof newValue === 'string') {
value = newValue;
}
}
// Return early if quick access is already showing and
// simply take over the filter value and select it for
// the user to be able to type over
if (descriptor && this.visibleQuickAccess?.descriptor === descriptor) {
this.visibleQuickAccess.picker.value = value;
this.visibleQuickAccess.picker.valueSelection = [descriptor.prefix.length, value.length];
return;
}
// Create a picker for the provider to use with the initial value
// and adjust the filtering to exclude the prefix from filtering
const disposables = new DisposableStore();
const picker = disposables.add(this.quickInputService.createQuickPick());
picker.placeholder = descriptor?.placeholder;
picker.value = value;
picker.quickNavigate = options?.quickNavigateConfiguration;
picker.hideInput = !!picker.quickNavigate && !this.visibleQuickAccess; // only hide input if there was no picker opened already
picker.autoFocusSecondEntry = !!options?.quickNavigateConfiguration || !!options?.autoFocus?.autoFocusSecondEntry;
picker.valueSelection = options?.inputSelection ? [options.inputSelection.start, options.inputSelection.end] : [value.length, value.length];
picker.valueSelection = [descriptor?.prefix.length ?? 0, value.length]; // always allow to type over value after prefix
picker.contextKey = descriptor?.contextKey;
picker.filterValue = (value: string) => value.substring(descriptor ? descriptor.prefix.length : 0);
// Remember as last active picker and clean up once picker get's disposed
this.lastActivePicker = picker;
// Register listeners
const cancellationToken = this.registerPickerListeners(disposables, picker, provider, descriptor, value);
// Ask provider to fill the picker as needed if we have one
if (provider) {
disposables.add(provider.provide(picker, cancellationToken));
}
// Finally, show the picker. This is important because a provider
// may not call this and then our disposables would leak that rely
// on the onDidHide event.
picker.show();
}
private registerPickerListeners(disposables: DisposableStore, picker: IQuickPick<IQuickPickItem>, provider: IQuickAccessProvider | undefined, descriptor: IQuickAccessProviderDescriptor | undefined, value: string): CancellationToken {
// Remember as last visible picker and clean up once picker get's disposed
const visibleQuickAccess = this.visibleQuickAccess = { picker, descriptor, value };
disposables.add(toDisposable(() => {
if (picker === this.lastActivePicker) {
this.lastActivePicker = undefined;
if (visibleQuickAccess === this.visibleQuickAccess) {
this.visibleQuickAccess = undefined;
}
}));
// Create a cancellation token source that is valid as long as the
// picker has not been closed without picking an item
const cts = disposables.add(new CancellationTokenSource());
once(picker.onDidHide)(() => {
if (picker.selectedItems.length === 0) {
cts.cancel();
}
// Start to dispose once picker hides
disposables.dispose();
});
// Whenever the value changes, check if the provider has
// changed and if so - re-create the picker from the beginning
disposables.add(picker.onDidChangeValue(value => {
const [providerForValue] = this.getOrInstantiateProvider(value);
if (providerForValue !== provider) {
this.show(value);
this.show(value, { preserveFilterValue: true } /* do not rewrite value from user typing! */);
} else {
visibleQuickAccess.value = value; // remember the value in our visible one
}
}));
// Remember picker input for future use when accepting
if (descriptor) {
disposables.add(picker.onDidAccept(() => {
this.lastPickerInputs.set(descriptor, picker.value);
this.lastAcceptedPickerValues.set(descriptor, picker.value);
}));
}
// Ask provider to fill the picker as needed if we have one
if (provider) {
disposables.add(provider.provide(picker, cts.token));
}
// Create a cancellation token source that is valid as long as the
// picker has not been closed without picking an item
const cts = disposables.add(new CancellationTokenSource());
once(picker.onDidHide)(() => {
if (picker.selectedItems.length === 0) {
cts.cancel();
}
// Finally, show the picker. This is important because a provider
// may not call this and then our disposables would leak that rely
// on the onDidHide event.
picker.show();
// Start to dispose once picker hides
disposables.dispose();
});
return cts.token;
}
private getOrInstantiateProvider(value: string): [IQuickAccessProvider | undefined, IQuickAccessProviderDescriptor | undefined] {
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
......@@ -154,8 +154,8 @@ export class QuickInputService extends Themable implements IQuickInputService {
this.controller.navigate(next, quickNavigate);
}
accept() {
return this.controller.accept();
accept(keyMods?: IKeyMods) {
return this.controller.accept(keyMods);
}
back() {
......
......@@ -12,16 +12,6 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
export interface IQuickAccessOptions {
/**
* Allows to control the part of text in the input field that should be selected.
*/
inputSelection?: { start: number; end: number; };
/**
* Allows to seed the input with the value that was previously used.
*/
inputUseLastValue?: boolean;
/**
* Allows to enable quick navigate support in quick input.
*/
......@@ -41,8 +31,32 @@ export interface IQuickAccessController {
show(value?: string, options?: IQuickAccessOptions): void;
}
export enum DefaultQuickAccessFilterValue {
/**
* Keep the value as it is given to quick access.
*/
PRESERVE = 0,
/**
* Use the value that was used last time something was accepted from the picker.
*/
LAST = 1
}
export interface IQuickAccessProvider {
/**
* Allows to set a default filter value when the provider opens. This can be:
* - `undefined` to not specify any default value
* - `DefaultFilterValues.PRESERVE` to use the value that was last typed
* - `string` for the actual value to use
*
* Note: the default filter will only be used if quick access was opened with
* the exact prefix of the provider. Otherwise the filter value is preserved.
*/
readonly defaultFilterValue?: string | DefaultQuickAccessFilterValue;
/**
* Called whenever a prefix was typed into quick pick that matches the provider.
*
......
......@@ -6,7 +6,7 @@
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput';
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInputButton, IInputBox, QuickPickInput, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput';
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
export * from 'vs/base/parts/quickinput/common/quickInput';
......@@ -84,8 +84,11 @@ export interface IQuickInputService {
/**
* Accept the selected item.
*
* @param keyMods allows to override the state of key
* modifiers that should be present when invoking.
*/
accept(): Promise<void>;
accept(keyMods?: IKeyMods): Promise<void>;
/**
* Cancels quick input and closes it.
......
......@@ -41,7 +41,7 @@ class InspectContextKeysAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
const disposables = new DisposableStore();
const stylesheet = createStyleSheet();
......@@ -85,8 +85,6 @@ class InspectContextKeysAction extends Action {
dispose(disposables);
}, null, disposables);
return Promise.resolve();
}
}
......
......@@ -31,13 +31,11 @@ class KeybindingsReferenceAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
const url = isLinux ? this.productService.keyboardShortcutsUrlLinux : isMacintosh ? this.productService.keyboardShortcutsUrlMac : this.productService.keyboardShortcutsUrlWin;
if (url) {
this.openerService.open(URI.parse(url));
}
return Promise.resolve();
}
}
......@@ -56,12 +54,10 @@ class OpenDocumentationUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.documentationUrl) {
this.openerService.open(URI.parse(this.productService.documentationUrl));
}
return Promise.resolve();
}
}
......@@ -80,12 +76,10 @@ class OpenIntroductoryVideosUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.introductoryVideosUrl) {
this.openerService.open(URI.parse(this.productService.introductoryVideosUrl));
}
return Promise.resolve();
}
}
......@@ -104,12 +98,10 @@ class OpenTipsAndTricksUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.tipsAndTricksUrl) {
this.openerService.open(URI.parse(this.productService.tipsAndTricksUrl));
}
return Promise.resolve();
}
}
......@@ -151,12 +143,10 @@ class OpenTwitterUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.twitterUrl) {
this.openerService.open(URI.parse(this.productService.twitterUrl));
}
return Promise.resolve();
}
}
......@@ -175,12 +165,10 @@ class OpenRequestFeatureUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.requestFeatureUrl) {
this.openerService.open(URI.parse(this.productService.requestFeatureUrl));
}
return Promise.resolve();
}
}
......@@ -199,7 +187,7 @@ class OpenLicenseUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.licenseUrl) {
if (language) {
const queryArgChar = this.productService.licenseUrl.indexOf('?') > 0 ? '&' : '?';
......@@ -208,8 +196,6 @@ class OpenLicenseUrlAction extends Action {
this.openerService.open(URI.parse(this.productService.licenseUrl));
}
}
return Promise.resolve();
}
}
......@@ -228,7 +214,7 @@ class OpenPrivacyStatementUrlAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.productService.privacyStatementUrl) {
if (language) {
const queryArgChar = this.productService.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?';
......@@ -237,8 +223,6 @@ class OpenPrivacyStatementUrlAction extends Action {
this.openerService.open(URI.parse(this.productService.privacyStatementUrl));
}
}
return Promise.resolve();
}
}
......
......@@ -645,9 +645,8 @@ export class IncreaseViewSizeAction extends BaseResizeViewAction {
super(id, label, layoutService);
}
run(): Promise<boolean> {
async run(): Promise<void> {
this.resizePart(BaseResizeViewAction.RESIZE_INCREMENT);
return Promise.resolve(true);
}
}
......@@ -665,9 +664,8 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction {
super(id, label, layoutService);
}
run(): Promise<boolean> {
async run(): Promise<void> {
this.resizePart(-BaseResizeViewAction.RESIZE_INCREMENT);
return Promise.resolve(true);
}
}
......
......@@ -36,7 +36,7 @@ export const inRecentFilesPickerContextKey = 'inRecentFilesPicker';
abstract class BaseOpenRecentAction extends Action {
private removeFromRecentlyOpened: IQuickInputButton = {
private readonly removeFromRecentlyOpened: IQuickInputButton = {
iconClass: 'codicon-close',
tooltip: nls.localize('remove', "Remove from Recently Opened")
};
......@@ -124,7 +124,7 @@ abstract class BaseOpenRecentAction extends Action {
const pick = await this.quickInputService.pick(picks, {
contextKey: inRecentFilesPickerContextKey,
activeItem: [...workspacePicks, ...filePicks][autoFocusSecondEntry ? 1 : 0],
placeHolder: isMacintosh ? nls.localize('openRecentPlaceHolderMac', "Select to open (hold Cmd-key to open in new window)") : nls.localize('openRecentPlaceHolder', "Select to open (hold Ctrl-key to open in new window)"),
placeHolder: isMacintosh ? nls.localize('openRecentPlaceHolderMac', "Select to open (hold Cmd-key to force new window or Alt-key for same window)") : nls.localize('openRecentPlaceHolder', "Select to open (hold Ctrl-key to force new window or Alt-key for same window)"),
matchOnDescription: true,
onKeyMods: mods => keyMods = mods,
quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined,
......@@ -135,7 +135,7 @@ abstract class BaseOpenRecentAction extends Action {
});
if (pick) {
return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd });
return this.hostService.openWindow([pick.openable], { forceNewWindow: keyMods?.ctrlCmd, forceReuseWindow: keyMods?.alt });
}
}
}
......
......@@ -112,11 +112,10 @@ export class CloseWorkspaceAction extends Action {
super(id, label);
}
run(): Promise<void> {
async run(): Promise<void> {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close."));
return Promise.resolve(undefined);
return;
}
return this.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: this.environmentService.configuration.remoteAuthority });
......
......@@ -286,7 +286,7 @@ class SwitchSideBarViewAction extends Action {
const activeViewlet = this.viewletService.getActiveViewlet();
if (!activeViewlet) {
return Promise.resolve();
return;
}
let targetViewletId: string | undefined;
for (let i = 0; i < pinnedViewletIds.length; i++) {
......
......@@ -292,11 +292,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
localize('closeGroupAction', "Close"),
'codicon-close',
true,
() => {
this.accessor.removeGroup(this);
return Promise.resolve(true);
}));
async () => this.accessor.removeGroup(this)));
const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id);
containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : undefined });
......@@ -807,7 +803,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Guard against invalid inputs
if (!editor) {
return Promise.resolve(null);
return null;
}
// Editor opening event allows for prevention
......@@ -822,13 +818,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return withUndefinedAsNull(await this.doOpenEditor(editor, options));
}
private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise<IEditorPane | undefined> {
private async doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise<IEditorPane | undefined> {
// Guard against invalid inputs. Disposed inputs
// should never open because they emit no events
// e.g. to indicate dirty changes.
if (editor.isDisposed()) {
return Promise.resolve(undefined);
return;
}
// Determine options
......
......@@ -140,9 +140,16 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
];
})(),
trigger: async () => {
await this.editorGroupService.getGroup(groupId)?.closeEditor(editor, { preserveFocus: true });
const group = this.editorGroupService.getGroup(groupId);
if (group) {
await group.closeEditor(editor, { preserveFocus: true });
if (!group.isOpened(editor)) {
return TriggerAction.REMOVE_ITEM;
}
}
return TriggerAction.REFRESH_PICKER;
return TriggerAction.NO_ACTION;
},
accept: (keyMods, event) => this.editorGroupService.getGroup(groupId)?.openEditor(editor, { preserveFocus: event.inBackground }),
};
......
......@@ -454,7 +454,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
const props: IStatusbarEntry = {
text,
tooltip: nls.localize('gotoLine', "Go to Line"),
tooltip: nls.localize('gotoLine', "Go to Line/Column"),
command: 'workbench.action.gotoLine'
};
......
......@@ -39,6 +39,17 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.alternativeAcceptSelectedQuickOpenItem',
weight: KeybindingWeight.WorkbenchContrib,
when: inQuickOpenContext,
primary: 0,
handler: accessor => {
const quickInputService = accessor.get(IQuickInputService);
return quickInputService.accept({ ctrlCmd: true, alt: false });
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.focusQuickOpen',
weight: KeybindingWeight.WorkbenchContrib,
......
......@@ -179,10 +179,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
show(prefix?: string, options?: IShowOptions): Promise<void> {
if (this.useNewExperimentalVersion) {
this.quickInputService.quickAccess.show(prefix, {
...options,
inputUseLastValue: !prefix && this.preserveInput
});
this.quickInputService.quickAccess.show(prefix, options);
return Promise.resolve();
}
......@@ -727,7 +724,7 @@ class EditorHistoryHandler {
// Sort by score and provide a fallback sorter that keeps the
// recency of items in case the score for items is the same
.sort((e1, e2) => compareItemsByScore(e1, e2, query, false, accessor, this.scorerCache, () => -1));
.sort((e1, e2) => compareItemsByScore(e1, e2, query, false, accessor, this.scorerCache));
}
}
......
......@@ -609,7 +609,6 @@ export class TreeView extends Disposable implements ITreeView {
return tree.expand(element, false);
}));
}
return Promise.resolve(undefined);
}
setSelection(items: ITreeItem[]): void {
......@@ -625,11 +624,10 @@ export class TreeView extends Disposable implements ITreeView {
}
}
reveal(item: ITreeItem): Promise<void> {
async reveal(item: ITreeItem): Promise<void> {
if (this.tree) {
return Promise.resolve(this.tree.reveal(item));
return this.tree.reveal(item);
}
return Promise.resolve();
}
private refreshing: boolean = false;
......@@ -689,11 +687,11 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
return !!this.treeView.dataProvider && (element.collapsibleState !== TreeItemCollapsibleState.None);
}
getChildren(element: ITreeItem): ITreeItem[] | Promise<ITreeItem[]> {
async getChildren(element: ITreeItem): Promise<ITreeItem[]> {
if (this.treeView.dataProvider) {
return this.withProgress(this.treeView.dataProvider.getChildren(element));
}
return Promise.resolve([]);
return [];
}
}
......
......@@ -32,6 +32,7 @@ export interface IWorkbenchQuickOpenConfiguration {
},
quickOpen: {
enableExperimentalNewVersion: boolean;
preserveInput: boolean;
}
};
}
......
......@@ -194,10 +194,6 @@ export class ShowViewletAction extends Action {
export class CollapseAction extends Action {
constructor(tree: AsyncDataTree<any, any, any> | AbstractTree<any, any, any>, enabled: boolean, clazz?: string) {
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, () => {
tree.collapseAll();
return Promise.resolve(undefined);
});
super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, async () => tree.collapseAll());
}
}
......@@ -697,15 +697,13 @@ export class ChoiceAction extends Action {
private readonly _keepOpen: boolean;
constructor(id: string, choice: IPromptChoice) {
super(id, choice.label, undefined, true, () => {
super(id, choice.label, undefined, true, async () => {
// Pass to runner
choice.run();
// Emit Event
this._onDidRun.fire();
return Promise.resolve();
});
this._keepOpen = !!choice.keepOpen;
......
......@@ -59,5 +59,5 @@ Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessPro
ctor: GotoLineQuickAccessProvider,
prefix: AbstractGotoLineQuickAccessProvider.PREFIX,
placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. 42:5 for line 42 and column 5)."),
helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line"), needsEditor: true }]
helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line/Column"), needsEditor: true }]
});
......@@ -20,6 +20,9 @@ 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 { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen';
export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider {
......@@ -34,6 +37,14 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce
protected get activeTextEditorControl(): IEditor | undefined { return this.editorService.activeTextEditorControl; }
get defaultFilterValue(): DefaultQuickAccessFilterValue | undefined {
if (this.configuration.preserveInput) {
return DefaultQuickAccessFilterValue.LAST;
}
return undefined;
}
constructor(
@IEditorService private readonly editorService: IEditorService,
@IMenuService private readonly menuService: IMenuService,
......@@ -42,11 +53,20 @@ export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAcce
@IKeybindingService keybindingService: IKeybindingService,
@ICommandService commandService: ICommandService,
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService notificationService: INotificationService
@INotificationService notificationService: INotificationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super({ showAlias: !Language.isDefaultVariant() }, instantiationService, keybindingService, commandService, telemetryService, notificationService);
}
private get configuration() {
const commandPaletteConfig = this.configurationService.getValue<IWorkbenchQuickOpenConfiguration>().workbench.commandPalette;
return {
preserveInput: commandPaletteConfig.preserveInput
};
}
protected async getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick>> {
// wait for extensions registration or 800ms once
......
......@@ -175,7 +175,7 @@ export class ShowAllCommandsAction extends Action {
const restoreInput = config.workbench?.commandPalette?.preserveInput === true;
if (this.configurationService.getValue(ENABLE_EXPERIMENTAL_VERSION_CONFIG) === true) {
this.quickInputService.quickAccess.show(ALL_COMMANDS_PREFIX, { inputUseLastValue: restoreInput });
this.quickInputService.quickAccess.show(ALL_COMMANDS_PREFIX);
} else {
// Show with last command palette input if any and configured
......
......@@ -29,7 +29,7 @@ export const GOTO_LINE_PREFIX = ':';
export class GotoLineAction extends QuickOpenAction {
static readonly ID = 'workbench.action.gotoLine';
static readonly LABEL = nls.localize('gotoLine', "Go to Line...");
static readonly LABEL = nls.localize('gotoLine', "Go to Line/Column...");
constructor(actionId: string, actionLabel: string,
@IQuickOpenService quickOpenService: IQuickOpenService,
......
......@@ -61,7 +61,7 @@ const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = {
export class GotoSymbolAction extends QuickOpenAction {
static readonly ID = 'workbench.action.gotoSymbol';
static readonly LABEL = nls.localize('gotoSymbol', "Go to Symbol in File...");
static readonly LABEL = nls.localize('gotoSymbol', "Go to Symbol in Editor...");
constructor(actionId: string, actionLabel: string, @IQuickOpenService quickOpenService: IQuickOpenService) {
super(actionId, actionLabel, GOTO_SYMBOL_PREFIX, quickOpenService);
......
......@@ -30,11 +30,11 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAllCommandsActi
registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_G,
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }
}), 'Go to Line...');
}), 'Go to Line/Column...');
registry.registerWorkbenchAction(SyncActionDescriptor.create(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O
}), 'Go to Symbol in File...');
}), 'Go to Symbol in Editor...');
const inViewsPickerContextKey = 'inViewsPicker';
const inViewsPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inViewsPickerContextKey));
......@@ -91,7 +91,7 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
{
prefix: GOTO_LINE_PREFIX,
needsEditor: true,
description: env.isMacintosh ? nls.localize('gotoLineDescriptionMac', "Go to Line") : nls.localize('gotoLineDescriptionWin', "Go to Line")
description: env.isMacintosh ? nls.localize('gotoLineDescriptionMac', "Go to Line/Column") : nls.localize('gotoLineDescriptionWin', "Go to Line/Column")
},
]
)
......@@ -107,12 +107,12 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
{
prefix: GOTO_SYMBOL_PREFIX,
needsEditor: true,
description: nls.localize('gotoSymbolDescription', "Go to Symbol in File")
description: nls.localize('gotoSymbolDescription', "Go to Symbol in Editor")
},
{
prefix: GOTO_SYMBOL_PREFIX + SCOPE_PREFIX,
needsEditor: true,
description: nls.localize('gotoSymbolDescriptionScoped', "Go to Symbol in File by Category")
description: nls.localize('gotoSymbolDescriptionScoped', "Go to Symbol in Editor by Category")
}
]
)
......@@ -170,7 +170,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
group: '4_symbol_nav',
command: {
id: 'workbench.action.gotoSymbol',
title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...")
title: nls.localize({ key: 'miGotoSymbolInEditor', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in Editor...")
},
order: 1
});
......
......@@ -39,6 +39,8 @@ import { Schemas } from 'vs/base/common/network';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ResourceMap } from 'vs/base/common/map';
import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess';
import { DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess';
import { IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen';
interface IAnythingQuickPickItem extends IPickerQuickAccessItem {
resource: URI | undefined;
......@@ -81,6 +83,14 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
}
}(this);
get defaultFilterValue(): DefaultQuickAccessFilterValue | undefined {
if (this.configuration.preserveInput) {
return DefaultQuickAccessFilterValue.LAST;
}
return undefined;
}
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ISearchService private readonly searchService: ISearchService,
......@@ -102,14 +112,17 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
private get configuration() {
const editorConfig = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor;
const searchConfig = this.configurationService.getValue<IWorkbenchSearchConfiguration>();
const searchConfig = this.configurationService.getValue<IWorkbenchSearchConfiguration>().search;
const quickOpenConfig = this.configurationService.getValue<IWorkbenchQuickOpenConfiguration>().workbench.quickOpen;
return {
openEditorPinned: !editorConfig.enablePreviewFromQuickOpen,
openSideBySideDirection: editorConfig.openSideBySideDirection,
includeSymbols: searchConfig.search.quickOpen.includeSymbols,
includeHistory: searchConfig.search.quickOpen.includeHistory,
shortAutoSaveDelay: this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY
includeSymbols: searchConfig.quickOpen.includeSymbols,
includeHistory: searchConfig.quickOpen.includeHistory,
historyFilterSortOrder: searchConfig.quickOpen.history.filterSortOrder,
shortAutoSaveDelay: this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY,
preserveInput: quickOpenConfig.preserveInput
};
}
......@@ -268,7 +281,12 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
editorHistoryPicks.push(editorHistoryPick);
}
return editorHistoryPicks.sort((editorA, editorB) => compareItemsByScore(editorA, editorB, query, false, editorHistoryScorerAccessor, this.pickState.scorerCache, () => -1));
// Return without sorting if settings tell to sort by recency
if (this.configuration.historyFilterSortOrder === 'recency') {
return editorHistoryPicks;
}
return editorHistoryPicks.sort((editorA, editorB) => compareItemsByScore(editorA, editorB, query, false, editorHistoryScorerAccessor, this.pickState.scorerCache));
}
//#endregion
......@@ -353,9 +371,21 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
this.getRelativePathFileResults(query, token)
]);
// Return quickly if no relative results are present
if (!relativePathFileResults) {
return fileSearchResults.results.map(result => result.resource);
}
// Otherwise, make sure to filter relative path results from
// the search results to prevent duplicates
const relativePathFileResultsMap = new ResourceMap<boolean>();
for (const relativePathFileResult of relativePathFileResults) {
relativePathFileResultsMap.set(relativePathFileResult, true);
}
return [
...fileSearchResults.results.map(result => result.resource),
...(relativePathFileResults || [])
...fileSearchResults.results.filter(result => !relativePathFileResultsMap.has(result.resource)).map(result => result.resource),
...relativePathFileResults
];
}
......@@ -541,7 +571,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
if (!URI.isUri(resourceOrEditor)) {
this.historyService.remove(resourceOrEditor);
return TriggerAction.REFRESH_PICKER;
return TriggerAction.REMOVE_ITEM;
}
}
......
......@@ -29,7 +29,7 @@ import { IListService, WorkbenchListFocusContextKey, WorkbenchObjectTree } from
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { Registry } from 'vs/platform/registry/common/platform';
import { defaultQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor, ENABLE_EXPERIMENTAL_VERSION_CONFIG } from 'vs/workbench/browser/quickopen';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
......@@ -58,6 +58,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess';
import { AnythingQuickAccessProvider } from 'vs/workbench/contrib/search/browser/anythingQuickAccess';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true);
registerSingleton(ISearchHistoryService, SearchHistoryService, true);
......@@ -479,22 +480,28 @@ class ShowAllSymbolsAction extends Action {
actionId: string,
actionLabel: string,
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
@ICodeEditorService private readonly editorService: ICodeEditorService
@ICodeEditorService private readonly editorService: ICodeEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super(actionId, actionLabel);
}
async run(): Promise<void> {
let prefix = ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX;
let inputSelection: { start: number; end: number; } | undefined = undefined;
const editor = this.editorService.getFocusedCodeEditor();
const word = editor && getSelectionSearchString(editor);
if (word) {
prefix = prefix + word;
inputSelection = { start: 1, end: word.length + 1 };
}
if (this.configurationService.getValue(ENABLE_EXPERIMENTAL_VERSION_CONFIG) === true) {
this.quickInputService.quickAccess.show(ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX);
} else {
let prefix = ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX;
let inputSelection: { start: number; end: number; } | undefined = undefined;
const editor = this.editorService.getFocusedCodeEditor();
const word = editor && getSelectionSearchString(editor);
if (word) {
prefix = prefix + word;
inputSelection = { start: 1, end: word.length + 1 };
}
this.quickOpenService.show(prefix, { inputSelection });
this.quickOpenService.show(prefix, { inputSelection });
}
}
}
......@@ -738,6 +745,16 @@ configurationRegistry.registerConfiguration({
description: nls.localize('search.quickOpen.includeHistory', "Whether to include results from recently opened files in the file results for Quick Open."),
default: true
},
'search.quickOpen.history.filterSortOrder': {
'type': 'string',
'enum': ['default', 'recency'],
'default': 'default',
'enumDescriptions': [
nls.localize('filterSortOrder.default', 'History entries are sorted by relevance based on the filter value used. More relevant entries appear first.'),
nls.localize('filterSortOrder.recency', 'History entries are sorted by recency. More recently opened entries appear first.')
],
'description': nls.localize('filterSortOrder', "Controls sorting order of editor history in quick open when filtering.")
},
'search.followSymlinks': {
type: 'boolean',
description: nls.localize('search.followSymlinks', "Controls whether to follow symlinks while searching."),
......
......@@ -24,6 +24,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search';
import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { getSelectionSearchString } from 'vs/editor/contrib/find/findController';
import { withNullAsUndefined } from 'vs/base/common/types';
interface ISymbolQuickPickItem extends IPickerQuickAccessItem {
resource: URI | undefined;
......@@ -51,12 +54,24 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider<ISymbo
private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService));
get defaultFilterValue(): string | undefined {
// Prefer the word under the cursor in the active editor as default filter
const editor = this.codeEditorService.getFocusedCodeEditor();
if (editor) {
return withNullAsUndefined(getSelectionSearchString(editor));
}
return undefined;
}
constructor(
@ILabelService private readonly labelService: ILabelService,
@IOpenerService private readonly openerService: IOpenerService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IInstantiationService private readonly instantiationService: IInstantiationService
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
super(SymbolsQuickAccessProvider.PREFIX, { canAcceptInBackground: true });
}
......
......@@ -77,6 +77,9 @@ export interface IWorkbenchSearchConfigurationProperties extends ISearchConfigur
quickOpen: {
includeSymbols: boolean;
includeHistory: boolean;
history: {
filterSortOrder: 'default' | 'recency'
}
};
}
......
......@@ -55,7 +55,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
return TriggerAction.NO_ACTION;
case 1:
terminal.dispose(true);
return TriggerAction.REFRESH_PICKER;
return TriggerAction.REMOVE_ITEM;
}
return TriggerAction.NO_ACTION;
......
......@@ -34,10 +34,8 @@ export class CloseCurrentWindowAction extends Action {
super(id, label);
}
run(): Promise<boolean> {
async run(): Promise<void> {
this.electronService.closeWindow();
return Promise.resolve(true);
}
}
......@@ -91,10 +89,8 @@ export class ZoomInAction extends BaseZoomAction {
super(id, label, configurationService);
}
run(): Promise<boolean> {
async run(): Promise<void> {
this.setConfiguredZoomLevel(webFrame.getZoomLevel() + 1);
return Promise.resolve(true);
}
}
......@@ -111,10 +107,8 @@ export class ZoomOutAction extends BaseZoomAction {
super(id, label, configurationService);
}
run(): Promise<boolean> {
async run(): Promise<void> {
this.setConfiguredZoomLevel(webFrame.getZoomLevel() - 1);
return Promise.resolve(true);
}
}
......@@ -131,10 +125,8 @@ export class ZoomResetAction extends BaseZoomAction {
super(id, label, configurationService);
}
run(): Promise<boolean> {
async run(): Promise<void> {
this.setConfiguredZoomLevel(0);
return Promise.resolve(true);
}
}
......@@ -160,8 +152,8 @@ export class ReloadWindowWithExtensionsDisabledAction extends Action {
export abstract class BaseSwitchWindow extends Action {
private closeWindowAction: IQuickInputButton = {
iconClass: 'action-remove-from-recently-opened',
private readonly closeWindowAction: IQuickInputButton = {
iconClass: 'codicon-close',
tooltip: nls.localize('close', "Close Window")
};
......@@ -204,7 +196,7 @@ export abstract class BaseSwitchWindow extends Action {
placeHolder,
quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined,
onDidTriggerItemButton: async context => {
await this.electronService.closeWindow();
await this.electronService.closeWindowById(context.item.payload);
context.removeItem();
}
});
......
......@@ -207,6 +207,7 @@ export class TestElectronService implements IElectronService {
async relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; } | undefined): Promise<void> { }
async reload(): Promise<void> { }
async closeWindow(): Promise<void> { }
async closeWindowById(): Promise<void> { }
async quit(): Promise<void> { }
async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise<void> { }
async toggleDevTools(): Promise<void> { }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册