terminal editor context menu (#127344)

This commit is contained in:
Megan Rogge 2021-06-28 13:03:38 -05:00 committed by GitHub
parent a3cfa24750
commit 01f230c3ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import * as dom from 'vs/base/browser/dom';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { CancellationToken } from 'vs/base/common/cancellation';
@ -30,6 +30,11 @@ import { ITerminalProfileResolverService, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIB
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { INotificationService } from 'vs/platform/notification/common/notification';
const xtermSelector = '.terminal.xterm';
const findWidgetSelector = '.simple-find-part-wrapper';
@ -42,7 +47,7 @@ export class TerminalEditor extends EditorPane {
private _editorInput?: TerminalEditorInput = undefined;
private _lastDimension?: Dimension;
private _lastDimension?: dom.Dimension;
private readonly _dropdownMenu: IMenu;
@ -50,6 +55,10 @@ export class TerminalEditor extends EditorPane {
private _findWidgetVisible: IContextKey<boolean>;
private _findState: FindReplaceState;
private readonly _instanceMenu: IMenu;
private _cancelContextMenu: boolean = false;
get findState(): FindReplaceState { return this._findState; }
constructor(
@ -65,12 +74,14 @@ export class TerminalEditor extends EditorPane {
@IMenuService menuService: IMenuService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@INotificationService private readonly _notificationService: INotificationService
) {
super(TerminalEditor.ID, telemetryService, themeService, storageService);
this._findState = new FindReplaceState();
this._findWidget = instantiationService.createInstance(TerminalFindWidget, this._findState);
this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE.bindTo(contextKeyService);
this._dropdownMenu = this._register(menuService.createMenu(MenuId.TerminalNewDropdownContext, contextKeyService));
this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalInstanceContext, contextKeyService));
}
override async setInput(newInput: TerminalEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken) {
@ -104,9 +115,96 @@ export class TerminalEditor extends EditorPane {
// eslint-disable-next-line @typescript-eslint/naming-convention
protected createEditor(parent: HTMLElement): void {
this._parentElement = parent;
this._registerListeners();
}
layout(dimension: Dimension): void {
private _registerListeners(): void {
if (!this._parentElement) {
return;
}
this._register(dom.addDisposableListener(this._parentElement, 'mousedown', async (event: MouseEvent) => {
if (this._terminalEditorService.instances.length === 0) {
return;
}
if (event.which === 2 && isLinux) {
// Drop selection and focus terminal on Linux to enable middle button paste when click
// occurs on the selection itself.
const terminal = this._terminalEditorService.activeInstance;
if (terminal) {
terminal.focus();
}
} else if (event.which === 3) {
const rightClickBehavior = this._terminalService.configHelper.config.rightClickBehavior;
if (rightClickBehavior === 'copyPaste' || rightClickBehavior === 'paste') {
const terminal = this._terminalEditorService.activeInstance;
if (!terminal) {
return;
}
// copyPaste: Shift+right click should open context menu
if (rightClickBehavior === 'copyPaste' && event.shiftKey) {
this._openContextMenu(event);
return;
}
if (rightClickBehavior === 'copyPaste' && terminal.hasSelection()) {
await terminal.copySelection();
terminal.clearSelection();
} else {
if (BrowserFeatures.clipboard.readText) {
terminal.paste();
} else {
this._notificationService.info(`This browser doesn't support the clipboard.readText API needed to trigger a paste, try ${isMacintosh ? '⌘' : 'Ctrl'}+V instead.`);
}
}
// Clear selection after all click event bubbling is finished on Mac to prevent
// right-click selecting a word which is seemed cannot be disabled. There is a
// flicker when pasting but this appears to give the best experience if the
// setting is enabled.
if (isMacintosh) {
setTimeout(() => {
terminal.clearSelection();
}, 0);
}
this._cancelContextMenu = true;
}
}
}));
this._register(dom.addDisposableListener(this._parentElement, dom.EventType.CONTEXT_MENU, e => {
const rightClickBehavior = this._terminalService.configHelper.config.rightClickBehavior;
if (e.button === 2 && rightClickBehavior !== 'copyPaste' && rightClickBehavior !== 'paste') {
this._openContextMenu(e);
e.preventDefault();
}
}));
this._register(dom.addDisposableListener(this._parentElement, 'contextmenu', (event: MouseEvent) => {
if (!this._cancelContextMenu) {
this._openContextMenu(event);
}
event.preventDefault();
event.stopImmediatePropagation();
this._cancelContextMenu = false;
}));
}
private _openContextMenu(event: MouseEvent): void {
const standardEvent = new StandardMouseEvent(event);
const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this._instanceMenu, undefined, actions);
this._contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => actions,
getActionsContext: () => this._parentElement,
onHide: () => actionsDisposable.dispose()
});
}
layout(dimension: dom.Dimension): void {
this._editorInput?.terminalInstance?.layout(dimension);
this._lastDimension = dimension;
}