diff --git a/packages/terminal/src/browser/base/terminal-widget.ts b/packages/terminal/src/browser/base/terminal-widget.ts index 84a458078b3be..897ed69355cc5 100644 --- a/packages/terminal/src/browser/base/terminal-widget.ts +++ b/packages/terminal/src/browser/base/terminal-widget.ts @@ -137,6 +137,11 @@ export abstract class TerminalWidget extends BaseWidget { */ abstract clearOutput(): void; + /** + * Select entire content in the terminal. + */ + abstract selectAll(): void; + abstract writeLine(line: string): void; abstract write(data: string): void; diff --git a/packages/terminal/src/browser/terminal-frontend-contribution.ts b/packages/terminal/src/browser/terminal-frontend-contribution.ts index 1b1b4555442ac..c8348d1761523 100644 --- a/packages/terminal/src/browser/terminal-frontend-contribution.ts +++ b/packages/terminal/src/browser/terminal-frontend-contribution.ts @@ -31,8 +31,8 @@ import { } from '@theia/core/lib/common'; import { ApplicationShell, KeybindingContribution, KeyCode, Key, WidgetManager, PreferenceService, - KeybindingRegistry, Widget, LabelProvider, WidgetOpenerOptions, StorageService, - QuickInputService, codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope + KeybindingRegistry, LabelProvider, WidgetOpenerOptions, StorageService, QuickInputService, + codicon, CommonCommands, FrontendApplicationContribution, OnWillStopAction, Dialog, ConfirmDialog, FrontendApplication, PreferenceScope } from '@theia/core/lib/browser'; import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { TERMINAL_WIDGET_FACTORY_ID, TerminalWidgetFactoryOptions, TerminalWidgetImpl } from './terminal-widget-impl'; @@ -70,6 +70,8 @@ export namespace TerminalMenus { export const TERMINAL_TASKS_CONFIG = [...TERMINAL_TASKS, '4_terminal']; export const TERMINAL_NAVIGATOR_CONTEXT_MENU = ['navigator-context-menu', 'navigation']; export const TERMINAL_OPEN_EDITORS_CONTEXT_MENU = ['open-editors-context-menu', 'navigation']; + + export const TERMINAL_CONTEXT_MENU = ['terminal-context-menu']; } export namespace TerminalCommands { @@ -150,6 +152,16 @@ export namespace TerminalCommands { category: TERMINAL_CATEGORY, label: 'Toggle Terminal' }); + export const KILL_TERMINAL = Command.toDefaultLocalizedCommand({ + id: 'terminal:kill', + category: TERMINAL_CATEGORY, + label: 'Kill Terminal' + }); + export const SELECT_ALL: Command = { + id: 'terminal:select:all', + label: CommonCommands.SELECT_ALL.label, + category: TERMINAL_CATEGORY, + }; /** * Command that displays all terminals that are currently opened @@ -524,9 +536,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu execute: () => this.openActiveWorkspaceTerminal() }); commands.registerCommand(TerminalCommands.SPLIT, { - execute: widget => this.splitTerminal(widget), - isEnabled: widget => !!this.getTerminalRef(widget), - isVisible: widget => !!this.getTerminalRef(widget) + execute: () => this.splitTerminal() }); commands.registerCommand(TerminalCommands.TERMINAL_CLEAR); commands.registerHandler(TerminalCommands.TERMINAL_CLEAR.id, { @@ -601,6 +611,14 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu commands.registerCommand(TerminalCommands.TOGGLE_TERMINAL, { execute: () => this.toggleTerminal() }); + commands.registerCommand(TerminalCommands.KILL_TERMINAL, { + isEnabled: () => !!this.currentTerminal, + execute: () => this.currentTerminal?.close() + }); + commands.registerCommand(TerminalCommands.SELECT_ALL, { + isEnabled: () => !!this.currentTerminal, + execute: () => this.currentTerminal?.selectAll() + }); } protected toggleTerminal(): void { @@ -679,6 +697,29 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu commandId: TerminalCommands.TERMINAL_CONTEXT.id, order: 'z' }); + + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_1'], { + commandId: TerminalCommands.NEW_ACTIVE_WORKSPACE.id, + label: nls.localizeByDefault('New Terminal') + }); + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_1'], { + commandId: TerminalCommands.SPLIT.id + }); + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], { + commandId: CommonCommands.COPY.id + }); + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], { + commandId: CommonCommands.PASTE.id + }); + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_2'], { + commandId: TerminalCommands.SELECT_ALL.id + }); + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_3'], { + commandId: TerminalCommands.TERMINAL_CLEAR.id + }); + menus.registerMenuAction([...TerminalMenus.TERMINAL_CONTEXT_MENU, '_4'], { + commandId: TerminalCommands.KILL_TERMINAL.id + }); } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { @@ -701,7 +742,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu keybindings.registerKeybinding({ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: KeyCode.createKeyCode({ key: k, ctrl: true }).toString(), - context: TerminalKeybindingContexts.terminalActive, + when: 'terminalFocus', }); }; @@ -711,7 +752,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu keybindings.registerKeybinding({ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: KeyCode.createKeyCode({ key: k, alt: true }).toString(), - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); }; @@ -770,7 +811,7 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu keybindings.registerKeybinding({ command: KeybindingRegistry.PASSTHROUGH_PSEUDO_COMMAND, keybinding: 'ctrlcmd+a', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); } @@ -785,12 +826,12 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu keybindings.registerKeybinding({ command: TerminalCommands.TERMINAL_CLEAR.id, keybinding: 'ctrlcmd+k', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.TERMINAL_FIND_TEXT.id, keybinding: 'ctrlcmd+f', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.TERMINAL_FIND_TEXT_CANCEL.id, @@ -800,32 +841,37 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu keybindings.registerKeybinding({ command: TerminalCommands.SCROLL_LINE_UP.id, keybinding: 'ctrl+shift+up', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.SCROLL_LINE_DOWN.id, keybinding: 'ctrl+shift+down', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.SCROLL_TO_TOP.id, keybinding: 'shift-home', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.SCROLL_PAGE_UP.id, keybinding: 'shift-pageUp', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.SCROLL_PAGE_DOWN.id, keybinding: 'shift-pageDown', - context: TerminalKeybindingContexts.terminalActive + when: 'terminalFocus' }); keybindings.registerKeybinding({ command: TerminalCommands.TOGGLE_TERMINAL.id, keybinding: 'ctrl+`', }); + keybindings.registerKeybinding({ + command: TerminalCommands.SELECT_ALL.id, + keybinding: 'ctrlcmd+a', + when: 'terminalFocus' + }); } async newTerminal(options: TerminalWidgetOptions): Promise { @@ -921,18 +967,13 @@ export class TerminalFrontendContribution implements FrontendApplicationContribu }); } - protected async splitTerminal(widget?: Widget): Promise { - const ref = this.getTerminalRef(widget); - if (ref) { + protected async splitTerminal(referenceTerminal?: TerminalWidget): Promise { + if (referenceTerminal || this.currentTerminal) { + const ref = referenceTerminal ?? this.currentTerminal; await this.openTerminal({ ref, mode: 'split-right' }); } } - protected getTerminalRef(widget?: Widget): TerminalWidget | undefined { - const ref = widget ? widget : this.shell.currentWidget; - return ref instanceof TerminalWidget ? ref : undefined; - } - protected async openTerminal(options?: ApplicationShell.WidgetOptions): Promise { let profile = this.profileService.defaultProfile; diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 5d5a2fc598fad..802cbb6c200fd 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -18,7 +18,9 @@ import { Terminal, RendererType } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify'; import { ContributionProvider, Disposable, Event, Emitter, ILogger, DisposableCollection, Channel, OS } from '@theia/core'; -import { Widget, Message, WebSocketConnectionProvider, StatefulWidget, isFirefox, MessageLoop, KeyCode, codicon, ExtractableWidget } from '@theia/core/lib/browser'; +import { + Widget, Message, WebSocketConnectionProvider, StatefulWidget, isFirefox, MessageLoop, KeyCode, codicon, ExtractableWidget, ContextMenuRenderer +} from '@theia/core/lib/browser'; import { isOSX } from '@theia/core/lib/common'; import { WorkspaceService } from '@theia/workspace/lib/browser'; import { ShellTerminalServerProxy, IShellTerminalPreferences } from '../common/shell-terminal-protocol'; @@ -39,6 +41,7 @@ import { TerminalThemeService } from './terminal-theme-service'; import { CommandLineOptions, ShellCommandBuilder } from '@theia/process/lib/common/shell-command-builder'; import { Key } from '@theia/core/lib/browser/keys'; import { nls } from '@theia/core/lib/common/nls'; +import { TerminalMenus } from './terminal-frontend-contribution'; export const TERMINAL_WIDGET_FACTORY_ID = 'terminal'; @@ -93,6 +96,7 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget @inject(TerminalCopyOnSelectionHandler) protected readonly copyOnSelectionHandler: TerminalCopyOnSelectionHandler; @inject(TerminalThemeService) protected readonly themeService: TerminalThemeService; @inject(ShellCommandBuilder) protected readonly shellCommandBuilder: ShellCommandBuilder; + @inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer; protected readonly onDidOpenEmitter = new Emitter(); readonly onDidOpen: Event = this.onDidOpenEmitter.event; @@ -251,6 +255,14 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget this.node.removeEventListener('mousemove', mouseListener); }); + const contextMenuListener = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + this.contextMenuRenderer.render({ menuPath: TerminalMenus.TERMINAL_CONTEXT_MENU, anchor: event }); + }; + this.node.addEventListener('contextmenu', contextMenuListener); + this.onDispose(() => this.node.removeEventListener('contextmenu', contextMenuListener)); + this.toDispose.push(this.term.onSelectionChange(() => { if (this.copyOnSelection) { this.copyOnSelectionHandler.copy(this.term.getSelection()); @@ -437,6 +449,10 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget this.term.clear(); } + selectAll(): void { + this.term.selectAll(); + } + async hasChildProcesses(): Promise { return this.shellTerminalServer.hasChildProcesses(await this.processId); }