From 7d637a175bcd653389ce363e08564a7b41417e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Tue, 17 Aug 2021 14:47:29 +0200 Subject: [PATCH] Refactor to hide VS Code interfaces behind core services (#9727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor to hide VS Code interfaces behind core services Signed-off-by: Thomas Mäder --- examples/api-tests/src/file-search.spec.js | 26 +- .../browser/common-frontend-contribution.ts | 5 +- .../browser/frontend-application-module.ts | 14 +- .../core/src/browser/quick-input/index.ts | 4 +- .../quick-input/quick-access-contribution.ts | 23 - .../src/browser/quick-input/quick-access.ts | 75 ++++ .../quick-command-frontend-contribution.ts | 9 +- .../quick-input/quick-command-service.ts | 97 +++- .../quick-input/quick-editor-service.ts | 33 -- .../quick-help-frontend-contribution.ts | 29 -- .../browser/quick-input/quick-help-service.ts | 73 ++- .../quick-input-frontend-contribution.ts | 2 +- .../quick-input/quick-input-service.ts | 91 ++-- .../browser/quick-input/quick-view-service.ts | 67 ++- .../src/browser/debug-frontend-module.ts | 2 +- .../src/browser/debug-prefix-configuration.ts | 49 +-- .../editor/src/browser/editor-contribution.ts | 11 +- .../src/browser/editor-frontend-module.ts | 7 +- .../src/browser/quick-editor-service.ts} | 63 ++- .../browser/file-search-frontend-module.ts | 2 +- .../src/browser/quick-file-open.ts | 161 +++---- .../git/src/browser/git-quick-open-service.ts | 27 +- packages/monaco/src/browser/monaco-command.ts | 22 +- .../src/browser/monaco-editor-provider.ts | 6 +- .../src/browser/monaco-frontend-module.ts | 25 +- .../browser/monaco-quick-access-registry.ts | 100 +++++ .../browser/monaco-quick-command-service.ts | 133 ------ .../src/browser/monaco-quick-help-service.ts | 106 ----- .../src/browser/monaco-quick-input-service.ts | 415 ++++++++++++++---- .../src/browser/monaco-quick-view-service.ts | 97 ---- .../src/browser/workspace-symbol-command.ts | 57 +-- packages/monaco/src/typings/monaco/index.d.ts | 77 +--- .../plugin-ext/src/common/plugin-api-rpc.ts | 4 +- .../main/browser/plugin-ext-deploy-command.ts | 5 +- .../src/main/browser/quick-open-main.ts | 8 +- packages/task/src/browser/quick-open-task.ts | 63 +-- .../task/src/browser/task-frontend-module.ts | 2 +- .../src/browser/terminal-frontend-module.ts | 2 +- .../browser/terminal-quick-open-service.ts | 55 +-- 39 files changed, 1009 insertions(+), 1038 deletions(-) delete mode 100644 packages/core/src/browser/quick-input/quick-access-contribution.ts create mode 100644 packages/core/src/browser/quick-input/quick-access.ts delete mode 100644 packages/core/src/browser/quick-input/quick-editor-service.ts delete mode 100644 packages/core/src/browser/quick-input/quick-help-frontend-contribution.ts rename packages/{monaco/src/browser/monaco-quick-editor-service.ts => editor/src/browser/quick-editor-service.ts} (53%) create mode 100644 packages/monaco/src/browser/monaco-quick-access-registry.ts delete mode 100644 packages/monaco/src/browser/monaco-quick-command-service.ts delete mode 100644 packages/monaco/src/browser/monaco-quick-help-service.ts delete mode 100644 packages/monaco/src/browser/monaco-quick-view-service.ts diff --git a/examples/api-tests/src/file-search.spec.js b/examples/api-tests/src/file-search.spec.js index 196abe1e59b92..6a225c23cb788 100644 --- a/examples/api-tests/src/file-search.spec.js +++ b/examples/api-tests/src/file-search.spec.js @@ -32,30 +32,26 @@ describe('file-search', function () { it('should compare two quick-open-items by `label`', () => { - /** @type monaco.quickInput.IAnythingQuickPickItem */ - const a = { label: 'a', resource: new Uri.default('a') }; - /** @type monaco.quickInput.IAnythingQuickPickItem */ - const b = { label: 'a', resource: new Uri.default('b') }; + /** @type import ('@theia/file-search/lib/browser/quick-file-open').FileQuickPickItem*/ + const a = { label: 'a', uri: new Uri.default('b') }; + /** @type import ('@theia/file-search/lib/browser/quick-file-open').FileQuickPickItem*/ + const b = { label: 'b', uri: new Uri.default('a') }; assert.equal(quickFileOpenService['compareItems'](a, b), 1, 'a should be before b'); assert.equal(quickFileOpenService['compareItems'](b, a), -1, 'a should be before b'); assert.equal(quickFileOpenService['compareItems'](a, a), 0, 'items should be equal'); - - assert.equal(quickFileOpenService['compareItems'](a, b, 'label'), 1, 'a should be before b'); - assert.equal(quickFileOpenService['compareItems'](b, a, 'label'), -1, 'a should be before b'); - assert.equal(quickFileOpenService['compareItems'](a, a, 'label'), 0, 'items should be equal'); }); it('should compare two quick-open-items by `uri`', () => { - /** @type monaco.quickInput.IAnythingQuickPickItem */ - const a = { label: 'a', resource: new Uri.default('a') }; - /** @type monaco.quickInput.IAnythingQuickPickItem */ - const b = { label: 'a', resource: new Uri.default('b') }; + /** @type import ('@theia/file-search/lib/browser/quick-file-open').FileQuickPickItem*/ + const a = { label: 'a', uri: new Uri.default('a') }; + /** @type import ('@theia/file-search/lib/browser/quick-file-open').FileQuickPickItem*/ + const b = { label: 'a', uri: new Uri.default('b') }; - assert.equal(quickFileOpenService['compareItems'](a, b, 'resource'), 1, 'a should be before b'); - assert.equal(quickFileOpenService['compareItems'](b, a, 'resource'), -1, 'a should be before b'); - assert.equal(quickFileOpenService['compareItems'](a, a, 'resource'), 0, 'items should be equal'); + assert.equal(quickFileOpenService['compareItems'](a, b), 1, 'a should be before b'); + assert.equal(quickFileOpenService['compareItems'](b, a), -1, 'a should be before b'); + assert.equal(quickFileOpenService['compareItems'](a, a), 0, 'items should be equal'); }); }); diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index 3abcb7012c0ab..a800bb0b202a6 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -304,9 +304,6 @@ export class CommonFrontendContribution implements FrontendApplicationContributi @inject(StorageService) protected readonly storageService: StorageService; - @inject(QuickViewService) @optional() - protected readonly quickView: QuickViewService; - @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; @@ -762,7 +759,7 @@ export class CommonFrontendContribution implements FrontendApplicationContributi }); commandRegistry.registerCommand(CommonCommands.OPEN_VIEW, { - execute: () => this.quickInputService?.open(this.quickView?.prefix) + execute: () => this.quickInputService?.open(QuickViewService.PREFIX) }); commandRegistry.registerCommand(CommonCommands.SELECT_COLOR_THEME, { diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index 3136c9f621d52..b868bbacc4791 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -96,13 +96,14 @@ import { keytarServicePath, KeytarService } from '../common/keytar-protocol'; import { CredentialsService, CredentialsServiceImpl } from './credentials-service'; import { ContributionFilterRegistry, ContributionFilterRegistryImpl } from '../common/contribution-filter'; import { QuickCommandFrontendContribution } from './quick-input/quick-command-frontend-contribution'; -import { QuickHelpFrontendContribution } from './quick-input/quick-help-frontend-contribution'; +import { QuickHelpService } from './quick-input/quick-help-service'; import { QuickPickService, quickPickServicePath } from '../common/quick-pick-service'; import { QuickPickServiceImpl, QuickInputFrontendContribution } from './quick-input'; -import { QuickAccessContribution } from './quick-input/quick-access-contribution'; +import { QuickAccessContribution } from './quick-input/quick-access'; +import { QuickCommandService } from './quick-input/quick-command-service'; export { bindResourceProvider, bindMessageService, bindPreferenceService }; @@ -233,12 +234,14 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo ); bind(QuickCommandFrontendContribution).toSelf().inSingletonScope(); - [CommandContribution, KeybindingContribution, MenuContribution, QuickAccessContribution].forEach(serviceIdentifier => + [CommandContribution, KeybindingContribution, MenuContribution].forEach(serviceIdentifier => bind(serviceIdentifier).toService(QuickCommandFrontendContribution) ); + bind(QuickCommandService).toSelf().inSingletonScope(); + bind(QuickAccessContribution).toService(QuickCommandService); - bind(QuickHelpFrontendContribution).toSelf().inSingletonScope(); - bind(QuickAccessContribution).toService(QuickHelpFrontendContribution); + bind(QuickHelpService).toSelf().inSingletonScope(); + bind(QuickAccessContribution).toService(QuickHelpService); bind(QuickPickService).to(QuickPickServiceImpl).inSingletonScope().onActivation(({ container }, quickPickService: QuickPickService) => { WebSocketConnectionProvider.createProxy(container, quickPickServicePath, quickPickService); @@ -319,6 +322,7 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo return container.get(ViewContainer); }); + bind(QuickViewService).toSelf().inSingletonScope(); bind(QuickAccessContribution).toService(QuickViewService); bind(DialogOverlayService).toSelf().inSingletonScope(); diff --git a/packages/core/src/browser/quick-input/index.ts b/packages/core/src/browser/quick-input/index.ts index e720b9e53447f..62586db3a949d 100644 --- a/packages/core/src/browser/quick-input/index.ts +++ b/packages/core/src/browser/quick-input/index.ts @@ -15,10 +15,8 @@ ********************************************************************************/ export * from './quick-command-frontend-contribution'; export * from './quick-command-service'; -export * from './quick-editor-service'; -export * from './quick-help-frontend-contribution'; export * from './quick-help-service'; -export * from './quick-access-contribution'; +export * from './quick-access'; export * from './quick-input-frontend-contribution'; export * from './quick-input-service'; export * from './quick-view-service'; diff --git a/packages/core/src/browser/quick-input/quick-access-contribution.ts b/packages/core/src/browser/quick-input/quick-access-contribution.ts deleted file mode 100644 index a810f22e6ea10..0000000000000 --- a/packages/core/src/browser/quick-input/quick-access-contribution.ts +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -export const QuickAccessContribution = Symbol('QuickAccessContribution'); -/** - * The quick access contribution should be implemented to register custom quick access provider. - */ -export interface QuickAccessContribution { - registerQuickAccessProvider(): void; -} diff --git a/packages/core/src/browser/quick-input/quick-access.ts b/packages/core/src/browser/quick-input/quick-access.ts new file mode 100644 index 0000000000000..d3c2587ebc642 --- /dev/null +++ b/packages/core/src/browser/quick-input/quick-access.ts @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { CancellationToken, Disposable } from '../../common'; +import { QuickPicks } from './quick-input-service'; + +export const QuickAccessContribution = Symbol('QuickAccessContribution'); +/** + * Bind this contribution in order to register quick access providers with the + * QuickAccessRegistry at startup + */ +export interface QuickAccessContribution { + registerQuickAccessProvider(): void; +} + +export interface QuickAccessProvider { + getPicks(filter: string, token: CancellationToken): QuickPicks | Promise; + reset?(): void; +} + +export interface QuickAccessProviderHelp { + prefix?: string; + description: string; + needsEditor: boolean; +} + +export interface QuickAccessProviderDescriptor { + /** + * return an instance of QuickAccessProvider. Implementers are free to return that same instance multiple times + */ + readonly getInstance: () => QuickAccessProvider; + /** + * The prefix for quick access picker to use the provider for. + */ + readonly prefix: string; + /** + * A placeholder to use for the input field when the provider is active. + * This will also be read out by screen readers and thus helps for + * accessibility. + */ + readonly placeholder?: string; + /** + * Help entries for this quick access provider + */ + readonly helpEntries: QuickAccessProviderHelp[]; + /** + * A context key that will be set automatically when this quick access is being shown + */ + readonly contextKey?: string; +} + +export const QuickAccessRegistry = Symbol('QuickAccessRegistry'); + +/** + * A registry for quick access providers. + */ +export interface QuickAccessRegistry { + registerQuickAccessProvider(provider: QuickAccessProviderDescriptor): Disposable; + getQuickAccessProviders(): QuickAccessProviderDescriptor[]; + getQuickAccessProvider(prefix: string): QuickAccessProviderDescriptor | undefined; + clear(): void; +} diff --git a/packages/core/src/browser/quick-input/quick-command-frontend-contribution.ts b/packages/core/src/browser/quick-input/quick-command-frontend-contribution.ts index 02131987fbfac..9d68bef051472 100644 --- a/packages/core/src/browser/quick-input/quick-command-frontend-contribution.ts +++ b/packages/core/src/browser/quick-input/quick-command-frontend-contribution.ts @@ -17,12 +17,11 @@ import { injectable, inject, optional } from 'inversify'; import { CommandRegistry, CommandContribution, MenuContribution, MenuModelRegistry } from '../../common'; import { KeybindingRegistry, KeybindingContribution } from '../keybinding'; import { CommonMenus } from '../common-frontend-contribution'; -import { QuickInputService } from './quick-input-service'; import { CLEAR_COMMAND_HISTORY, quickCommand, QuickCommandService } from './quick-command-service'; -import { QuickAccessContribution } from './quick-access-contribution'; +import { QuickInputService } from './quick-input-service'; @injectable() -export class QuickCommandFrontendContribution implements CommandContribution, KeybindingContribution, MenuContribution, QuickAccessContribution { +export class QuickCommandFrontendContribution implements CommandContribution, KeybindingContribution, MenuContribution { @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; @@ -59,8 +58,4 @@ export class QuickCommandFrontendContribution implements CommandContribution, Ke keybinding: 'ctrlcmd+shift+p' }); } - - registerQuickAccessProvider(): void { - this.quickCommandService?.registerQuickAccessProvider(); - } } diff --git a/packages/core/src/browser/quick-input/quick-command-service.ts b/packages/core/src/browser/quick-input/quick-command-service.ts index 8b0d468fb04c1..afcb6006d0a22 100644 --- a/packages/core/src/browser/quick-input/quick-command-service.ts +++ b/packages/core/src/browser/quick-input/quick-command-service.ts @@ -15,11 +15,13 @@ ********************************************************************************/ import { inject, injectable } from 'inversify'; -import { Disposable, Command, CommandRegistry } from '../../common'; +import { KeybindingRegistry } from '../keybinding'; +import { Disposable, Command, CommandRegistry, CancellationToken } from '../../common'; import { ContextKeyService } from '../context-key-service'; import { CorePreferences } from '../core-preferences'; -import { QuickAccessContribution } from './quick-access-contribution'; -import { QuickInputService } from './quick-input-service'; +import { QuickAccessContribution, QuickAccessProvider, QuickAccessRegistry } from './quick-access'; +import { filterItems, QuickPickItem, QuickPicks } from './quick-input-service'; +import { KeySequence } from '../keys'; export const quickCommand: Command = { id: 'workbench.action.showCommands' @@ -31,7 +33,8 @@ export const CLEAR_COMMAND_HISTORY: Command = { }; @injectable() -export class QuickCommandService extends QuickInputService implements QuickAccessContribution { +export class QuickCommandService implements QuickAccessContribution, QuickAccessProvider { + static PREFIX = '>'; @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService; @@ -42,12 +45,96 @@ export class QuickCommandService extends QuickInputService implements QuickAcces @inject(CorePreferences) protected readonly corePreferences: CorePreferences; + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; + + @inject(KeybindingRegistry) + protected readonly keybindingRegistry: KeybindingRegistry; + // The list of exempted commands not to be displayed in the recently used list. readonly exemptedCommands: Command[] = [ CLEAR_COMMAND_HISTORY, ]; - registerQuickAccessProvider(): void { } + private recentItems: QuickPickItem[] = []; + private otherItems: QuickPickItem[] = []; + + registerQuickAccessProvider(): void { + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: QuickCommandService.PREFIX, + placeholder: '', + helpEntries: [{ description: 'Quick Command', needsEditor: false }] + }); + } + + reset(): void { + const { recent, other } = this.getCommands(); + this.recentItems = []; + this.otherItems = []; + this.recentItems.push(...recent.map(command => this.toItem(command))); + this.otherItems.push(...other.map(command => this.toItem(command))); + } + + getPicks(filter: string, token: CancellationToken): QuickPicks { + const items: QuickPicks = []; + if (this.recentItems.length === 0 && this.otherItems.length === 0) { + this.reset(); + } + const recentItems = filterItems(this.recentItems.slice(), filter); + const otherItems = filterItems(this.otherItems.slice(), filter); + + if (recentItems.length > 0) { + items.push({ type: 'separator', label: 'recently used' }, ...recentItems); + } + + if (otherItems.length > 0) { + if (recentItems.length > 0) { + items.push({ type: 'separator', label: 'other commands' }); + } + items.push(...otherItems); + } + return items; + } + + private toItem(command: Command): QuickPickItem { + const label = (command.category) ? `${command.category}: ` + command.label! : command.label!; + const iconClasses = this.getItemIconClasses(command); + const activeElement = window.document.activeElement as HTMLElement; + + return { + label, + iconClasses, + alwaysShow: !!this.commandRegistry.getActiveHandler(command.id), + keySequence: this.getKeybinding(command), + execute: () => { + activeElement.focus({ preventScroll: true }); + this.commandRegistry.executeCommand(command.id); + this.commandRegistry.addRecentCommand(command); + } + }; + } + + private getKeybinding(command: Command): KeySequence | undefined { + const keybindings = this.keybindingRegistry.getKeybindingsForCommand(command.id); + if (!keybindings || keybindings.length === 0) { + return undefined; + } + + try { + return this.keybindingRegistry.resolveKeybinding(keybindings[0]); + } catch (error) { + return undefined; + } + } + + private getItemIconClasses(command: Command): string[] | undefined { + const toggledHandler = this.commandRegistry.getToggledHandler(command.id); + if (toggledHandler) { + return ['fa fa-check']; + } + return undefined; + } protected readonly contexts = new Map(); pushCommandContext(commandId: string, when: string): Disposable { diff --git a/packages/core/src/browser/quick-input/quick-editor-service.ts b/packages/core/src/browser/quick-input/quick-editor-service.ts deleted file mode 100644 index 4067f3a9cef34..0000000000000 --- a/packages/core/src/browser/quick-input/quick-editor-service.ts +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { inject, injectable } from 'inversify'; -import { LabelProvider } from '../label-provider'; -import { OpenerService } from '../opener-service'; -import { QuickAccessContribution } from './quick-access-contribution'; -import { QuickInputService } from './quick-input-service'; - -@injectable() -export class QuickEditorService extends QuickInputService implements QuickAccessContribution { - - @inject(OpenerService) - protected readonly openerService: OpenerService; - - @inject(LabelProvider) - protected readonly labelProvider: LabelProvider; - - registerQuickAccessProvider(): void { } -} diff --git a/packages/core/src/browser/quick-input/quick-help-frontend-contribution.ts b/packages/core/src/browser/quick-input/quick-help-frontend-contribution.ts deleted file mode 100644 index c9b55cd58e63c..0000000000000 --- a/packages/core/src/browser/quick-input/quick-help-frontend-contribution.ts +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ -import { injectable, inject, optional } from 'inversify'; -import { QuickHelpService } from './quick-help-service'; -import { QuickAccessContribution } from './quick-access-contribution'; - -@injectable() -export class QuickHelpFrontendContribution implements QuickAccessContribution { - - @inject(QuickHelpService) @optional() - protected readonly quickHelpService: QuickHelpService; - - registerQuickAccessProvider(): void { - this.quickHelpService?.registerQuickAccessProvider(); - } -} diff --git a/packages/core/src/browser/quick-input/quick-help-service.ts b/packages/core/src/browser/quick-input/quick-help-service.ts index caaf22b77b9b5..eebc28d1c729f 100644 --- a/packages/core/src/browser/quick-input/quick-help-service.ts +++ b/packages/core/src/browser/quick-input/quick-help-service.ts @@ -14,11 +14,74 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { QuickAccessContribution } from './quick-access-contribution'; -import { QuickInputService } from './quick-input-service'; +import { inject, injectable } from 'inversify'; +import { CancellationToken } from '../../common'; +import { QuickAccessContribution, QuickAccessProvider, QuickAccessRegistry } from './quick-access'; +import { QuickInputService, QuickPickItem, QuickPickSeparator } from './quick-input-service'; @injectable() -export class QuickHelpService extends QuickInputService implements QuickAccessContribution { - registerQuickAccessProvider(): void { } +export class QuickHelpService implements QuickAccessProvider, QuickAccessContribution { + static PREFIX = '?'; + + @inject(QuickAccessRegistry) + protected quickAccessRegistry: QuickAccessRegistry; + + @inject(QuickInputService) + protected quickInputService: QuickInputService; + + getPicks(filter: string, token: CancellationToken): (QuickPickItem | QuickPickSeparator)[] { + const { editorProviders, globalProviders } = this.getQuickAccessProviders(); + const result: (QuickPickItem | QuickPickSeparator)[] = editorProviders.length === 0 || globalProviders.length === 0 ? + // Without groups + [ + ...(editorProviders.length === 0 ? globalProviders : editorProviders) + ] : + + // With groups + [ + { type: 'separator', label: 'global commands' }, + ...globalProviders, + { type: 'separator', label: 'editor commands' }, + ...editorProviders + ]; + return result; + } + + private getQuickAccessProviders(): { editorProviders: QuickPickItem[], globalProviders: QuickPickItem[] } { + const globalProviders: QuickPickItem[] = []; + const editorProviders: QuickPickItem[] = []; + + const providers = this.quickAccessRegistry.getQuickAccessProviders(); + + for (const provider of providers.sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))) { + if (provider.prefix === QuickHelpService.PREFIX) { + continue; // exclude help which is already active + } + + for (const helpEntry of provider.helpEntries) { + const prefix = helpEntry.prefix || provider.prefix; + const label = prefix || '\u2026' /* ... */; + + (helpEntry.needsEditor ? editorProviders : globalProviders).push({ + label, + ariaLabel: `${label}, ${helpEntry.description}`, + description: helpEntry.description, + execute: () => this.quickInputService.open(prefix) + }); + } + } + + return { editorProviders, globalProviders }; + } + + registerQuickAccessProvider(): void { + this.quickAccessRegistry.registerQuickAccessProvider( + { + getInstance: () => this, + prefix: QuickHelpService.PREFIX, + placeholder: 'Type "?" to get help on the actions you can take from here.', + helpEntries: [{ description: 'Show all Quick Access Providers', needsEditor: false }] + } + ); + } } diff --git a/packages/core/src/browser/quick-input/quick-input-frontend-contribution.ts b/packages/core/src/browser/quick-input/quick-input-frontend-contribution.ts index 3d2e44feb55e4..8c61eca25d34f 100644 --- a/packages/core/src/browser/quick-input/quick-input-frontend-contribution.ts +++ b/packages/core/src/browser/quick-input/quick-input-frontend-contribution.ts @@ -17,7 +17,7 @@ import { injectable, inject, named } from 'inversify'; import { ContributionProvider } from '../../common'; import { FrontendApplicationContribution } from '../frontend-application'; -import { QuickAccessContribution } from './quick-access-contribution'; +import { QuickAccessContribution } from './quick-access'; @injectable() export class QuickInputFrontendContribution implements FrontendApplicationContribution { diff --git a/packages/core/src/browser/quick-input/quick-input-service.ts b/packages/core/src/browser/quick-input/quick-input-service.ts index bbbb1ece98219..eafaa012f798a 100644 --- a/packages/core/src/browser/quick-input/quick-input-service.ts +++ b/packages/core/src/browser/quick-input/quick-input-service.ts @@ -14,9 +14,19 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; import { CancellationToken, Event } from '../../common'; import URI from '../../common/uri'; +import { KeySequence } from '../keyboard'; + +export interface Match { + start: number; + end: number; +} +export interface QuickPickItemHighlights { + label?: Match[]; + description?: Match[]; + detail?: Match[]; +} export interface QuickPickItem { type?: 'item' | 'separator'; @@ -26,9 +36,19 @@ export interface QuickPickItem { ariaLabel?: string; description?: string; detail?: string; - resource?: URI; + keySequence?: KeySequence; iconClasses?: string[]; - execute?: (item: QuickPickItem, lookFor: string) => void; + alwaysShow?: boolean; + highlights?: QuickPickItemHighlights; + buttons?: QuickInputButton[]; + execute?: () => void; +} + +export namespace QuickPickItem { + export function is(item: QuickPickSeparator | QuickPickItem): item is QuickPickItem { + // if it's not a separator, it's an item + return item.type !== 'separator'; + } } export interface QuickPickSeparator { @@ -36,6 +56,14 @@ export interface QuickPickSeparator { label?: string; } +export namespace QuickPickSeparator { + export function is(item: QuickPickSeparator | QuickPickItem): item is QuickPickSeparator { + return item.type === 'separator'; + } +} + +export type QuickPicks = (QuickPickSeparator | QuickPickItem)[]; + export interface QuickPickValue extends QuickPickItem { value: V } @@ -66,7 +94,7 @@ export interface QuickInput { dispose(): void; } -export interface IInputBox extends QuickInput { +export interface InputBox extends QuickInput { value: string | undefined; valueSelection: Readonly<[number, number]> | undefined; placeholder: string | undefined; @@ -82,21 +110,21 @@ export interface IInputBox extends QuickInput { export interface QuickPick extends QuickInput { value: string; placeholder: string | undefined; - readonly onDidChangeValue: Event; - readonly onDidAccept: Event; - buttons: ReadonlyArray; - readonly onDidTriggerButton: Event; items: ReadonlyArray; + activeItems: ReadonlyArray; + selectedItems: ReadonlyArray; canSelectMany: boolean; matchOnDescription: boolean; matchOnDetail: boolean; - activeItems: ReadonlyArray; + readonly onDidAccept: Event; + readonly onDidChangeValue: Event; + readonly onDidTriggerButton: Event; + readonly onDidTriggerItemButton: Event>; readonly onDidChangeActive: Event; - selectedItems: ReadonlyArray; readonly onDidChangeSelection: Event; } -export interface IPickOptions { +export interface PickOptions { placeHolder?: string; matchOnDescription?: boolean; matchOnDetail?: boolean; @@ -109,7 +137,7 @@ export interface IPickOptions { onDidFocus?: (entry: T) => void; } -export interface IInputOptions { +export interface InputOptions { value?: string; valueSelection?: [number, number]; prompt?: string; @@ -165,22 +193,21 @@ export interface QuickPickOptions { onDidTriggerItemButton?: (ItemButtonEvent: QuickPickItemButtonEvent) => void } -export interface IQuickInputService { +export const QuickInputService = Symbol('QuickInputService'); +export interface QuickInputService { readonly backButton: QuickInputButton; readonly onShow: Event; readonly onHide: Event; open(filter: string): void; - reset(): void; - createInputBox(): IInputBox; - createQuickPick(): QuickPick; - input(options?: IInputOptions, token?: CancellationToken): Promise; - pick>(picks: Promise | T[], options?: O, token?: CancellationToken): + createInputBox(): InputBox; + input(options?: InputOptions, token?: CancellationToken): Promise; + pick>(picks: Promise | T[], options?: O, token?: CancellationToken): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined>; showQuickPick(items: Array, options?: QuickPickOptions): Promise; hide(): void; } -export function filterItems(items: Array, filter: string): Array { +export function filterItems(items: QuickPickItem[], filter: string): QuickPickItem[] { return filter.trim().length === 0 ? items : items .filter(item => item.label.toLowerCase().indexOf(filter.toLowerCase()) > -1) .map(item => Object.assign(item, { highlights: { label: findMatches(item.label.toLowerCase(), filter.toLowerCase()) } })); @@ -190,29 +217,3 @@ export function findMatches(label: string, lookFor: string): Array<{ start: numb const _label = label.toLocaleLowerCase(); const _lookFor = lookFor.toLocaleLowerCase(); return _label.indexOf(_lookFor) > -1 ? [{ start: _label.indexOf(_lookFor), end: _label.indexOf(_lookFor) + _lookFor.length }] : undefined; } - -@injectable() -export class QuickInputService implements IQuickInputService { - createInputBox(): IInputBox { return {} as IInputBox; } - - createQuickPick(): QuickPick { return {} as QuickPick; } - - input(options?: IInputOptions, token?: CancellationToken): Promise { return Promise.resolve(undefined); } - - pick>(picks: Promise | T[], options: O = {}, token?: CancellationToken): - Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { - return Promise.resolve(undefined); - } - - showQuickPick(items: Array, options?: QuickPickOptions): Promise { return Promise.resolve({} as T); } - - get backButton(): QuickInputButton { return {} as QuickInputButton; } - get onShow(): Event { return {} as Event; } - get onHide(): Event { return {} as Event; } - - open(filter: string): void { } - - reset(): void { } - - hide(): void { } -} diff --git a/packages/core/src/browser/quick-input/quick-view-service.ts b/packages/core/src/browser/quick-input/quick-view-service.ts index 5ffd727f27f41..4ac6e7f8f1e79 100644 --- a/packages/core/src/browser/quick-input/quick-view-service.ts +++ b/packages/core/src/browser/quick-input/quick-view-service.ts @@ -14,24 +14,71 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable } from 'inversify'; -import { Disposable } from '../../common'; -import { QuickInputService } from './quick-input-service'; +import { inject, injectable } from 'inversify'; +import { filterItems, QuickPickItem, QuickPicks } from '..'; +import { CancellationToken, Disposable } from '../../common'; +import { ContextKeyService } from '../context-key-service'; +import { QuickAccessProvider, QuickAccessRegistry } from './quick-access'; +import { QuickAccessContribution } from './quick-access'; export interface QuickViewItem { readonly label: string; readonly when?: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly open: () => any; + readonly open: () => void; } @injectable() -export class QuickViewService extends QuickInputService { - readonly prefix = 'view '; +export class QuickViewService implements QuickAccessContribution, QuickAccessProvider { + static PREFIX = 'view '; + + protected readonly items: (QuickPickItem & { when?: string })[] = []; + private hiddenItemLabels = new Set(); + + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; + + @inject(ContextKeyService) + protected readonly contextKexService: ContextKeyService; registerItem(item: QuickViewItem): Disposable { - return Disposable.create(() => { }); + const quickOpenItem = { + label: item.label, + execute: () => item.open(), + when: item.when + }; + this.items.push(quickOpenItem); + this.items.sort((a, b) => a.label!.localeCompare(b.label!)); + + return Disposable.create(() => { + const index = this.items.indexOf(quickOpenItem); + if (index !== -1) { + this.items.splice(index, 1); + } + }); + } + + hideItem(label: string): void { + this.hiddenItemLabels.add(label); + } + + showItem(label: string): void { + this.hiddenItemLabels.delete(label); + } + + registerQuickAccessProvider(): void { + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: QuickViewService.PREFIX, + placeholder: '', + helpEntries: [{ description: 'Open View', needsEditor: false }] + }); + } + + getPicks(filter: string, token: CancellationToken): QuickPicks { + const items = this.items.filter(item => + (item.when === undefined || this.contextKexService.match(item.when)) && + (!this.hiddenItemLabels.has(item.label)) + ); + return filterItems(items, filter); } - hideItem(label: string): void { } - showItem(label: string): void { } } diff --git a/packages/debug/src/browser/debug-frontend-module.ts b/packages/debug/src/browser/debug-frontend-module.ts index e247d2f8058c8..55f0cb38bd761 100644 --- a/packages/debug/src/browser/debug-frontend-module.ts +++ b/packages/debug/src/browser/debug-frontend-module.ts @@ -59,8 +59,8 @@ import { DebugInlineValueDecorator } from './editor/debug-inline-value-decorator import { JsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { DebugTabBarDecorator } from './debug-tab-bar-decorator'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; import { DebugContribution } from './debug-contribution'; +import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access'; export default new ContainerModule((bind: interfaces.Bind) => { bindContributionProvider(bind, DebugContribution); diff --git a/packages/debug/src/browser/debug-prefix-configuration.ts b/packages/debug/src/browser/debug-prefix-configuration.ts index fbb9aca3fc5ca..45b05299c4e2a 100644 --- a/packages/debug/src/browser/debug-prefix-configuration.ts +++ b/packages/debug/src/browser/debug-prefix-configuration.ts @@ -23,12 +23,14 @@ import { DebugSessionOptions } from './debug-session-options'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { LabelProvider } from '@theia/core/lib/browser/label-provider'; import URI from '@theia/core/lib/common/uri'; -import { QuickAccessContribution, QuickInputService, StatusBar, StatusBarAlignment } from '@theia/core/lib/browser'; +import { QuickAccessContribution, QuickAccessProvider, QuickAccessRegistry, QuickInputService, StatusBar, StatusBarAlignment } from '@theia/core/lib/browser'; import { DebugPreferences } from './debug-preferences'; -import { filterItems } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { filterItems, QuickPickItem, QuickPicks } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { CancellationToken } from '@theia/core/lib/common'; @injectable() -export class DebugPrefixConfiguration implements CommandContribution, CommandHandler, QuickAccessContribution, monaco.quickInput.IQuickAccessDataService { +export class DebugPrefixConfiguration implements CommandContribution, CommandHandler, QuickAccessContribution, QuickAccessProvider { + static readonly PREFIX = 'debug '; @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; @@ -45,6 +47,9 @@ export class DebugPrefixConfiguration implements CommandContribution, CommandHan @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; + @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @@ -78,7 +83,7 @@ export class DebugPrefixConfiguration implements CommandContribution, CommandHan } execute(): void { - this.quickInputService?.open(DebugQuickAccessProvider.PREFIX); + this.quickInputService?.open(DebugPrefixConfiguration.PREFIX); } isEnabled(): boolean { @@ -94,17 +99,16 @@ export class DebugPrefixConfiguration implements CommandContribution, CommandHan } registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: DebugQuickAccessProvider, - prefix: DebugQuickAccessProvider.PREFIX, + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: DebugPrefixConfiguration.PREFIX, placeholder: '', helpEntries: [{ description: 'Debug Configuration', needsEditor: false }] }); - DebugQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; } - async getPicks(filter: string, token: monaco.CancellationToken): Promise> { - const items: Array = []; + getPicks(filter: string, token: CancellationToken): QuickPicks { + const items: QuickPickItem[] = []; const configurations = this.debugConfigurationManager.all; Array.from(configurations).forEach(config => { items.push({ @@ -112,7 +116,7 @@ export class DebugPrefixConfiguration implements CommandContribution, CommandHan description: this.workspaceService.isMultiRootWorkspaceOpened ? this.labelProvider.getName(new URI(config.workspaceFolderUri)) : '', - accept: () => this.runConfiguration(config) + execute: () => this.runConfiguration(config) }); }); return filterItems(items, filter); @@ -164,26 +168,3 @@ export class DebugPrefixConfiguration implements CommandContribution, CommandHan this.statusBar.removeElement(this.statusBarId); } } -export class DebugQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = 'debug '; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching launch configurations' - }; - - constructor() { - super(DebugQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: DebugQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return DebugQuickAccessProvider.dataService?.getPicks(filter, token); - } -} diff --git a/packages/editor/src/browser/editor-contribution.ts b/packages/editor/src/browser/editor-contribution.ts index 91bac4110a33d..f24c8a5f27bc1 100644 --- a/packages/editor/src/browser/editor-contribution.ts +++ b/packages/editor/src/browser/editor-contribution.ts @@ -26,11 +26,9 @@ import { CommandRegistry, CommandContribution } from '@theia/core/lib/common'; import { KeybindingRegistry, KeybindingContribution } from '@theia/core/lib/browser'; import { LanguageService } from '@theia/core/lib/browser/language-service'; import { SUPPORTED_ENCODINGS } from '@theia/core/lib/browser/supported-encodings'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; -import { QuickEditorService } from '@theia/core/lib/browser/quick-input/quick-editor-service'; @injectable() -export class EditorContribution implements FrontendApplicationContribution, CommandContribution, KeybindingContribution, QuickAccessContribution { +export class EditorContribution implements FrontendApplicationContribution, CommandContribution, KeybindingContribution { @inject(StatusBar) protected readonly statusBar: StatusBar; @inject(EditorManager) protected readonly editorManager: EditorManager; @@ -39,9 +37,6 @@ export class EditorContribution implements FrontendApplicationContribution, Comm @inject(ContextKeyService) protected readonly contextKeyService: ContextKeyService; - @inject(QuickEditorService) @optional() - protected readonly quickEditorService: QuickEditorService; - @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; @@ -172,8 +167,4 @@ export class EditorContribution implements FrontendApplicationContribution, Comm keybinding: 'ctrlcmd+k ctrlcmd+\\', }); } - - registerQuickAccessProvider(): void { - this.quickEditorService?.registerQuickAccessProvider(); - } } diff --git a/packages/editor/src/browser/editor-frontend-module.ts b/packages/editor/src/browser/editor-frontend-module.ts index e8b6f71be4df5..330b5f8bdb67b 100644 --- a/packages/editor/src/browser/editor-frontend-module.ts +++ b/packages/editor/src/browser/editor-frontend-module.ts @@ -33,7 +33,8 @@ import { NavigationLocationUpdater } from './navigation/navigation-location-upda import { NavigationLocationService } from './navigation/navigation-location-service'; import { NavigationLocationSimilarity } from './navigation/navigation-location-similarity'; import { EditorVariableContribution } from './editor-variable-contribution'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; +import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access'; +import { QuickEditorService } from './quick-editor-service'; export default new ContainerModule(bind => { bindEditorPreferences(bind); @@ -68,9 +69,11 @@ export default new ContainerModule(bind => { bind(VariableContribution).to(EditorVariableContribution).inSingletonScope(); - [CommandContribution, KeybindingContribution, QuickAccessContribution].forEach(serviceIdentifier => { + [CommandContribution, KeybindingContribution].forEach(serviceIdentifier => { bind(serviceIdentifier).toService(EditorContribution); }); + bind(QuickEditorService).toSelf().inSingletonScope(); + bind(QuickAccessContribution).to(QuickEditorService); bind(CurrentEditorAccess).toSelf().inSingletonScope(); bind(ActiveEditorAccess).toSelf().inSingletonScope(); diff --git a/packages/monaco/src/browser/monaco-quick-editor-service.ts b/packages/editor/src/browser/quick-editor-service.ts similarity index 53% rename from packages/monaco/src/browser/monaco-quick-editor-service.ts rename to packages/editor/src/browser/quick-editor-service.ts index 03effbda8d380..157439cef9ea1 100644 --- a/packages/monaco/src/browser/monaco-quick-editor-service.ts +++ b/packages/editor/src/browser/quick-editor-service.ts @@ -14,29 +14,43 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { inject, injectable } from '@theia/core/shared/inversify'; -import { QuickEditorService, filterItems } from '@theia/core/lib/browser'; -import { EditorManager, EditorWidget } from '@theia/editor/lib/browser'; +import { injectable, inject } from '@theia/core/shared/inversify'; +import { CancellationToken } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; +import { LabelProvider } from '@theia/core/lib/browser/label-provider'; +import { OpenerService } from '@theia/core/lib/browser/opener-service'; +import { QuickAccessProvider, QuickAccessRegistry } from '@theia/core/lib/browser/quick-input/quick-access'; +import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access'; +import { filterItems, QuickPickItem, QuickPickSeparator } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { EditorManager, EditorWidget } from '.'; @injectable() -export class MonacoQuickEditorService extends QuickEditorService implements monaco.quickInput.IQuickAccessDataService { +export class QuickEditorService implements QuickAccessContribution, QuickAccessProvider { + static PREFIX = 'edt '; + + @inject(OpenerService) + protected readonly openerService: OpenerService; + + @inject(LabelProvider) + protected readonly labelProvider: LabelProvider; + + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; @inject(EditorManager) protected readonly editorManager: EditorManager; registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: EditorQuickAccessProvider, - prefix: EditorQuickAccessProvider.PREFIX, + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: QuickEditorService.PREFIX, placeholder: '', helpEntries: [{ description: 'Show All Opened Editors', needsEditor: false }] }); - EditorQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; } - getPicks(filter: string, token: monaco.CancellationToken): monaco.quickInput.Picks { - const editorItems: Array = []; + getPicks(filter: string, token: CancellationToken): (QuickPickItem | QuickPickSeparator)[] { + const editorItems: QuickPickItem[] = []; // Get the alphabetically sorted list of URIs of all currently opened editor widgets. const widgets: URI[] = this.editorManager.all @@ -57,7 +71,7 @@ export class MonacoQuickEditorService extends QuickEditorService implements mona return filterItems(editorItems.slice(), filter); } - protected toItem(uri: URI): monaco.quickInput.IAnythingQuickPickItem { + protected toItem(uri: URI): QuickPickItem { const description = this.labelProvider.getLongName(uri.parent); const icon = this.labelProvider.getIcon(uri); const iconClasses = icon === '' ? undefined : [icon + ' file-icon']; @@ -67,9 +81,8 @@ export class MonacoQuickEditorService extends QuickEditorService implements mona description: description, iconClasses, ariaLabel: uri.path.toString(), - resource: uri, alwaysShow: true, - accept: () => this.openFile(uri) + execute: () => this.openFile(uri) }; } @@ -78,27 +91,3 @@ export class MonacoQuickEditorService extends QuickEditorService implements mona .then(opener => opener.open(uri)); } } - -export class EditorQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = 'edt '; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching results' - }; - - constructor() { - super(EditorQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: EditorQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return EditorQuickAccessProvider.dataService?.getPicks(filter, token); - } -} diff --git a/packages/file-search/src/browser/file-search-frontend-module.ts b/packages/file-search/src/browser/file-search-frontend-module.ts index 829fae7d3271d..9cf6ba3dbe063 100644 --- a/packages/file-search/src/browser/file-search-frontend-module.ts +++ b/packages/file-search/src/browser/file-search-frontend-module.ts @@ -20,7 +20,7 @@ import { WebSocketConnectionProvider, KeybindingContribution } from '@theia/core import { QuickFileOpenFrontendContribution } from './quick-file-open-contribution'; import { QuickFileOpenService } from './quick-file-open'; import { fileSearchServicePath, FileSearchService } from '../common/file-search-service'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; +import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access'; export default new ContainerModule((bind: interfaces.Bind) => { bind(FileSearchService).toDynamicValue(ctx => { diff --git a/packages/file-search/src/browser/quick-file-open.ts b/packages/file-search/src/browser/quick-file-open.ts index 9007892fcaf4e..0827ea9175f1c 100644 --- a/packages/file-search/src/browser/quick-file-open.ts +++ b/packages/file-search/src/browser/quick-file-open.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { inject, injectable, optional, postConstruct } from '@theia/core/shared/inversify'; -import { OpenerService, KeybindingRegistry } from '@theia/core/lib/browser'; +import { OpenerService, KeybindingRegistry, QuickAccessRegistry, QuickAccessProvider } from '@theia/core/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import URI from '@theia/core/lib/common/uri'; import { FileSearchService, WHITESPACE_QUERY_SEPARATOR } from '../common/file-search-service'; @@ -27,14 +27,13 @@ import * as fuzzy from '@theia/core/shared/fuzzy'; import { MessageService } from '@theia/core/lib/common/message-service'; import { FileSystemPreferences } from '@theia/filesystem/lib/browser'; import { EditorOpenerOptions, EditorWidget, Position, Range } from '@theia/editor/lib/browser'; -import { findMatches, QuickInputService } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { findMatches, QuickInputService, QuickPickItem, QuickPicks } from '@theia/core/lib/browser/quick-input/quick-input-service'; export const quickFileOpen: Command = { id: 'file-search.openFile', category: 'File', label: 'Open File...' }; - export interface FilterAndRange { filter: string; range?: Range; @@ -42,9 +41,12 @@ export interface FilterAndRange { // Supports patterns of <#|:><#|:|,> const LINE_COLON_PATTERN = /\s?[#:\(](?:line )?(\d*)(?:[#:,](\d*))?\)?\s*$/; +export type FileQuickPickItem = QuickPickItem & { uri: URI }; @injectable() -export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataService { +export class QuickFileOpenService implements QuickAccessProvider { + static readonly PREFIX = ''; + @inject(KeybindingRegistry) protected readonly keybindingRegistry: KeybindingRegistry; @inject(WorkspaceService) @@ -53,6 +55,8 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS protected readonly openerService: OpenerService; @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; @inject(FileSearchService) protected readonly fileSearchService: FileSearchService; @inject(LabelProvider) @@ -65,13 +69,12 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS protected readonly fsPreferences: FileSystemPreferences; registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: AnythingQuickAccessProvider, - prefix: AnythingQuickAccessProvider.PREFIX, + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: QuickFileOpenService.PREFIX, placeholder: this.getPlaceHolder(), helpEntries: [{ description: 'Open File', needsEditor: false }] }); - AnythingQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; } /** @@ -151,14 +154,14 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS return undefined; } - async getPicks(filter: string, token: CancellationToken): Promise> { + async getPicks(filter: string, token: CancellationToken): Promise { const roots = this.workspaceService.tryGetRoots(); this.filterAndRange = this.splitFilterAndRange(filter); const fileFilter = this.filterAndRange.filter; const alreadyCollected = new Set(); - const recentlyUsedItems: Array = []; + const recentlyUsedItems: QuickPicks = []; const locations = [...this.navigationLocationService.locations()].reverse(); for (const location of locations) { @@ -179,7 +182,9 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS if (token.isCancellationRequested || results.length <= 0) { return []; } - const fileSearchResultItems: Array = []; + + const result = [...recentlyUsedItems]; + const fileSearchResultItems: FileQuickPickItem[] = []; for (const fileUri of results) { if (!alreadyCollected.has(fileUri)) { @@ -194,11 +199,12 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS sortedResults.sort((a, b) => this.compareItems(a, b)); if (sortedResults.length > 0) { - sortedResults.unshift({ type: 'separator', label: 'file results' }); + result.unshift({ type: 'separator', label: 'file results' }); + result.push(...sortedResults); } // Return the recently used items, followed by the search results. - return ([...recentlyUsedItems, ...sortedResults]); + return result; }; return this.fileSearchService.find(fileFilter, { @@ -214,31 +220,9 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS return roots.length !== 0 ? recentlyUsedItems : []; } } - - /** - * Compare two `IAnythingQuickPickItem`. - * - * @param a `IAnythingQuickPickItem` for comparison. - * @param b `IAnythingQuickPickItem` for comparison. - * @param member the `IAnythingQuickPickItem` object member for comparison. - */ protected compareItems( - a: monaco.quickInput.IAnythingQuickPickItem, - b: monaco.quickInput.IAnythingQuickPickItem, - member: 'label' | 'resource' = 'label'): number { - - /** - * Normalize a given string. - * - * @param str the raw string value. - * @returns the normalized string value. - */ - function normalize(str: string): string { - return str.trim().toLowerCase(); - } - - // Normalize the user query. - const query: string = normalize(this.filterAndRange.filter); + left: FileQuickPickItem, + right: FileQuickPickItem): number { /** * Score a given string. @@ -246,7 +230,10 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS * @param str the string to score on. * @returns the score. */ - function score(str: string): number { + function score(str: string | undefined): number { + if (!str) { + return 0; + } // Adjust for whitespaces in the query. const querySplit = query.split(WHITESPACE_QUERY_SEPARATOR); const queryJoin = querySplit.join(''); @@ -279,57 +266,17 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS } } - // Get the item's member values for comparison. - let itemA = a[member]!; - let itemB = b[member]!; - - // If the `URI` is used as a comparison member, perform the necessary string conversions. - if (typeof itemA !== 'string') { - itemA = itemA.path.toString(); - } - if (typeof itemB !== 'string') { - itemB = itemB.path.toString(); - } - - // Normalize the item labels. - itemA = normalize(itemA); - itemB = normalize(itemB); - - // Score the item labels. - const scoreA: number = score(itemA); - const scoreB: number = score(itemB); - - // If both label scores are identical, perform additional computation. - if (scoreA === scoreB) { - - // Favor the label which have the smallest substring index. - const indexA: number = itemA.indexOf(query); - const indexB: number = itemB.indexOf(query); - - if (indexA === indexB) { - - // Favor the result with the shortest label length. - if (itemA.length !== itemB.length) { - return (itemA.length < itemB.length) ? -1 : 1; - } - - // Fallback to the alphabetical order. - const comparison = itemB.localeCompare(itemA); - - // Compare results by `uri` if necessary. - if (comparison === 0) { - return member === 'resource' - ? 0 // Avoid infinite recursion if we have already compared by `uri`. - : this.compareItems(a, b, 'resource'); - } + const query: string = normalize(this.filterAndRange.filter); - return itemB.localeCompare(itemA); - } + const compareByLabelScore = (l: FileQuickPickItem, r: FileQuickPickItem) => score(r.label) - score(l.label); + const compareByLabelIndex = (l: FileQuickPickItem, r: FileQuickPickItem) => r.label.indexOf(query) - l.label.indexOf(query); + const compareByLabel = (l: FileQuickPickItem, r: FileQuickPickItem) => r.label.localeCompare(l.label); - return indexA - indexB; - } + const compareByPathScore = (l: FileQuickPickItem, r: FileQuickPickItem) => score(r.uri.path.toString()) - score(l.uri.path.toString()); + const compareByPathIndex = (l: FileQuickPickItem, r: FileQuickPickItem) => r.uri.path.toString().indexOf(query) - l.uri.path.toString().indexOf(query); + const compareByPathLabel = (l: FileQuickPickItem, r: FileQuickPickItem) => r.uri.path.toString().localeCompare(l.uri.path.toString()); - return scoreB - scoreA; + return compareWithDiscriminators(left, right, compareByLabelScore, compareByLabelIndex, compareByLabel, compareByPathScore, compareByPathIndex, compareByPathLabel); } openFile(uri: URI): void { @@ -350,14 +297,13 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS return { selection: this.filterAndRange.range }; } - private toItem(lookFor: string, uriOrString: URI | string): monaco.quickInput.IAnythingQuickPickItem { + private toItem(lookFor: string, uriOrString: URI | string): FileQuickPickItem { const uri = uriOrString instanceof URI ? uriOrString : new URI(uriOrString); const label = this.labelProvider.getName(uri); const description = this.getItemDescription(uri); const iconClasses = this.getItemIconClasses(uri); return { - resource: uri, label, description, highlights: { @@ -365,7 +311,8 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS description: findMatches(description, lookFor) }, iconClasses, - accept: () => this.openFile(uri) + uri, + execute: () => this.openFile(uri) }; } @@ -424,27 +371,23 @@ export class QuickFileOpenService implements monaco.quickInput.IQuickAccessDataS } } -export class AnythingQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = ''; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching results' - }; +/** + * Normalize a given string. + * + * @param str the raw string value. + * @returns the normalized string value. + */ +function normalize(str: string): string { + return str.trim().toLowerCase(); +} - constructor() { - super(AnythingQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: AnythingQuickAccessProvider.NO_RESULTS_PICK - }); - } +function compareWithDiscriminators(left: T, right: T, ...discriminators: ((left: T, right: T) => number)[]): number { + let comparisonValue = 0; + let i = 0; - // TODO: disposabled - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return AnythingQuickAccessProvider.dataService?.getPicks(filter, token); + while (comparisonValue === 0 && i < discriminators.length) { + comparisonValue = discriminators[i](left, right); + i++; } + return comparisonValue; } diff --git a/packages/git/src/browser/git-quick-open-service.ts b/packages/git/src/browser/git-quick-open-service.ts index a0817ce80aaf9..11ef547f80e1b 100644 --- a/packages/git/src/browser/git-quick-open-service.ts +++ b/packages/git/src/browser/git-quick-open-service.ts @@ -124,7 +124,7 @@ export class GitQuickOpenService { } return this.withProgress(async () => { const remotes = await this.getRemotes(); - const execute = async (item: GitQuickPickItem, lookFor: string) => { + const execute = async (item: GitQuickPickItem) => { try { await this.git.fetch(repository, { remote: item.ref!.name }); } catch (error) { @@ -165,7 +165,7 @@ export class GitQuickOpenService { } return this.withProgress(async () => { const [remotes, currentBranch] = await Promise.all([this.getRemotes(), this.getCurrentBranch()]); - const execute = async (item: GitQuickPickItem, lookFor: string) => { + const execute = async (item: GitQuickPickItem) => { try { await this.git.push(repository, { remote: item.label, setUpstream: true }); } catch (error) { @@ -186,7 +186,7 @@ export class GitQuickOpenService { return this.withProgress(async () => { const remotes = await this.getRemotes(); const defaultRemote = remotes[0].name; // I wish I could use assignment destructuring here. (GH-413) - const executeRemote = async (remoteItem: GitQuickPickItem, lookFor: string) => { + const executeRemote = async (remoteItem: GitQuickPickItem) => { // The first remote is the default. if (remoteItem.ref!.name === defaultRemote) { try { @@ -197,7 +197,7 @@ export class GitQuickOpenService { } else { // Otherwise we need to propose the branches from const branches = await this.getBranches(); - const executeBranch = async (branchItem: GitQuickPickItem, lookForBranch: string) => { + const executeBranch = async (branchItem: GitQuickPickItem) => { try { await this.git.pull(repository, { remote: remoteItem.ref!.name, branch: branchItem.ref!.nameWithoutRemote }); } catch (error) { @@ -224,7 +224,7 @@ export class GitQuickOpenService { } return this.withProgress(async () => { const [branches, currentBranch] = await Promise.all([this.getBranches(), this.getCurrentBranch()]); - const execute = async (item: GitQuickPickItem, lookFor: string) => { + const execute = async (item: GitQuickPickItem) => { try { await this.git.merge(repository, { branch: item.label }); } catch (error) { @@ -249,7 +249,7 @@ export class GitQuickOpenService { const index = branches.findIndex(branch => branch && branch.name === currentBranch.name); branches.splice(index, 1); } - const switchBranch = async (item: GitQuickPickItem, lookFor: string) => { + const switchBranch = async (item: GitQuickPickItem) => { try { await this.git.checkout(repository, { branch: item.ref!.nameWithoutRemote }); } catch (error) { @@ -303,7 +303,7 @@ export class GitQuickOpenService { } return this.withProgress(async () => { const [branches, tags, currentBranch] = await Promise.all([this.getBranches(repository), this.getTags(repository), this.getCurrentBranch(repository)]); - const execute = async (item: GitQuickPickItem, lookFor: string) => { + const execute = async (item: GitQuickPickItem) => { execFunc(item.ref!.name, currentBranch ? currentBranch.name : ''); }; const branchItems = branches.map(branch => new GitQuickPickItem(branch.name, execute, branch)); @@ -471,7 +471,7 @@ export class GitQuickOpenService { private toRepositoryPathQuickOpenItem(root: FileStat): GitQuickPickItem { const rootUri = root.resource; - const execute = async (item: GitQuickPickItem, lookFor: string) => { + const execute = async (item: GitQuickPickItem) => { const wsRoot = item.ref!.toString(); this.doInitRepository(wsRoot); }; @@ -550,11 +550,18 @@ export class GitQuickOpenService { } class GitQuickPickItem implements QuickPickItem { + readonly execute?: () => void; constructor( public label: string, - public readonly execute?: (item: QuickPickItem, lookFor: string) => void, + execute?: (item: QuickPickItem) => void, public readonly ref?: T, public description?: string, public alwaysShow = true, - public sortByLabel = false) { } + public sortByLabel = false) { + this.execute = execute ? createExecFunction(execute, this) : undefined; + } +} + +function createExecFunction(f: (item: QuickPickItem) => void, item: QuickPickItem): () => void { + return () => { f(item); }; } diff --git a/packages/monaco/src/browser/monaco-command.ts b/packages/monaco/src/browser/monaco-command.ts index d13c931b51a8c..b56120f4010a7 100644 --- a/packages/monaco/src/browser/monaco-command.ts +++ b/packages/monaco/src/browser/monaco-command.ts @@ -229,10 +229,10 @@ export class MonacoEditorCommandHandlers implements CommandContribution { protected configureEol(editor: MonacoEditor): void { const items = ['LF', 'CRLF'].map(lineEnding => - ({ - label: lineEnding, - execute: () => this.setEol(editor, lineEnding) - }) + ({ + label: lineEnding, + execute: () => this.setEol(editor, lineEnding) + }) ); this.quickInputService?.showQuickPick(items, { placeholder: 'Select End of Line Sequence' }); } @@ -259,14 +259,14 @@ export class MonacoEditorCommandHandlers implements CommandContribution { const { tabSize } = model.getOptions(); const sizes = Array.from(Array(8), (_, x) => x + 1); const tabSizeOptions = sizes.map(size => - ({ - label: size === tabSize ? `${size} Configured Tab Size` : size.toString(), - // eslint-disable-next-line @typescript-eslint/no-explicit-any - execute: (quickPick: any, lookFor: string) => model.updateOptions({ - tabSize: size || tabSize, - insertSpaces: useSpaces - }) + ({ + label: size === tabSize ? `${size} Configured Tab Size` : size.toString(), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + execute: () => model.updateOptions({ + tabSize: size || tabSize, + insertSpaces: useSpaces }) + }) ); this.quickInputService?.showQuickPick(tabSizeOptions, { placeholder: 'Select Tab Size for Current File' }); } diff --git a/packages/monaco/src/browser/monaco-editor-provider.ts b/packages/monaco/src/browser/monaco-editor-provider.ts index e26dcc15e11b1..003e0d409e3ec 100644 --- a/packages/monaco/src/browser/monaco-editor-provider.ts +++ b/packages/monaco/src/browser/monaco-editor-provider.ts @@ -41,7 +41,7 @@ import { HttpOpenHandlerOptions } from '@theia/core/lib/browser/http-open-handle import { MonacoToProtocolConverter } from './monaco-to-protocol-converter'; import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter'; import { FileSystemPreferences } from '@theia/filesystem/lib/browser'; -import { MonacoQuickInputService } from './monaco-quick-input-service'; +import { MonacoQuickInputImplementation } from './monaco-quick-input-service'; export const MonacoEditorFactory = Symbol('MonacoEditorFactory'); export interface MonacoEditorFactory { @@ -71,8 +71,8 @@ export class MonacoEditorProvider { @inject(FileSystemPreferences) protected readonly filePreferences: FileSystemPreferences; - @inject(MonacoQuickInputService) - protected readonly quickInputService: MonacoQuickInputService; + @inject(MonacoQuickInputImplementation) + protected readonly quickInputService: MonacoQuickInputImplementation; protected _current: MonacoEditor | undefined; /** diff --git a/packages/monaco/src/browser/monaco-frontend-module.ts b/packages/monaco/src/browser/monaco-frontend-module.ts index c10a52c0762a4..22fbd9b72d5f9 100644 --- a/packages/monaco/src/browser/monaco-frontend-module.ts +++ b/packages/monaco/src/browser/monaco-frontend-module.ts @@ -22,7 +22,7 @@ import { MenuContribution, CommandContribution } from '@theia/core/lib/common'; import { FrontendApplicationContribution, KeybindingContribution, PreferenceService, PreferenceSchemaProvider, createPreferenceProxy, - PreferenceScope, PreferenceChange, OVERRIDE_PROPERTY_PATTERN, QuickInputService, QuickCommandService, QuickHelpService, QuickViewService, QuickEditorService + PreferenceScope, PreferenceChange, OVERRIDE_PROPERTY_PATTERN, QuickInputService } from '@theia/core/lib/browser'; import { TextEditorProvider, DiffNavigatorProvider } from '@theia/editor/lib/browser'; import { StrictEditorTextFocusContext } from '@theia/editor/lib/browser/editor-keybinding-contexts'; @@ -61,14 +61,11 @@ import { LanguageService } from '@theia/core/lib/browser/language-service'; import { MonacoToProtocolConverter } from './monaco-to-protocol-converter'; import { ProtocolToMonacoConverter } from './protocol-to-monaco-converter'; import { MonacoFormattingConflictsContribution } from './monaco-formatting-conflicts'; -import { MonacoQuickInputService } from './monaco-quick-input-service'; -import { MonacoQuickCommandService } from './monaco-quick-command-service'; -import { MonacoQuickHelpService } from './monaco-quick-help-service'; -import { MonacoQuickViewService } from './monaco-quick-view-service'; -import { MonacoQuickEditorService } from './monaco-quick-editor-service'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; +import { MonacoQuickInputImplementation, MonacoQuickInputService } from './monaco-quick-input-service'; import { GotoLineQuickAccessContribution } from './monaco-gotoline-quick-access'; import { GotoSymbolQuickAccessContribution } from './monaco-gotosymbol-quick-access'; +import { QuickAccessContribution, QuickAccessRegistry } from '@theia/core/lib/browser/quick-input/quick-access'; +import { MonacoQuickAccessRegistry } from './monaco-quick-access-registry'; decorate(injectable(), monaco.contextKeyService.ContextKeyService); @@ -139,20 +136,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(KeybindingContribution).toService(MonacoKeybindingContribution); rebind(StrictEditorTextFocusContext).to(MonacoStrictEditorTextFocusContext).inSingletonScope(); + bind(MonacoQuickInputImplementation).toSelf().inSingletonScope(); bind(MonacoQuickInputService).toSelf().inSingletonScope(); bind(QuickInputService).toService(MonacoQuickInputService); - bind(MonacoQuickCommandService).toSelf().inSingletonScope(); - bind(QuickCommandService).toService(MonacoQuickCommandService); - - bind(MonacoQuickHelpService).toSelf().inSingletonScope(); - bind(QuickHelpService).toService(MonacoQuickHelpService); - - bind(MonacoQuickViewService).toSelf().inSingletonScope(); - bind(QuickViewService).toService(MonacoQuickViewService); - - bind(MonacoQuickEditorService).toSelf().inSingletonScope(); - bind(QuickEditorService).toService(MonacoQuickEditorService); + bind(MonacoQuickAccessRegistry).toSelf().inSingletonScope(); + bind(QuickAccessRegistry).toService(MonacoQuickAccessRegistry); bind(GotoLineQuickAccessContribution).toSelf().inSingletonScope(); bind(QuickAccessContribution).toService(GotoLineQuickAccessContribution); diff --git a/packages/monaco/src/browser/monaco-quick-access-registry.ts b/packages/monaco/src/browser/monaco-quick-access-registry.ts new file mode 100644 index 0000000000000..80a843ad8dd48 --- /dev/null +++ b/packages/monaco/src/browser/monaco-quick-access-registry.ts @@ -0,0 +1,100 @@ +/******************************************************************************** + * Copyright (c) 2021 Red Hat and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { KeybindingRegistry, QuickPickItem, QuickPickSeparator } from '@theia/core/lib/browser'; +import { QuickAccessProviderDescriptor, QuickAccessRegistry } from '@theia/core/lib/browser/quick-input/quick-access'; +import { CancellationToken, Disposable } from '@theia/core/lib/common'; +import { inject, injectable } from '@theia/core/shared/inversify'; +import { MonacoQuickPickItem } from './monaco-quick-input-service'; + +abstract class MonacoPickerAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { + constructor(prefix: string, options?: monaco.quickInput.IPickerQuickAccessProviderOptions) { + super(prefix, options); + } + + abstract getDescriptor(): QuickAccessProviderDescriptor; +} + +class TheiaQuickAccessDescriptor implements monaco.quickInput.IQuickAccessProviderDescriptor { + constructor( + public readonly theiaDescriptor: QuickAccessProviderDescriptor, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + readonly ctor: { new(...services: any /* TS BrandedService but no clue how to type this properly */[]): monaco.quickInput.IQuickAccessProvider }, + readonly prefix: string, + readonly helpEntries: monaco.quickInput.IQuickAccessProviderHelp[], + readonly placeholder?: string) { } +} + +@injectable() +export class MonacoQuickAccessRegistry implements QuickAccessRegistry { + @inject(KeybindingRegistry) + protected readonly keybindingRegistry: KeybindingRegistry; + + private get monacoRegistry(): monaco.quickInput.IQuickAccessRegistry { + return monaco.platform.Registry.as('workbench.contributions.quickaccess'); + } + + registerQuickAccessProvider(descriptor: QuickAccessProviderDescriptor): Disposable { + const toMonacoPick = (item: QuickPickItem): monaco.quickInput.Pick => { + if (QuickPickSeparator.is(item)) { + return item; + } else { + return new MonacoQuickPickItem(item, this.keybindingRegistry); + } + }; + + const inner = + class extends MonacoPickerAccessProvider { + getDescriptor(): QuickAccessProviderDescriptor { + return descriptor; + } + constructor() { + super(descriptor.prefix); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getPicks(filter: string, disposables: any, token: CancellationToken): monaco.quickInput.Picks | Promise> { + const result = descriptor.getInstance().getPicks(filter, token); + if (result instanceof Promise) { + return result.then(picks => picks.map(toMonacoPick)); + } else { + return result.map(toMonacoPick); + } + } + }; + + return this.monacoRegistry.registerQuickAccessProvider(new TheiaQuickAccessDescriptor( + descriptor, + inner, + descriptor.prefix, + descriptor.helpEntries, + descriptor.placeholder + )); + } + + getQuickAccessProviders(): QuickAccessProviderDescriptor[] { + return this.monacoRegistry.getQuickAccessProviders() + .filter(provider => provider instanceof TheiaQuickAccessDescriptor) + .map(provider => (provider as TheiaQuickAccessDescriptor).theiaDescriptor); + } + getQuickAccessProvider(prefix: string): QuickAccessProviderDescriptor | undefined { + const monacoDescriptor = this.monacoRegistry.getQuickAccessProvider(prefix); + return monacoDescriptor ? (monacoDescriptor as TheiaQuickAccessDescriptor).theiaDescriptor : undefined; + } + clear(): void { + this.monacoRegistry.clear(); + } +} diff --git a/packages/monaco/src/browser/monaco-quick-command-service.ts b/packages/monaco/src/browser/monaco-quick-command-service.ts deleted file mode 100644 index e01452232676c..0000000000000 --- a/packages/monaco/src/browser/monaco-quick-command-service.ts +++ /dev/null @@ -1,133 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { inject, injectable } from '@theia/core/shared/inversify'; -import { KeybindingRegistry, KeySequence, QuickCommandService } from '@theia/core/lib/browser'; -import { Command } from '@theia/core/lib/common/command'; -import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding'; -import { filterItems } from '@theia/core/lib/browser/quick-input/quick-input-service'; - -@injectable() -export class MonacoQuickCommandService extends QuickCommandService implements monaco.quickInput.IQuickAccessDataService { - - private recentItems: Array = []; - private otherItems: Array = []; - - @inject(KeybindingRegistry) - protected readonly keybindingRegistry: KeybindingRegistry; - - registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: CommandsQuickAccessProvider, - prefix: CommandsQuickAccessProvider.PREFIX, - placeholder: '', - helpEntries: [{ description: 'Quick Command', needsEditor: false }] - }); - CommandsQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; - } - - reset(): void { - const { recent, other } = this.getCommands(); - this.recentItems = []; - this.otherItems = []; - this.recentItems.push(...recent.map(command => this.toItem(command))); - this.otherItems.push(...other.map(command => this.toItem(command))); - } - - getPicks(filter: string, token: monaco.CancellationToken): monaco.quickInput.Picks { - const items: Array = []; - this.reset(); - const recentItems = filterItems(this.recentItems.slice(), filter); - const otherItems = filterItems(this.otherItems.slice(), filter); - - if (recentItems.length > 0) { - items.push({ type: 'separator', label: 'recently used' }, ...recentItems); - } - - if (otherItems.length > 0) { - if (recentItems.length > 0) { - items.push({ type: 'separator', label: 'other commands' }); - } - items.push(...otherItems); - } - return items; - } - - private toItem(command: Command): monaco.quickInput.IAnythingQuickPickItem { - const label = (command.category) ? `${command.category}: ` + command.label! : command.label!; - const iconClasses = this.getItemIconClasses(command); - const activeElement = window.document.activeElement as HTMLElement; - - return { - label, - iconClasses, - alwaysShow: !!this.commandRegistry.getActiveHandler(command.id), - keybinding: this.getKeybinding(command), - accept: () => { - activeElement.focus({ preventScroll: true }); - this.commandRegistry.executeCommand(command.id); - this.commandRegistry.addRecentCommand(command); - } - }; - } - - private getKeybinding(command: Command): monaco.keybindings.ResolvedKeybinding | undefined { - const keybindings = this.keybindingRegistry.getKeybindingsForCommand(command.id); - if (!keybindings || keybindings.length === 0) { - return undefined; - } - - let keySequence: KeySequence; - try { - keySequence = this.keybindingRegistry.resolveKeybinding(keybindings[0]); - } catch (error) { - return undefined; - } - return new MonacoResolvedKeybinding(keySequence, this.keybindingRegistry); - } - - private getItemIconClasses(command: Command): string[] | undefined { - const toggledHandler = this.commandRegistry.getToggledHandler(command.id); - if (toggledHandler) { - return ['fa fa-check']; - } - return undefined; - } -} - -export class CommandsQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = '>'; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching results' - }; - - constructor() { - super(CommandsQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: CommandsQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return CommandsQuickAccessProvider.dataService?.getPicks(filter, token); - } -} diff --git a/packages/monaco/src/browser/monaco-quick-help-service.ts b/packages/monaco/src/browser/monaco-quick-help-service.ts deleted file mode 100644 index dcd8542f71abc..0000000000000 --- a/packages/monaco/src/browser/monaco-quick-help-service.ts +++ /dev/null @@ -1,106 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { QuickHelpService } from '@theia/core/lib/browser'; -import { inject, injectable } from '@theia/core/shared/inversify'; -import { MonacoQuickInputService } from './monaco-quick-input-service'; - -@injectable() -export class MonacoQuickHelpService extends QuickHelpService implements monaco.quickInput.IQuickAccessDataService { - - @inject(MonacoQuickInputService) - protected readonly monacoQuickInputService: MonacoQuickInputService; - - registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: HelpQuickAccessProvider, - prefix: HelpQuickAccessProvider.PREFIX, - placeholder: 'Type "?" to get help on the actions you can take from here.', - helpEntries: [{ description: 'Show all Quick Access Providers', needsEditor: false }] - }); - HelpQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; - } - - getPicks(filter: string, token: monaco.CancellationToken): monaco.quickInput.Picks { - const { editorProviders, globalProviders } = this.getQuickAccessProviders(); - const result = editorProviders.length === 0 || globalProviders.length === 0 ? - // Without groups - [ - ...(editorProviders.length === 0 ? globalProviders : editorProviders) - ] : - - // With groups - [ - { label: 'global commands', type: 'separator' }, - ...globalProviders, - { label: 'editor commands', type: 'separator' }, - ...editorProviders - ]; - return result as monaco.quickInput.Picks; - } - - private getQuickAccessProviders(): { editorProviders: monaco.quickInput.IHelpQuickAccessPickItem[], globalProviders: monaco.quickInput.IHelpQuickAccessPickItem[] } { - const globalProviders: monaco.quickInput.IHelpQuickAccessPickItem[] = []; - const editorProviders: monaco.quickInput.IHelpQuickAccessPickItem[] = []; - - const providers = monaco.platform.Registry.as('workbench.contributions.quickaccess').getQuickAccessProviders(); - - for (const provider of providers.sort((providerA, providerB) => providerA.prefix.localeCompare(providerB.prefix))) { - if (provider.prefix === HelpQuickAccessProvider.PREFIX) { - continue; // exclude help which is already active - } - - for (const helpEntry of provider.helpEntries) { - const prefix = helpEntry.prefix || provider.prefix; - const label = prefix || '\u2026' /* ... */; - - (helpEntry.needsEditor ? editorProviders : globalProviders).push({ - prefix, - label, - ariaLabel: `${label}, ${helpEntry.description}`, - description: helpEntry.description, - accept: () => this.monacoQuickInputService.open(prefix) - }); - } - } - - return { editorProviders, globalProviders }; - } -} - -export class HelpQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = '?'; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching results' - }; - - constructor() { - super(HelpQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: HelpQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - protected getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return HelpQuickAccessProvider.dataService.getPicks(filter, token); - } -} diff --git a/packages/monaco/src/browser/monaco-quick-input-service.ts b/packages/monaco/src/browser/monaco-quick-input-service.ts index f1a14cb41aa7a..8a30933a366a9 100644 --- a/packages/monaco/src/browser/monaco-quick-input-service.ts +++ b/packages/monaco/src/browser/monaco-quick-input-service.ts @@ -15,19 +15,23 @@ ********************************************************************************/ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { QuickInputService } from '@theia/core/lib/browser'; +import { + InputBox, InputOptions, KeybindingRegistry, PickOptions, + QuickInputButton, QuickInputService, QuickPick, QuickPickItem, QuickPickItemButtonEvent, QuickPickItemHighlights, QuickPickOptions, QuickPickSeparator +} from '@theia/core/lib/browser'; import { CancellationToken, Event } from '@theia/core/lib/common'; -import { injectable, inject, postConstruct } from '@theia/core/shared/inversify'; +import { injectable, inject } from '@theia/core/shared/inversify'; +import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding'; @injectable() -export class MonacoQuickInputService extends QuickInputService implements monaco.quickInput.IQuickInputService { - - @inject(monaco.contextKeyService.ContextKeyService) - protected readonly contextKeyService: monaco.contextKeyService.ContextKeyService; +export class MonacoQuickInputImplementation implements monaco.quickInput.IQuickInputService { controller: monaco.quickInput.QuickInputController; quickAccess: monaco.quickInput.IQuickAccessController; + @inject(monaco.contextKeyService.ContextKeyService) + protected readonly contextKeyService: monaco.contextKeyService.ContextKeyService; + protected container: HTMLElement; private quickInputList: monaco.list.List; @@ -36,13 +40,8 @@ export class MonacoQuickInputService extends QuickInputService implements monaco get onHide(): Event { return this.controller.onHide; } constructor() { - super(); this.initContainer(); this.initController(); - } - - @postConstruct() - protected async init(): Promise { this.quickAccess = new monaco.quickInput.QuickAccessController(this, monaco.services.StaticServices.instantiationService.get()); } @@ -67,87 +66,6 @@ export class MonacoQuickInputService extends QuickInputService implements monaco }, 300); } - showQuickPick(items: Array, options?: monaco.quickInput.IQuickPickOptions): Promise { - return new Promise((resolve, reject) => { - const quickPick = this.createQuickPick(); - - if (options) { - quickPick.ariaLabel = options.ariaLabel; - quickPick.canAcceptInBackground = !!options.canAcceptInBackground; - quickPick.canSelectMany = !!options.canSelectMany; - quickPick.contextKey = options.contextKey; - quickPick.customButton = !!options.customButton; - quickPick.customHover = options.customHover; - quickPick.customLabel = options.customLabel; - quickPick.description = options.description; - quickPick.enabled = options.enabled ?? true; - quickPick.hideCheckAll = !!options.hideCheckAll; - quickPick.hideInput = !!options.hideInput; - quickPick.ignoreFocusOut = !!options.ignoreFocusOut; - quickPick.autoFocusOnList = options.autoFocusOnList ?? true; - quickPick.matchOnDescription = options.matchOnDescription ?? true; - quickPick.matchOnDetail = options.matchOnDetail ?? true; - quickPick.matchOnLabel = options.matchOnLabel ?? true; - quickPick.placeholder = options.placeholder; - quickPick.sortByLabel = options.sortByLabel ?? true; - quickPick.step = options.step; - quickPick.title = options.title; - quickPick.totalSteps = options.totalSteps; - quickPick.validationMessage = options.validationMessage; - - if (options.activeItem) { - quickPick.activeItems = [options.activeItem]; - } - - quickPick.onDidAccept((event: Event) => { - if (options?.onDidAccept) { - options.onDidAccept(); - } - }); - - quickPick.onDidHide(() => { - if (options.onDidHide) { - options.onDidHide(); - }; - quickPick.dispose(); - }); - quickPick.onDidChangeValue((filter: string) => { - if (options.onDidChangeValue) { - options.onDidChangeValue(quickPick, filter); - } - }); - quickPick.onDidChangeActive((activeItems: Array) => { - if (options.onDidChangeActive) { - options.onDidChangeActive(quickPick, activeItems); - } - }); - quickPick.onDidTriggerButton((button: monaco.quickInput.IQuickInputButton) => { - if (options.onDidTriggerButton) { - options.onDidTriggerButton(button); - } - }); - quickPick.onDidTriggerItemButton((itemButtonEvent: monaco.quickInput.IQuickPickItemButtonEvent) => { - if (options.onDidTriggerItemButton) { - options.onDidTriggerItemButton(itemButtonEvent); - } - }); - quickPick.onDidChangeSelection((selectedItems: Array) => { - if (options.onDidChangeSelection) { - options.onDidChangeSelection(quickPick, selectedItems); - } - if (selectedItems[0].execute) { - selectedItems[0].execute(selectedItems[0], quickPick.value); - } - quickPick.hide(); - resolve(selectedItems[0]); - }); - } - - quickPick.items = items; - quickPick.show(); - }); - } - input(options?: monaco.quickInput.IInputOptions, token?: CancellationToken): Promise { return this.controller.input(options, token); } @@ -240,3 +158,316 @@ export class MonacoQuickInputService extends QuickInputService implements monaco } } +@injectable() +export class MonacoQuickInputService implements QuickInputService { + @inject(MonacoQuickInputImplementation) + private monacoService: MonacoQuickInputImplementation; + + @inject(KeybindingRegistry) + protected readonly keybindingRegistry: KeybindingRegistry; + + get backButton(): QuickInputButton { + return this.monacoService.backButton; + } + + get onShow(): Event { return this.monacoService.onShow; } + get onHide(): Event { return this.monacoService.onHide; } + + open(filter: string): void { + this.monacoService.open(filter); + } + + createInputBox(): InputBox { + return this.monacoService.createInputBox(); + } + + input(options?: InputOptions, token?: CancellationToken): Promise { + return this.monacoService.input(); + } + pick>(picks: T[] | Promise, options?: O, token?: CancellationToken): + Promise<(O extends { canPickMany: true; } ? T[] : T) | undefined> { + return this.monacoService.pick(picks, options, token); + } + + showQuickPick(items: T[], options?: QuickPickOptions): Promise { + return new Promise((resolve, reject) => { + const quickPick = this.monacoService.createQuickPick>(); + const wrapped = this.wrapQuickPick(quickPick); + + if (options) { + wrapped.canSelectMany = !!options.canSelectMany; + wrapped.contextKey = options.contextKey; + wrapped.description = options.description; + wrapped.enabled = options.enabled ?? true; + wrapped.ignoreFocusOut = !!options.ignoreFocusOut; + wrapped.matchOnDescription = options.matchOnDescription ?? true; + wrapped.matchOnDetail = options.matchOnDetail ?? true; + wrapped.placeholder = options.placeholder; + wrapped.step = options.step; + wrapped.title = options.title; + wrapped.totalSteps = options.totalSteps; + + if (options.activeItem) { + wrapped.activeItems = [options.activeItem]; + } + + wrapped.onDidAccept(() => { + if (options?.onDidAccept) { + options.onDidAccept(); + } + wrapped.hide(); + resolve(wrapped.selectedItems[0]); + }); + + wrapped.onDidHide(() => { + if (options.onDidHide) { + options.onDidHide(); + }; + wrapped.dispose(); + }); + wrapped.onDidChangeValue((filter: string) => { + if (options.onDidChangeValue) { + options.onDidChangeValue(wrapped, filter); + } + }); + wrapped.onDidChangeActive((activeItems: Array) => { + if (options.onDidChangeActive) { + options.onDidChangeActive(wrapped, activeItems); + } + }); + wrapped.onDidTriggerButton((button: monaco.quickInput.IQuickInputButton) => { + if (options.onDidTriggerButton) { + options.onDidTriggerButton(button); + } + }); + wrapped.onDidTriggerItemButton((evt: QuickPickItemButtonEvent) => { + if (options.onDidTriggerItemButton) { + options.onDidTriggerItemButton(evt); + } + }); + wrapped.onDidChangeSelection((selectedItems: Array) => { + if (options.onDidChangeSelection) { + options.onDidChangeSelection(wrapped, selectedItems); + } + }); + } + + wrapped.items = items; + wrapped.show(); + }).then(item => { + if (item?.execute) { + item.execute(); + } + return item; + }); + } + wrapQuickPick(wrapped: monaco.quickInput.IQuickPick>): QuickPick { + return new MonacoQuickPick(wrapped, this.keybindingRegistry); + } + + protected convertItems(item: T): MonacoQuickPickItem { + return new MonacoQuickPickItem(item, this.keybindingRegistry); + } + + hide(): void { + return this.monacoService.hide(); + } +} + +class MonacoQuickInput { + constructor(protected readonly wrapped: monaco.quickInput.IQuickInput) { + } + + readonly onDidHide: Event = this.wrapped.onDidHide; + readonly onDispose: Event = this.wrapped.onDispose; + + get title(): string | undefined { + return this.wrapped.title; + } + + set title(v: string | undefined) { + this.wrapped.title = v; + } + + get description(): string | undefined { + return this.wrapped.description; + } + + set description(v: string | undefined) { + this.wrapped.description = v; + } + get step(): number | undefined { + return this.wrapped.step; + } + + set step(v: number | undefined) { + this.wrapped.step = v; + } + + get enabled(): boolean { + return this.wrapped.enabled; + } + + set enabled(v: boolean) { + this.wrapped.enabled = v; + } + get totalSteps(): number | undefined { + return this.wrapped.totalSteps; + } + + set totalSteps(v: number | undefined) { + this.wrapped.totalSteps = v; + } + get contextKey(): string | undefined { + return this.wrapped.contextKey; + } + + set contextKey(v: string | undefined) { + this.wrapped.contextKey = v; + } + + get busy(): boolean { + return this.wrapped.busy; + } + + set busy(v: boolean) { + this.wrapped.busy = v; + } + + get ignoreFocusOut(): boolean { + return this.wrapped.ignoreFocusOut; + } + + set ignoreFocusOut(v: boolean) { + this.wrapped.ignoreFocusOut = v; + } + + show(): void { + this.wrapped.show(); + } + hide(): void { + this.wrapped.hide(); + } + dispose(): void { + this.wrapped.dispose(); + } + +} + +class MonacoQuickPick extends MonacoQuickInput implements QuickPick { + constructor(protected readonly wrapped: monaco.quickInput.IQuickPick>, protected readonly keybindingRegistry: KeybindingRegistry) { + super(wrapped); + } + + get value(): string { + return this.wrapped.value; + }; + + set value(v: string) { + this.wrapped.value = v; + } + + get placeholder(): string | undefined { + return this.wrapped.placeholder; + } + + set placeholder(v: string | undefined) { + this.wrapped.placeholder = v; + } + + get canSelectMany(): boolean { + return this.wrapped.canSelectMany; + } + + set canSelectMany(v: boolean) { + this.wrapped.canSelectMany = v; + } + + get matchOnDescription(): boolean { + return this.wrapped.matchOnDescription; + } + + set matchOnDescription(v: boolean) { + this.wrapped.matchOnDescription = v; + } + + get matchOnDetail(): boolean { + return this.wrapped.matchOnDetail; + } + + set matchOnDetail(v: boolean) { + this.wrapped.matchOnDetail = v; + } + + get items(): readonly (T | QuickPickSeparator)[] { + return this.wrapped.items.map(item => QuickPickSeparator.is(item) ? item : item.item); + } + + set items(itms: readonly (T | QuickPickSeparator)[]) { + this.wrapped.items = itms.map(item => QuickPickSeparator.is(item) ? item : new MonacoQuickPickItem(item, this.keybindingRegistry)); + } + + set activeItems(itms: readonly T[]) { + this.wrapped.activeItems = itms.map(item => new MonacoQuickPickItem(item, this.keybindingRegistry)); + } + + get activeItems(): readonly (T)[] { + return this.wrapped.activeItems.map(item => item.item); + } + + set selectedItems(itms: readonly T[]) { + this.wrapped.selectedItems = itms.map(item => new MonacoQuickPickItem(item, this.keybindingRegistry)); + } + + get selectedItems(): readonly (T)[] { + return this.wrapped.selectedItems.map(item => item.item); + } + + readonly onDidAccept: Event = this.wrapped.onDidAccept; + readonly onDidChangeValue: Event = this.wrapped.onDidChangeValue; + readonly onDidTriggerButton: Event = this.wrapped.onDidTriggerButton; + readonly onDidTriggerItemButton: Event> = + Event.map(this.wrapped.onDidTriggerItemButton, (evt: monaco.quickInput.IQuickPickItemButtonEvent>) => ({ + item: evt.item.item, + button: evt.button + })); + readonly onDidChangeActive: Event = Event.map(this.wrapped.onDidChangeActive, (items: MonacoQuickPickItem[]) => items.map(item => item.item)); + readonly onDidChangeSelection: Event = Event.map(this.wrapped.onDidChangeSelection, (items: MonacoQuickPickItem[]) => items.map(item => item.item)); +} + +export class MonacoQuickPickItem implements monaco.quickInput.IQuickPickItem { + readonly type?: 'item' | 'separator'; + readonly id?: string; + readonly label: string; + readonly meta?: string; + readonly ariaLabel?: string; + readonly description?: string; + readonly detail?: string; + readonly keybinding?: monaco.keybindings.ResolvedKeybinding; + readonly iconClasses?: string[]; + buttons?: monaco.quickInput.IQuickInputButton[]; + readonly alwaysShow?: boolean; + readonly highlights?: QuickPickItemHighlights; + + constructor(readonly item: T, kbRegistry: KeybindingRegistry) { + this.type = item.type; + this.id = item.id; + this.label = item.label; + this.meta = item.meta; + this.ariaLabel = item.ariaLabel; + this.description = item.description; + this.detail = item.detail; + this.keybinding = item.keySequence ? new MonacoResolvedKeybinding(item.keySequence, kbRegistry) : undefined; + this.iconClasses = item.iconClasses; + this.buttons = item.buttons; + this.alwaysShow = item.alwaysShow; + this.highlights = item.highlights; + } + + accept(): void { + if (this.item.execute) { + this.item.execute(); + } + } +} + diff --git a/packages/monaco/src/browser/monaco-quick-view-service.ts b/packages/monaco/src/browser/monaco-quick-view-service.ts deleted file mode 100644 index ababfaa6c5c9b..0000000000000 --- a/packages/monaco/src/browser/monaco-quick-view-service.ts +++ /dev/null @@ -1,97 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 SAP SE or an SAP affiliate company and others. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the Eclipse - * Public License v. 2.0 are satisfied: GNU General Public License, version 2 - * with the GNU Classpath Exception which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - ********************************************************************************/ - -import { inject, injectable } from '@theia/core/shared/inversify'; -import { QuickViewItem, QuickViewService } from '@theia/core/lib/browser/quick-input/quick-view-service'; -import { ContextKeyService } from '@theia/core/lib/browser/context-key-service'; -import { Disposable } from '@theia/core/lib/common'; -import { filterItems } from '@theia/core/lib/browser/quick-input/quick-input-service'; - -@injectable() -export class MonacoQuickViewService extends QuickViewService implements monaco.quickInput.IQuickAccessDataService { - protected readonly items: (monaco.quickInput.IAnythingQuickPickItem & { when?: string })[] = []; - private hiddenItemLabels = new Set(); - - @inject(ContextKeyService) - protected readonly contextKexService: ContextKeyService; - - registerItem(item: QuickViewItem): Disposable { - const quickOpenItem = { - label: item.label, - accept: () => item.open(), - when: item.when - }; - this.items.push(quickOpenItem); - this.items.sort((a, b) => a.label!.localeCompare(b.label!)); - - return Disposable.create(() => { - const index = this.items.indexOf(quickOpenItem); - if (index !== -1) { - this.items.splice(index, 1); - } - }); - } - - hideItem(label: string): void { - this.hiddenItemLabels.add(label); - } - - showItem(label: string): void { - this.hiddenItemLabels.delete(label); - } - - registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: ViewQuickAccessProvider, - prefix: ViewQuickAccessProvider.PREFIX, - placeholder: '', - helpEntries: [{ description: 'Open View', needsEditor: false }] - }); - ViewQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; - } - - getPicks(filter: string, token: monaco.CancellationToken): monaco.quickInput.Picks { - const items = this.items.filter(item => - (item.when === undefined || this.contextKexService.match(item.when)) && - (!this.hiddenItemLabels.has(item.label)) - ); - return filterItems(items, filter); - } -} - -export class ViewQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = 'view '; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching views' - }; - - constructor() { - super(ViewQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: ViewQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return ViewQuickAccessProvider.dataService?.getPicks(filter, token); - } -} diff --git a/packages/monaco/src/browser/workspace-symbol-command.ts b/packages/monaco/src/browser/workspace-symbol-command.ts index 5ad82258b4302..3613bd5e77eb4 100644 --- a/packages/monaco/src/browser/workspace-symbol-command.ts +++ b/packages/monaco/src/browser/workspace-symbol-command.ts @@ -16,22 +16,21 @@ import { injectable, inject } from '@theia/core/shared/inversify'; import { environment } from '@theia/core/shared/@theia/application-package/lib/environment'; -import { - KeybindingContribution, KeybindingRegistry, QuickAccessContribution, OpenerService, LabelProvider, findMatches -} from '@theia/core/lib/browser'; +import { KeybindingContribution, KeybindingRegistry, OpenerService, LabelProvider } from '@theia/core/lib/browser'; + +import { QuickAccessContribution, QuickAccessProvider, QuickInputService, QuickAccessRegistry, QuickPicks, QuickPickItem, findMatches } from '@theia/core/lib/browser/quick-input'; import { CommandRegistry, CommandHandler, Command, SelectionService, CancellationToken -} from '@theia/core'; +} from '@theia/core/lib/common'; import { CommandContribution } from '@theia/core/lib/common'; import { Range, Position, SymbolInformation } from '@theia/core/shared/vscode-languageserver-types'; import { WorkspaceSymbolParams } from '@theia/core/shared/vscode-languageserver-protocol'; import { MonacoLanguages, WorkspaceSymbolProvider } from './monaco-languages'; -import { MonacoQuickInputService } from './monaco-quick-input-service'; import URI from '@theia/core/lib/common/uri'; @injectable() -export class WorkspaceSymbolCommand implements monaco.quickInput.IQuickAccessDataService, CommandContribution, KeybindingContribution, CommandHandler, QuickAccessContribution { - readonly prefix = '#'; +export class WorkspaceSymbolCommand implements QuickAccessProvider, CommandContribution, KeybindingContribution, CommandHandler, QuickAccessContribution { + public static readonly PREFIX = '#'; private command: Command = { id: 'languages.workspace.symbol', @@ -40,7 +39,8 @@ export class WorkspaceSymbolCommand implements monaco.quickInput.IQuickAccessDat @inject(MonacoLanguages) protected readonly languages: MonacoLanguages; @inject(OpenerService) protected readonly openerService: OpenerService; - @inject(MonacoQuickInputService) protected monacoQuickInputService: MonacoQuickInputService; + @inject(QuickInputService) protected quickInputService: QuickInputService; + @inject(QuickAccessRegistry) protected quickAccessRegistry: QuickAccessRegistry; @inject(SelectionService) protected selectionService: SelectionService; @inject(LabelProvider) protected readonly labelProvider: LabelProvider; @@ -49,7 +49,7 @@ export class WorkspaceSymbolCommand implements monaco.quickInput.IQuickAccessDat } execute(): void { - this.monacoQuickInputService.open(this.prefix); + this.quickInputService.open(WorkspaceSymbolCommand.PREFIX); } registerCommands(commands: CommandRegistry): void { @@ -68,17 +68,16 @@ export class WorkspaceSymbolCommand implements monaco.quickInput.IQuickAccessDat } registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: SymbolsQuickAccessProvider, - prefix: SymbolsQuickAccessProvider.PREFIX, + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: WorkspaceSymbolCommand.PREFIX, placeholder: '', helpEntries: [{ description: 'Go to Symbol in Workspace', needsEditor: false }] }); - SymbolsQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; } - async getPicks(filter: string, token: CancellationToken): Promise> { - const items: Array = []; + async getPicks(filter: string, token: CancellationToken): Promise { + const items: QuickPicks = []; if (this.languages.workspaceSymbolProviders) { const param: WorkspaceSymbolParams = { query: filter @@ -109,7 +108,7 @@ export class WorkspaceSymbolCommand implements monaco.quickInput.IQuickAccessDat return items; } - protected createItem(sym: SymbolInformation, provider: WorkspaceSymbolProvider, filter: string, token: CancellationToken): monaco.quickInput.IAnythingQuickPickItem { + protected createItem(sym: SymbolInformation, provider: WorkspaceSymbolProvider, filter: string, token: CancellationToken): QuickPickItem { const uri = new URI(sym.location.uri); const iconClasses = this.toCssClassName(sym.kind); let parent = sym.containerName; @@ -126,7 +125,7 @@ export class WorkspaceSymbolCommand implements monaco.quickInput.IQuickAccessDat label: findMatches(sym.name, filter), description: findMatches(description, filter) }, - accept: () => { + execute: () => { if (provider.resolveWorkspaceSymbol) { provider.resolveWorkspaceSymbol(sym, token).then(resolvedSymbol => { if (resolvedSymbol) { @@ -187,27 +186,3 @@ enum SymbolKind { Operator = 25, TypeParameter = 26 } - -export class SymbolsQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = '#'; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching results' - }; - - constructor() { - super(SymbolsQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: SymbolsQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return SymbolsQuickAccessProvider.dataService?.getPicks(filter, token); - } -} diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index 9e05af5588312..4ef99c3d661f7 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -1451,7 +1451,6 @@ declare module monaco.quickInput { buttons?: IQuickInputButton[]; picked?: boolean; alwaysShow?: boolean; - execute?: (item: IQuickPickItem, lookFor: string) => void } // https://github.com/theia-ide/vscode/blob/standalone/0.23.x/src/vs/base/parts/quickinput/common/quickInput.ts#L306 @@ -1672,58 +1671,6 @@ declare module monaco.quickInput { onDidTriggerItemButton?: (context: IQuickPickItemButtonContext) => void; } - export interface IQuickPickOptions { - busy?: boolean; - enabled?: boolean; - title?: string; - description?: string; - value?: string; - filterValue?: (value: string) => string; - ariaLabel?: string; - buttons?: Array; - placeholder?: string; - canAcceptInBackground?: boolean; - customButton?: boolean; - customLabel?: string; - customHover?: string; - canSelectMany?: boolean; - matchOnDescription?: boolean; - matchOnDetail?: boolean; - matchOnLabel?: boolean; - sortByLabel?: boolean; - autoFocusOnList?: boolean; - ignoreFocusOut?: boolean; - quickNavigate?: monaco.quickInput.IQuickNavigateConfiguration; - itemActivation?: monaco.quickInput.ItemActivation; - valueSelection?: Readonly<[number, number]>; - validationMessage?: string; - hideInput?: boolean; - hideCheckAll?: boolean; - runIfSingle?: boolean - contextKey?: string; - activeItem?: T, - step?: number; - totalSteps?: number; - - onDidAccept?: () => void, - onDidChangeActive?: (quickPick: IQuickPick, activeItems: Array) => void, - onDidChangeSelection?: (quickPick: IQuickPick, selectedItems: Array) => void, - onDidChangeValue?: (quickPick: IQuickPick, filter: string) => void, - onDidCustom?: () => void, - onDidHide?: () => void, - onDidTriggerButton?: Event; - onDidTriggerItemButton?: Event>; - } - - export interface IQuickAccessDataService { - getPicks(filter: string, token: CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null; - registerQuickAccessProvider(): void; - } - - // https://github.com/theia-ide/vscode/blob/standalone/0.23.x/src/vs/platform/quickinput/browser/pickerQuickAccess.ts#L75 export type Pick = T | IQuickPickSeparator; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type PicksWithActive = { items: Array>, active?: T }; @@ -1733,9 +1680,8 @@ declare module monaco.quickInput { // https://github.com/theia-ide/vscode/blob/standalone/0.23.x/src/vs/platform/quickinput/browser/pickerQuickAccess.ts#L92 export class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { - constructor(private prefix: string, protected options?: IPickerQuickAccessProviderOptions) { - super(); - } + constructor(prefix: string, options?: IPickerQuickAccessProviderOptions); + provide(picker: IQuickPick, token: CancellationToken): IDisposable; protected getPicks(filter: string, disposables: any, token: CancellationToken): Picks | Promise> | FastAndSlowPicks | null; } @@ -1851,7 +1797,24 @@ declare module monaco.quickInput { provide(picker: monaco.quickInput.IQuickPick, token: CancellationToken): IDisposable; } - // https://github.com/theia-ide/vscode/blob/standalone/0.23.x/src/vs/platform/quickinput/common/quickAccess.ts#L101 + export interface IQuickAccessProviderHelp { + + /** + * The prefix to show for the help entry. If not provided, + * the prefix used for registration will be taken. + */ + prefix?: string; + + /** + * A description text to help understand the intent of the provider. + */ + description: string; + + /** + * Separation between provider for editors and global ones. + */ + needsEditor: boolean; + } export interface IQuickAccessProviderDescriptor { /** * The actual provider that will be instantiated as needed. diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index e669d93b506a0..39186e2ef1260 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -93,7 +93,7 @@ import type { import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol'; import { ThemeType } from '@theia/core/lib/browser/theming'; import { Disposable } from '@theia/core/lib/common/disposable'; -import { IPickOptions, QuickInputButtonHandle, QuickPickItem } from '@theia/core/lib/browser'; +import { PickOptions, QuickInputButtonHandle, QuickPickItem } from '@theia/core/lib/browser'; export interface PreferenceData { [scope: number]: any; @@ -600,7 +600,7 @@ export interface IInputBoxOptions { } export interface QuickOpenMain { - $show(instance: number, options: IPickOptions, token: CancellationToken): Promise; + $show(instance: number, options: PickOptions, token: CancellationToken): Promise; $setItems(instance: number, items: TransferQuickPickItems[]): Promise; $setError(instance: number, error: Error): Promise; $input(options: theia.InputBoxOptions, validateInput: boolean, token: CancellationToken): Promise; diff --git a/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts b/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts index 8c920dbe03b69..367e4fe7b8788 100644 --- a/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts +++ b/packages/plugin-ext/src/main/browser/plugin-ext-deploy-command.ts @@ -17,7 +17,7 @@ import { injectable, inject, optional } from '@theia/core/shared/inversify'; import { PluginServer } from '../../common'; import { Command } from '@theia/core/lib/common/command'; -import { QuickInputService } from '@theia/core/lib/browser'; +import { QuickInputService, QuickPick, QuickPickItem } from '@theia/core/lib/browser'; @injectable() export class PluginExtDeployCommandService /* implements QuickOpenModel */ { @@ -37,8 +37,7 @@ export class PluginExtDeployCommandService /* implements QuickOpenModel */ { this.quickInputService?.showQuickPick([], { placeholder: "Plugin's id to deploy.", - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onDidChangeValue: (quickPick: any, filter: string) => { + onDidChangeValue: (quickPick: QuickPick, filter: string) => { quickPick.items = [{ label: filter, detail: 'Deploy this plugin', diff --git a/packages/plugin-ext/src/main/browser/quick-open-main.ts b/packages/plugin-ext/src/main/browser/quick-open-main.ts index 4737a6088a318..2e6482e11eec6 100644 --- a/packages/plugin-ext/src/main/browser/quick-open-main.ts +++ b/packages/plugin-ext/src/main/browser/quick-open-main.ts @@ -28,7 +28,9 @@ import { TransferQuickInput, TransferQuickInputButton } from '../../common/plugin-api-rpc'; -import { IInputOptions, IPickOptions, QuickInputButton, QuickInputService, QuickPickItem, QuickPickValue } from '@theia/core/lib/browser'; +import { + InputOptions, PickOptions, QuickInputButton, QuickInputService, QuickPickItem, QuickPickValue +} from '@theia/core/lib/browser'; import { QuickPickService } from '@theia/core/lib/common/quick-pick-service'; import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; @@ -66,7 +68,7 @@ export class QuickOpenMainImpl implements QuickOpenMain, Disposable { this.toDispose.dispose(); } - async $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { + async $show(instance: number, options: PickOptions, token: CancellationToken): Promise { const contents = new Promise((resolve, reject) => { this.items[instance] = { resolve, reject }; }); @@ -114,7 +116,7 @@ export class QuickOpenMainImpl implements QuickOpenMain, Disposable { } $input(options: InputBoxOptions, validateInput: boolean, token: CancellationToken): Promise { - const inputOptions: IInputOptions = Object.create(null); + const inputOptions: InputOptions = Object.create(null); if (options) { inputOptions.password = options.password; diff --git a/packages/task/src/browser/quick-open-task.ts b/packages/task/src/browser/quick-open-task.ts index c74a62f745a1a..4471fca689e54 100644 --- a/packages/task/src/browser/quick-open-task.ts +++ b/packages/task/src/browser/quick-open-task.ts @@ -19,14 +19,15 @@ import { TaskService } from './task-service'; import { TaskInfo, TaskConfiguration, TaskCustomization, TaskScope, TaskConfigurationScope } from '../common/task-protocol'; import { TaskDefinitionRegistry } from './task-definition-registry'; import URI from '@theia/core/lib/common/uri'; -import { LabelProvider, QuickInputService } from '@theia/core/lib/browser'; +import { LabelProvider, QuickAccessProvider, QuickAccessRegistry, QuickInputService } from '@theia/core/lib/browser'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service'; import { PreferenceService } from '@theia/core/lib/browser'; import { TaskNameResolver } from './task-name-resolver'; import { TaskSourceResolver } from './task-source-resolver'; import { TaskConfigurationManager } from './task-configuration-manager'; -import { filterItems, QuickInputButton, QuickPickItem } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { filterItems, QuickInputButton, QuickPickItem, QuickPicks } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { CancellationToken } from '@theia/core/lib/common'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -34,8 +35,8 @@ export namespace ConfigureTaskAction { } @injectable() -export class QuickOpenTask implements monaco.quickInput.IQuickAccessDataService { - readonly prefix: string = 'task '; +export class QuickOpenTask implements QuickAccessProvider { + static readonly PREFIX = 'task '; readonly description: string = 'Run Task'; protected items: Array = []; @@ -45,6 +46,9 @@ export class QuickOpenTask implements monaco.quickInput.IQuickAccessDataService @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; + @inject(WorkspaceService) protected readonly workspaceService: WorkspaceService; @@ -292,19 +296,20 @@ export class QuickOpenTask implements monaco.quickInput.IQuickAccessDataService }); } - async getPicks(filter: string, token: monaco.CancellationToken): Promise> { - await this.init(); + async getPicks(filter: string, token: CancellationToken): Promise { + if (this.items.length === 0) { + await this.init(); + } return filterItems(this.items, filter); } registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: TaskQuickAccessProvider, - prefix: TaskQuickAccessProvider.PREFIX, + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: QuickOpenTask.PREFIX, placeholder: 'Select the task to run', helpEntries: [{ description: 'Run Task', needsEditor: false }] }); - TaskQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; } protected getRunningTaskLabel(task: TaskInfo): string { @@ -312,8 +317,8 @@ export class QuickOpenTask implements monaco.quickInput.IQuickAccessDataService } private getItems(tasks: TaskConfiguration[], groupLabel: string, token: number, isMulti: boolean): - Array { - const items: Array = tasks.map(task => + QuickPickItem[] { + const items: QuickPickItem[] = tasks.map(task => new TaskRunQuickOpenItem(token, task, this.taskService, isMulti, this.taskDefinitionRegistry, this.taskNameResolver, this.taskSourceResolver, this.taskConfigurationManager, [{ iconClass: 'codicon-gear', @@ -377,7 +382,7 @@ export class QuickOpenTask implements monaco.quickInput.IQuickAccessDataService } } -export class TaskRunQuickOpenItem implements monaco.quickInput.IAnythingQuickPickItem { +export class TaskRunQuickOpenItem implements QuickPickItem { constructor( public readonly token: number, public readonly task: TaskConfiguration, @@ -402,10 +407,6 @@ export class TaskRunQuickOpenItem implements monaco.quickInput.IAnythingQuickPic return this.task.detail; } - accept(): void { - this.execute(); - } - execute(): void { const scope = this.task._scope; if (this.taskDefinitionRegistry && !!this.taskDefinitionRegistry.getDefinition(this.task)) { @@ -435,9 +436,6 @@ export class ConfigureBuildOrTestTaskQuickOpenItem extends TaskRunQuickOpenItem ) { super(token, task, taskService, isMulti, taskDefinitionRegistry, taskNameResolver, taskSourceResolver, taskConfigurationManager); } - accept(): void { - this.execute(); - } execute(): void { this.taskService.updateTaskConfiguration(this.token, this.task, { group: { kind: this.isBuildTask ? 'build' : 'test', isDefault: true } }) @@ -461,7 +459,7 @@ function renderScope(scope: TaskConfigurationScope, isMulti: boolean): string { } } -export class TaskConfigureQuickOpenItem implements monaco.quickInput.IAnythingQuickPickItem { +export class TaskConfigureQuickOpenItem implements QuickPickItem { protected taskDefinitionRegistry: TaskDefinitionRegistry; @@ -702,26 +700,3 @@ export class TaskRestartRunningQuickOpen { } } -export class TaskQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = 'task '; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching tasks' - }; - - constructor() { - super(TaskQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: TaskQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return TaskQuickAccessProvider.dataService?.getPicks(filter, token); - } -} diff --git a/packages/task/src/browser/task-frontend-module.ts b/packages/task/src/browser/task-frontend-module.ts index 56009843b4431..2bb52b4f81de4 100644 --- a/packages/task/src/browser/task-frontend-module.ts +++ b/packages/task/src/browser/task-frontend-module.ts @@ -41,7 +41,7 @@ import { TaskSourceResolver } from './task-source-resolver'; import { TaskTemplateSelector } from './task-templates'; import { TaskTerminalWidgetManager } from './task-terminal-widget-manager'; import { JsonSchemaContribution } from '@theia/core/lib/browser/json-schema-store'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; +import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access'; export default new ContainerModule(bind => { bind(TaskFrontendContribution).toSelf().inSingletonScope(); diff --git a/packages/terminal/src/browser/terminal-frontend-module.ts b/packages/terminal/src/browser/terminal-frontend-module.ts index bd2ef52b6c720..677d3d158aa18 100644 --- a/packages/terminal/src/browser/terminal-frontend-module.ts +++ b/packages/terminal/src/browser/terminal-frontend-module.ts @@ -42,7 +42,7 @@ import { createTerminalSearchFactory } from './search/terminal-search-container' import { TerminalCopyOnSelectionHandler } from './terminal-copy-on-selection-handler'; import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution'; import { TerminalThemeService } from './terminal-theme-service'; -import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access-contribution'; +import { QuickAccessContribution } from '@theia/core/lib/browser/quick-input/quick-access'; export default new ContainerModule(bind => { bindTerminalPreferences(bind); diff --git a/packages/terminal/src/browser/terminal-quick-open-service.ts b/packages/terminal/src/browser/terminal-quick-open-service.ts index e5d63cf508291..b16d494f0fdba 100644 --- a/packages/terminal/src/browser/terminal-quick-open-service.ts +++ b/packages/terminal/src/browser/terminal-quick-open-service.ts @@ -17,20 +17,26 @@ import { inject, injectable, optional } from '@theia/core/shared/inversify'; import { QuickAccessContribution, + QuickAccessProvider, + QuickAccessRegistry, QuickInputService } from '@theia/core/lib/browser'; -import { CommandContribution, CommandRegistry, CommandService } from '@theia/core/lib/common'; +import { CancellationToken, CommandContribution, CommandRegistry, CommandService } from '@theia/core/lib/common'; import { TerminalWidget } from './base/terminal-widget'; import { TerminalService } from './base/terminal-service'; import { TerminalCommands } from './terminal-frontend-contribution'; -import { filterItems } from '@theia/core/lib/browser/quick-input/quick-input-service'; +import { filterItems, QuickPickItem, QuickPicks } from '@theia/core/lib/browser/quick-input/quick-input-service'; @injectable() -export class TerminalQuickOpenService implements monaco.quickInput.IQuickAccessDataService { +export class TerminalQuickOpenService implements QuickAccessProvider { + static readonly PREFIX = 'term '; @inject(QuickInputService) @optional() protected readonly quickInputService: QuickInputService; + @inject(QuickAccessRegistry) + protected readonly quickAccessRegistry: QuickAccessRegistry; + @inject(CommandService) protected readonly commandService: CommandService; @@ -38,11 +44,11 @@ export class TerminalQuickOpenService implements monaco.quickInput.IQuickAccessD protected readonly terminalService: TerminalService; open(): void { - this.quickInputService?.open(TerminalQuickAccessProvider.PREFIX); + this.quickInputService?.open(TerminalQuickOpenService.PREFIX); } - async getPicks(filter: string, token: monaco.CancellationToken): Promise> { - const items: Array = []; + async getPicks(filter: string, token: CancellationToken): Promise { + const items: QuickPickItem[] = []; // Get the sorted list of currently opened terminal widgets const widgets: TerminalWidget[] = this.terminalService.all @@ -55,20 +61,19 @@ export class TerminalQuickOpenService implements monaco.quickInput.IQuickAccessD items.push({ label: 'Open New Terminal', iconClasses: ['fa fa-plus'], - accept: () => this.doCreateNewTerminal() + execute: () => this.doCreateNewTerminal() }); return filterItems(items, filter); } registerQuickAccessProvider(): void { - monaco.platform.Registry.as('workbench.contributions.quickaccess').registerQuickAccessProvider({ - ctor: TerminalQuickAccessProvider, - prefix: TerminalQuickAccessProvider.PREFIX, + this.quickAccessRegistry.registerQuickAccessProvider({ + getInstance: () => this, + prefix: TerminalQuickOpenService.PREFIX, placeholder: '', helpEntries: [{ description: 'Show All Opened Terminals', needsEditor: false }] }); - TerminalQuickAccessProvider.dataService = this as monaco.quickInput.IQuickAccessDataService; } /** @@ -95,12 +100,12 @@ export class TerminalQuickOpenService implements monaco.quickInput.IQuickAccessD * @param {TerminalWidget} widget - the terminal widget. * @returns quick pick item. */ - protected toItem(widget: TerminalWidget): monaco.quickInput.IAnythingQuickPickItem { + protected toItem(widget: TerminalWidget): QuickPickItem { return { label: widget.title.label, description: widget.id, ariaLabel: widget.title.label, - accept: () => this.terminalService.open(widget) + execute: () => this.terminalService.open(widget) }; } } @@ -124,27 +129,3 @@ export class TerminalQuickOpenContribution implements CommandContribution, Quick }); } } - -export class TerminalQuickAccessProvider extends monaco.quickInput.PickerQuickAccessProvider { - static PREFIX = 'term '; - static dataService: monaco.quickInput.IQuickAccessDataService; - - private static readonly NO_RESULTS_PICK: monaco.quickInput.IAnythingQuickPickItem = { - label: 'No matching results' - }; - - constructor() { - super(TerminalQuickAccessProvider.PREFIX, { - canAcceptInBackground: true, - noResultsPick: TerminalQuickAccessProvider.NO_RESULTS_PICK - }); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - getPicks(filter: string, disposables: any, token: monaco.CancellationToken): monaco.quickInput.Picks - | Promise> - | monaco.quickInput.FastAndSlowPicks - | null { - return TerminalQuickAccessProvider.dataService?.getPicks(filter, token); - } -}