提交 781dcc77 编写于 作者: B Benjamin Pasero

quick access - introduce a base PickerQuickAccessProvider helper to simplify all existing pickers

上级 683e0dfc
......@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Registry } from 'vs/platform/registry/common/platform';
import { first } from 'vs/base/common/arrays';
import { startsWith } from 'vs/base/common/strings';
import { assertIsDefined } from 'vs/base/common/types';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput';
export interface IQuickAccessController {
......@@ -140,3 +141,77 @@ class QuickAccessRegistry implements IQuickAccessRegistry {
}
Registry.add(Extensions.Quickaccess, new QuickAccessRegistry());
//#region Helper class for simple picker based providers
export interface IQuickPickItemRunnable extends IQuickPickItem {
/**
* A method that will be executed when the pick item is accepted from the picker.
*/
run?: () => Promise<unknown>;
}
export abstract class PickerQuickAccessProvider<T extends IQuickPickItemRunnable> implements IQuickAccessProvider {
constructor(private prefix: string) { }
provide(picker: IQuickPick<T>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// 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 picks and support both long running and short
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token);
if (Array.isArray(res)) {
picker.items = res;
} else {
picker.busy = true;
try {
picker.items = await res;
} finally {
picker.busy = false;
}
}
this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token);
};
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Run the pick on accept and hide picker
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (typeof item?.run === 'function') {
picker.hide();
item.run();
}
}));
return disposables;
}
/**
* Returns an array of picks and separators as needed. If the picks are resolved
* long running, the provided cancellation token should be used to cancel the
* operation when the token signals this.
*
* The implementor is responsible for filtering and sorting the picks given the
* provided `filter`.
*/
protected abstract getPicks(filter: string, token: CancellationToken): Array<T | IQuickPickSeparator> | Promise<Array<T | IQuickPickSeparator>>;
}
//#endregion
......@@ -4,60 +4,33 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput';
import { PickerQuickAccessProvider, IQuickPickItemRunnable } from 'vs/platform/quickinput/common/quickAccess';
import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { prepareQuery, IPreparedQuery, ScorerCache, scoreItem, compareItemsByScore } from 'vs/base/common/fuzzyScorer';
import { URI } from 'vs/base/common/uri';
import { prepareQuery, scoreItem, compareItemsByScore } from 'vs/base/common/fuzzyScorer';
interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier {
resource: URI | undefined;
}
export abstract class BaseEditorQuickAccessProvider implements IQuickAccessProvider {
interface IEditorQuickPickItem extends IQuickPickItemWithResource, IEditorIdentifier, IQuickPickItemRunnable { }
protected abstract readonly prefix: string;
export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessProvider<IEditorQuickPickItem> {
constructor(
prefix: string,
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
@IEditorService protected readonly editorService: IEditorService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService
) {
super(prefix);
}
provide(picker: IQuickPick<IEditorQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Add all view items & filter on type
protected getPicks(filter: string): Array<IEditorQuickPickItem | IQuickPickSeparator> {
const query = prepareQuery(filter);
const scorerCache = Object.create(null);
const updatePickerItems = () => picker.items = this.getEditorPickItems(prepareQuery(picker.value.trim().substr(this.prefix.length)), scorerCache);
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Open the picked view on accept
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
picker.hide();
this.editorGroupService.getGroup(item.groupId)?.openEditor(item.editor);
}
}));
return disposables;
}
private getEditorPickItems(query: IPreparedQuery, scorerCache: ScorerCache): Array<IEditorQuickPickItem | IQuickPickSeparator> {
const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => {
if (!query.value) {
return true;
......@@ -121,7 +94,8 @@ export abstract class BaseEditorQuickAccessProvider implements IQuickAccessProvi
ariaLabel: localize('entryAriaLabel', "{0}, editor picker", editor.getName()),
description: editor.getDescription(),
iconClasses: getIconClasses(this.modelService, this.modeService, resource),
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor)
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor),
run: async () => this.editorGroupService.getGroup(groupId)?.openEditor(editor)
};
});
}
......@@ -135,7 +109,14 @@ export class ActiveGroupEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQ
static PREFIX = 'edt active ';
readonly prefix = ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX;
constructor(
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService
) {
super(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService);
}
protected doGetEditors(): IEditorIdentifier[] {
const group = this.editorGroupService.activeGroup;
......@@ -153,7 +134,14 @@ export class AllEditorsByAppearanceQuickAccess extends BaseEditorQuickAccessProv
static PREFIX = 'edt ';
readonly prefix = AllEditorsByAppearanceQuickAccess.PREFIX;
constructor(
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService
) {
super(AllEditorsByAppearanceQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService);
}
protected doGetEditors(): IEditorIdentifier[] {
const entries: IEditorIdentifier[] = [];
......@@ -177,7 +165,14 @@ export class AllEditorsByMostRecentlyUsedQuickAccess extends BaseEditorQuickAcce
static PREFIX = 'edt mru ';
readonly prefix = AllEditorsByMostRecentlyUsedQuickAccess.PREFIX;
constructor(
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService
) {
super(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, editorGroupService, editorService, modelService, modeService);
}
protected doGetEditors(): IEditorIdentifier[] {
const entries: IEditorIdentifier[] = [];
......
......@@ -3,11 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { PickerQuickAccessProvider, IQuickPickItemRunnable } from 'vs/platform/quickinput/common/quickAccess';
import { localize } from 'vs/nls';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
......@@ -16,11 +14,7 @@ import { matchesFuzzy } from 'vs/base/common/filters';
import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { withNullAsUndefined } from 'vs/base/common/types';
interface IDebugQuickPickItem extends IQuickPickItem {
run: () => Promise<unknown>;
}
export class StartDebugQuickAccessProvider implements IQuickAccessProvider {
export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IQuickPickItemRunnable> {
static PREFIX = 'debug ';
......@@ -29,40 +23,19 @@ export class StartDebugQuickAccessProvider implements IQuickAccessProvider {
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@ICommandService private readonly commandService: ICommandService,
@INotificationService private readonly notificationService: INotificationService
) { }
provide(picker: IQuickPick<IDebugQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Add all view items & filter on type
const updatePickerItems = () => picker.items = this.getDebugPickItems(picker.value.trim().substr(StartDebugQuickAccessProvider.PREFIX.length));
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Open extensions view on accept
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
picker.hide();
item.run();
}
}));
return disposables;
) {
super(StartDebugQuickAccessProvider.PREFIX);
}
private getDebugPickItems(value: string): Array<IDebugQuickPickItem | IQuickPickSeparator> {
const picks: Array<IDebugQuickPickItem | IQuickPickSeparator> = [];
protected getPicks(filter: string): (IQuickPickSeparator | IQuickPickItemRunnable)[] {
const picks: Array<IQuickPickItemRunnable | IQuickPickSeparator> = [];
const configManager = this.debugService.getConfigurationManager();
// Entries: configs
let lastGroup: string | undefined;
for (let config of configManager.getAllConfigurations()) {
const highlights = matchesFuzzy(value, config.name, true);
const highlights = matchesFuzzy(filter, config.name, true);
if (highlights) {
// Separator
......@@ -109,7 +82,7 @@ export class StartDebugQuickAccessProvider implements IQuickAccessProvider {
label,
ariaLabel: localize('entryAriaLabel', "{0}, debug", label),
description: this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? launch.name : '',
highlights: { label: withNullAsUndefined(matchesFuzzy(value, label, true)) },
highlights: { label: withNullAsUndefined(matchesFuzzy(filter, label, true)) },
run: async () => this.commandService.executeCommand('debug.addConfiguration', launch.uri.toString())
});
}
......@@ -117,4 +90,3 @@ export class StartDebugQuickAccessProvider implements IQuickAccessProvider {
return picks;
}
}
......@@ -3,25 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IQuickPickItemRunnable, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILogService } from 'vs/platform/log/common/log';
interface IInstallExtensionQuickPickItem extends IQuickPickItem {
extension?: {
name: string;
resolved?: IGalleryExtension;
}
}
export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider {
export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider<IQuickPickItemRunnable> {
static PREFIX = 'ext install ';
......@@ -31,103 +23,59 @@ export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider
@IExtensionManagementService private readonly extensionsService: IExtensionManagementService,
@INotificationService private readonly notificationService: INotificationService,
@ILogService private readonly logService: ILogService
) { }
provide(picker: IQuickPick<IInstallExtensionQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Update picker item
let extensionSearchToken: CancellationTokenSource | undefined = undefined;
const updatePickerItems = () => {
if (extensionSearchToken) {
extensionSearchToken.dispose(true);
}
extensionSearchToken = new CancellationTokenSource(token);
this.updateExtensionPickerItems(picker, extensionSearchToken.token);
};
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Open extensions view on accept
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
picker.hide();
if (item.extension) {
if (item.extension.resolved) {
this.installExtension(item.extension.resolved, item.extension.name);
} else {
this.searchExtension(item.extension.name);
}
}
}
}));
return disposables;
) {
super(InstallExtensionQuickAccessProvider.PREFIX);
}
private async updateExtensionPickerItems(picker: IQuickPick<IInstallExtensionQuickPickItem>, token: CancellationToken): Promise<void> {
const value = picker.value.trim().substr(InstallExtensionQuickAccessProvider.PREFIX.length);
protected getPicks(filter: string, token: CancellationToken): Array<IQuickPickItemRunnable | IQuickPickSeparator> | Promise<Array<IQuickPickItemRunnable | IQuickPickSeparator>> {
// Nothing typed
if (!value) {
picker.busy = false;
picker.items = [{
if (!filter) {
return [{
label: localize('type', "Type an extension name to install or search.")
}];
return;
}
const genericSearchPickItem = {
label: localize('searchFor', "Press Enter to search for extension '{0}'.", value),
extension: { name: value }
const genericSearchPickItem: IQuickPickItemRunnable = {
label: localize('searchFor', "Press Enter to search for extension '{0}'.", filter),
run: () => this.searchExtension(filter)
};
// Extension ID typed: try to find it
if (/\./.test(value)) {
picker.busy = true;
try {
const galleryResult = await this.galleryService.query({ names: [value], pageSize: 1 }, token);
if (token.isCancellationRequested) {
return; // return early if canceled
}
const galleryExtension = galleryResult.firstPage[0];
if (!galleryExtension) {
picker.items = [genericSearchPickItem];
} else {
picker.items = [{
label: localize('install', "Press Enter to install extension '{0}'.", value),
extension: {
name: value,
resolved: galleryExtension
}
}];
}
} catch (error) {
if (token.isCancellationRequested) {
return; // expected error
}
this.logService.error(error);
picker.items = [genericSearchPickItem];
} finally {
picker.busy = false;
}
if (/\./.test(filter)) {
return this.getPicksForExtensionId(filter, genericSearchPickItem, token);
}
// Extension name typed: offer to search it
else {
picker.busy = false;
picker.items = [genericSearchPickItem];
return [genericSearchPickItem];
}
}
protected async getPicksForExtensionId(filter: string, fallback: IQuickPickItemRunnable, token: CancellationToken): Promise<Array<IQuickPickItemRunnable | IQuickPickSeparator>> {
try {
const galleryResult = await this.galleryService.query({ names: [filter], pageSize: 1 }, token);
if (token.isCancellationRequested) {
return []; // return early if canceled
}
const galleryExtension = galleryResult.firstPage[0];
if (!galleryExtension) {
return [fallback];
} else {
return [{
label: localize('install', "Press Enter to install extension '{0}'.", filter),
run: () => this.installExtension(galleryExtension, filter)
}];
}
} catch (error) {
if (token.isCancellationRequested) {
return []; // expected error
}
this.logService.error(error);
return [fallback];
}
}
......@@ -145,33 +93,19 @@ export class InstallExtensionQuickAccessProvider implements IQuickAccessProvider
}
}
export class ManageExtensionsQuickAccessProvider implements IQuickAccessProvider {
export class ManageExtensionsQuickAccessProvider extends PickerQuickAccessProvider<IQuickPickItemRunnable> {
static PREFIX = 'ext ';
constructor(@IViewletService private readonly viewletService: IViewletService) { }
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;
constructor(@IViewletService private readonly viewletService: IViewletService) {
super(ManageExtensionsQuickAccessProvider.PREFIX);
}
// Have just one static picker item
picker.items = [{
label: localize('manage', "Press Enter to manage your extensions.")
protected getPicks(): Array<IQuickPickItemRunnable | IQuickPickSeparator> {
return [{
label: localize('manage', "Press Enter to manage your extensions."),
run: () => openExtensionsViewlet(this.viewletService)
}];
// Open extensions view on accept
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
picker.hide();
openExtensionsViewlet(this.viewletService);
}
}));
return disposables;
}
}
......
......@@ -5,10 +5,8 @@
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IQuickPickItemRunnable, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IViewDescriptorService, IViewsService, ViewContainer, IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
......@@ -20,12 +18,11 @@ import { matchesFuzzy } from 'vs/base/common/filters';
import { fuzzyContains } from 'vs/base/common/strings';
import { withNullAsUndefined } from 'vs/base/common/types';
interface IViewQuickPickItem extends IQuickPickItem {
interface IViewQuickPickItem extends IQuickPickItemRunnable {
containerLabel: string;
run: () => Promise<unknown>;
}
export class ViewQuickAccessProvider implements IQuickAccessProvider {
export class ViewQuickAccessProvider extends PickerQuickAccessProvider<IViewQuickPickItem> {
static PREFIX = 'view ';
......@@ -38,32 +35,10 @@ export class ViewQuickAccessProvider implements IQuickAccessProvider {
@IPanelService private readonly panelService: IPanelService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
super(ViewQuickAccessProvider.PREFIX);
}
provide(picker: IQuickPick<IViewQuickPickItem>, token: CancellationToken): IDisposable {
const disposables = new DisposableStore();
// Disable filtering & sorting, we control the results
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
// Add all view items & filter on type
const updatePickerItems = () => picker.items = this.getViewPickItems(picker.value.trim().substr(ViewQuickAccessProvider.PREFIX.length));
disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
updatePickerItems();
// Open the picked view on accept
disposables.add(picker.onDidAccept(() => {
const [item] = picker.selectedItems;
if (item) {
picker.hide();
item.run();
}
}));
return disposables;
}
private getViewPickItems(filter: string): Array<IViewQuickPickItem | IQuickPickSeparator> {
protected getPicks(filter: string): Array<IViewQuickPickItem | IQuickPickSeparator> {
const filteredViewEntries = this.doGetViewPickItems().filter(entry => {
if (!filter) {
return true;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册