From 2bdbf82fe7a3ff618adae295bae28602bcd0051d Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 2 May 2024 19:39:34 -0700 Subject: [PATCH] Accept in background using a Command (#211896) More progress towards https://github.com/microsoft/vscode/issues/98479 --- .../platform/quickinput/browser/quickInput.ts | 37 ++++++++----------- .../quickinput/browser/quickInputActions.ts | 22 ++++++++++- .../browser/quickInputController.ts | 19 +++++++++- .../platform/quickinput/common/quickInput.ts | 6 +++ 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 0e88e988a10..7b43505fb64 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -42,6 +42,10 @@ export const inQuickInputContext = ContextKeyExpr.has(inQuickInputContextKeyValu export const quickInputTypeContextKeyValue = 'quickInputType'; export const QuickInputTypeContextKey = new RawContextKey(quickInputTypeContextKeyValue, undefined, localize('quickInputType', "The type of the currently visible quick input")); +export const endOfQuickInputBoxContextKeyValue = 'cursorAtEndOfQuickInputBox'; +export const EndOfQuickInputBoxContextKey = new RawContextKey(endOfQuickInputBoxContextKeyValue, false, localize('cursorAtEndOfQuickInputBox', "Whether the cursor in the quick input is at the end of the input box")); +export const endOfQuickInputBoxContext = ContextKeyExpr.has(endOfQuickInputBoxContextKeyValue); + export interface IQuickInputOptions { idPrefix: string; container: HTMLElement; @@ -841,27 +845,6 @@ export class QuickPick extends QuickInput implements I this.ui.inputBox.onDidChange(value => { this.doSetValue(value, true /* skip update since this originates from the UI */); })); - // Keybindings for the input box or list if there is no input box - this.visibleDisposables.add((this._hideInput ? this.ui.list : this.ui.inputBox).onKeyDown((event: KeyboardEvent | StandardKeyboardEvent) => { - switch (event.keyCode) { - case KeyCode.RightArrow: - if (!this._canAcceptInBackground) { - return; // needs to be enabled - } - - if (!this.ui.inputBox.isSelectionAtEnd()) { - return; // ensure input box selection at end - } - - if (this.activeItems[0]) { - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.handleAccept(true); - } - - break; - } - })); this.visibleDisposables.add(this.ui.onDidAccept(() => { if (this.canSelectMany) { // if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite @@ -1127,6 +1110,18 @@ export class QuickPick extends QuickInput implements I this.ui.list.domFocus(); } } + + accept(inBackground?: boolean | undefined): void { + if (inBackground && !this._canAcceptInBackground) { + return; // needs to be enabled + } + + if (this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.handleAccept(inBackground ?? false); + } + } } export class InputBox extends QuickInput implements IInputBox { diff --git a/src/vs/platform/quickinput/browser/quickInputActions.ts b/src/vs/platform/quickinput/browser/quickInputActions.ts index 21b1d58bb42..14f4a49b539 100644 --- a/src/vs/platform/quickinput/browser/quickInputActions.ts +++ b/src/vs/platform/quickinput/browser/quickInputActions.ts @@ -9,8 +9,9 @@ import { PartialExcept } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { ICommandAndKeybindingRule, KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { inQuickInputContext, quickInputTypeContextKeyValue } from 'vs/platform/quickinput/browser/quickInput'; +import { endOfQuickInputBoxContext, inQuickInputContext, quickInputTypeContextKeyValue } from 'vs/platform/quickinput/browser/quickInput'; import { IQuickInputService, IQuickPick, QuickInputType, QuickPickFocus } from 'vs/platform/quickinput/common/quickInput'; const defaultCommandAndKeybindingRule = { @@ -178,3 +179,22 @@ if (isMacintosh) { } //#endregion + +//#region Accept + +registerQuickPickCommandAndKeybindingRule( + { + id: 'quickInput.acceptInBackground', + // If we are in the quick pick but the input box is not focused or our cursor is at the end of the input box + when: ContextKeyExpr.and(defaultCommandAndKeybindingRule.when, ContextKeyExpr.or(InputFocusedContext.negate(), endOfQuickInputBoxContext)), + primary: KeyCode.RightArrow, + // Need a little extra weight to ensure this keybinding is preferred over the default cmd+alt+right arrow keybinding + // https://github.com/microsoft/vscode/blob/1451e4fbbbf074a4355cc537c35b547b80ce1c52/src/vs/workbench/browser/parts/editor/editorActions.ts#L1178-L1195 + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: (accessor) => { + const currentQuickPick = accessor.get(IQuickInputService).currentQuickInput as IQuickPick; + currentQuickPick?.accept(true); + }, + }, + { withAltMod: true, withCtrlMod: true, withCmdMod: true } +); diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index 9d47f5cc17e..e9508015487 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -18,7 +18,7 @@ import { isString } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, IQuickWidget, QuickInputHideReason, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { QuickInputBox } from 'vs/platform/quickinput/browser/quickInputBox'; -import { QuickInputUI, Writeable, IQuickInputStyles, IQuickInputOptions, QuickPick, backButton, InputBox, Visibilities, QuickWidget, InQuickInputContextKey, QuickInputTypeContextKey } from 'vs/platform/quickinput/browser/quickInput'; +import { QuickInputUI, Writeable, IQuickInputStyles, IQuickInputOptions, QuickPick, backButton, InputBox, Visibilities, QuickWidget, InQuickInputContextKey, QuickInputTypeContextKey, EndOfQuickInputBoxContextKey } from 'vs/platform/quickinput/browser/quickInput'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { mainWindow } from 'vs/base/browser/window'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -60,6 +60,7 @@ export class QuickInputController extends Disposable { private readonly inQuickInputContext = InQuickInputContextKey.bindTo(this.contextKeyService); private readonly quickInputTypeContext = QuickInputTypeContextKey.bindTo(this.contextKeyService); + private readonly endOfQuickInputBoxContext = EndOfQuickInputBoxContextKey.bindTo(this.contextKeyService); constructor( private options: IQuickInputOptions, @@ -212,8 +213,15 @@ export class QuickInputController extends Disposable { const focusTracker = dom.trackFocus(container); this._register(focusTracker); this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => { + const ui = this.getUI(); + if (dom.isAncestor(e.relatedTarget as HTMLElement, ui.inputContainer)) { + const value = ui.inputBox.isSelectionAtEnd(); + if (this.endOfQuickInputBoxContext.get() !== value) { + this.endOfQuickInputBoxContext.set(value); + } + } // Ignore focus events within container - if (dom.isAncestor(e.relatedTarget as HTMLElement, container)) { + if (dom.isAncestor(e.relatedTarget as HTMLElement, ui.container)) { return; } this.inQuickInputContext.set(true); @@ -224,8 +232,15 @@ export class QuickInputController extends Disposable { this.hide(QuickInputHideReason.Blur); } this.inQuickInputContext.set(false); + this.endOfQuickInputBoxContext.set(false); this.previousFocusElement = undefined; })); + this._register(inputBox.onKeyDown(_ => { + const value = this.getUI().inputBox.isSelectionAtEnd(); + if (this.endOfQuickInputBoxContext.get() !== value) { + this.endOfQuickInputBoxContext.set(value); + } + })); this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { inputBox.setFocus(); })); diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 6bdd8f9ca05..f893e58fbe6 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -629,6 +629,12 @@ export interface IQuickPick extends IQuickInput { * @param focus The focus behavior. */ focus(focus: QuickPickFocus): void; + + /** + * Programmatically accepts an item. Used internally for keyboard navigation. + * @param inBackground Whether you are accepting an item in the background and keeping the picker open. + */ + accept(inBackground?: boolean): void; } /**