From 150c15874ca14217061caa183349ff505410b015 Mon Sep 17 00:00:00 2001 From: fangnx Date: Thu, 20 Jun 2019 13:18:55 -0400 Subject: [PATCH] Fixed #5543: implemented "show all opened terminals" quick-open command - Implemented the "Show All Opened Terminals" command in the quick command menu, which can display all the currently opened terminal widgets. - User can navigate to each terminal widget by selection, and has the option to create a new terminal. - The command has a global prefix: `term `, to be aligned to VS Code. Signed-off-by: fangnx --- .../browser/terminal-frontend-contribution.ts | 8 + .../src/browser/terminal-frontend-module.ts | 10 +- .../browser/terminal-quick-open-service.ts | 173 ++++++++++++++++++ 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 packages/terminal/src/browser/terminal-quick-open-service.ts diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index 0ba104f17209a..5b496b0d76c12 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -79,6 +79,14 @@ export namespace TerminalCommands { category: TERMINAL_CATEGORY, label: 'Split Terminal' }; + /** + * Command that displays all terminals that are currently opened + */ + export const SHOW_ALL_OPENED_TERMINALS: Command = { + id: 'workbench.action.showAllTerminals', + category: 'View', + label: 'Show All Opened Terminals' + }; } @injectable() diff --git a/packages/terminal/src/browser/terminal-frontend-module.ts b/packages/terminal/src/browser/terminal-frontend-module.ts index 46f76b7a0cc84..8801cc8e73691 100644 --- a/packages/terminal/src/browser/terminal-frontend-module.ts +++ b/packages/terminal/src/browser/terminal-frontend-module.ts @@ -17,7 +17,7 @@ import { ContainerModule, Container } from 'inversify'; import { CommandContribution, MenuContribution } from '@theia/core/lib/common'; import { bindContributionProvider } from '@theia/core'; -import { KeybindingContribution, WebSocketConnectionProvider, WidgetFactory, KeybindingContext } from '@theia/core/lib/browser'; +import { KeybindingContribution, WebSocketConnectionProvider, WidgetFactory, KeybindingContext, QuickOpenContribution } from '@theia/core/lib/browser'; import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { TerminalFrontendContribution } from './terminal-frontend-contribution'; import { TerminalWidgetImpl, TERMINAL_WIDGET_FACTORY_ID } from './terminal-widget-impl'; @@ -33,6 +33,7 @@ import { URLMatcher, LocalhostMatcher } from './terminal-linkmatcher'; import { TerminalContribution } from './terminal-contribution'; import { TerminalLinkmatcherFiles } from './terminal-linkmatcher-files'; import { TerminalLinkmatcherDiffPre, TerminalLinkmatcherDiffPost } from './terminal-linkmatcher-diff'; +import { TerminalQuickOpenService, TerminalQuickOpenContribution } from './terminal-quick-open-service'; import '../../src/browser/terminal.css'; import 'xterm/lib/xterm.css'; @@ -65,6 +66,13 @@ export default new ContainerModule(bind => { } })); + bind(TerminalQuickOpenService).toSelf().inSingletonScope(); + + bind(TerminalQuickOpenContribution).toSelf().inSingletonScope(); + for (const identifier of [CommandContribution, QuickOpenContribution]) { + bind(identifier).toService(TerminalQuickOpenContribution); + } + bind(TerminalFrontendContribution).toSelf().inSingletonScope(); bind(TerminalService).toService(TerminalFrontendContribution); for (const identifier of [CommandContribution, MenuContribution, KeybindingContribution, TabBarToolbarContribution]) { diff --git a/packages/terminal/src/browser/terminal-quick-open-service.ts b/packages/terminal/src/browser/terminal-quick-open-service.ts new file mode 100644 index 0000000000000..d0ed9bce9234c --- /dev/null +++ b/packages/terminal/src/browser/terminal-quick-open-service.ts @@ -0,0 +1,173 @@ +/******************************************************************************** + * Copyright (C) 2019 Ericsson 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 { + QuickOpenModel, QuickOpenGroupItem, QuickOpenHandler, + QuickOpenOptions, QuickOpenItemOptions, QuickOpenMode, + PrefixQuickOpenService, + QuickOpenContribution, QuickOpenHandlerRegistry, QuickOpenGroupItemOptions +} from '@theia/core/lib/browser'; +import { 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'; + +@injectable() +export class TerminalQuickOpenService implements QuickOpenModel, QuickOpenHandler { + + @inject(PrefixQuickOpenService) + protected readonly prefixQuickOpenService: PrefixQuickOpenService; + + @inject(CommandService) + protected readonly commandService: CommandService; + + @inject(TerminalService) + protected readonly terminalService: TerminalService; + + readonly prefix: string = 'term '; + + get description(): string { + return 'Show All Opened Terminals'; + } + + getModel(): QuickOpenModel { + return this; + } + + getOptions(): QuickOpenOptions { + return { + fuzzyMatchLabel: { + enableSeparateSubstringMatching: true + }, + fuzzyMatchDescription: { + enableSeparateSubstringMatching: true + } + }; + } + + open(): void { + this.prefixQuickOpenService.open(this.prefix); + } + + async onType(lookFor: string, acceptor: (items: QuickOpenGroupItem[]) => void): Promise { + const terminalItems: QuickOpenGroupItem[] = []; + + // Get the sorted list of currently opened terminal widgets + const widgets: TerminalWidget[] = this.terminalService.all + .sort((a: TerminalWidget, b: TerminalWidget) => this.compareItems(a, b)); + + for (const widget of widgets) { + const item = await this.toItem(widget); + terminalItems.push(item); + } + // Append a quick open item to create a new terminal. + const createNewTerminalItem = new QuickOpenGroupItem({ + label: 'Open New Terminal', + iconClass: 'fa fa-plus', + run: this.doCreateNewTerminal(), + groupLabel: undefined, + showBorder: !!terminalItems.length + }); + terminalItems.push(createNewTerminalItem); + + acceptor(terminalItems); + return; + } + + /** + * Compare two terminal widgets by label. If labels are identical, compare by the widget id. + * @param a `TerminalWidget` for comparison + * @param b `TerminalWidget` for comparison + */ + protected compareItems(a: TerminalWidget, b: TerminalWidget): number { + const normalize = (str: string) => str.trim().toLowerCase(); + + if (normalize(a.title.label) !== normalize(b.title.label)) { + return normalize(a.title.label).localeCompare(normalize(b.title.label)); + } else { + return normalize(a.id).localeCompare(normalize(b.id)); + } + } + + /** + * Get the function that can create a new terminal. + * @param {TerminalWidget} widget - the terminal widget to be opened. + * @returns Function that would create a new terminal if mode === QuickOpenMode.OPEN. + */ + protected doCreateNewTerminal(): (mode: QuickOpenMode) => boolean { + return (mode: QuickOpenMode) => { + if (mode !== QuickOpenMode.OPEN) { + return false; + } + this.commandService.executeCommand(TerminalCommands.NEW.id); + return true; + }; + } + + /** + * Convert the terminal widget to the quick open item. + * @param {TerminalWidget} widget - the terminal widget. + * @returns The quick open group item. + */ + protected async toItem(widget: TerminalWidget): Promise> { + const options: QuickOpenGroupItemOptions = { + label: widget.title.label, + description: widget.id, + tooltip: widget.title.label, + hidden: false, + run: this.getRunFunction(widget), + groupLabel: undefined, + showBorder: false + }; + return new QuickOpenGroupItem(options); + } + + /** + * Get the function that can open the editor file. + * @param {TerminalWidget} widget - the terminal widget to be opened. + * @returns Function that would open the terminal if mode === QuickOpenMode.OPEN. + */ + protected getRunFunction(widget: TerminalWidget): (mode: QuickOpenMode) => boolean { + return (mode: QuickOpenMode) => { + if (mode !== QuickOpenMode.OPEN) { + return false; + } + this.terminalService.open(widget); + return true; + }; + } +} + +/** + * TODO: merge it to TerminalFrontendContribution. + */ +@injectable() +export class TerminalQuickOpenContribution implements CommandContribution, QuickOpenContribution { + + @inject(TerminalQuickOpenService) + protected readonly terminalQuickOpenService: TerminalQuickOpenService; + + registerQuickOpenHandlers(handlers: QuickOpenHandlerRegistry): void { + handlers.registerHandler(this.terminalQuickOpenService); + } + + registerCommands(commands: CommandRegistry): void { + commands.registerCommand(TerminalCommands.SHOW_ALL_OPENED_TERMINALS, { + execute: () => this.terminalQuickOpenService.open() + }); + } +}