From 91ced52bc8547ce1c21138ee4a6a5061cf995c92 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 20 Dec 2024 11:01:48 +0100 Subject: [PATCH 1/3] Add the possibility to define context keys on CodeEditorWidgets --- .../browser/widget/codeEditor/codeEditorWidget.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts index ed20f95580a8e..3bd2f0a903e7e 100644 --- a/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts @@ -290,6 +290,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE })); this._contextKeyService = this._register(contextKeyService.createScoped(this._domElement)); + if (codeEditorWidgetOptions.contextKeyValues) { + for (const [key, value] of Object.entries(codeEditorWidgetOptions.contextKeyValues)) { + this._contextKeyService.createKey(key, value); + } + } this._notificationService = notificationService; this._codeEditorService = codeEditorService; this._commandService = commandService; @@ -1988,6 +1993,12 @@ export interface ICodeEditorWidgetOptions { * Defaults to MenuId.SimpleEditorContext or MenuId.EditorContext depending on whether the widget is simple. */ contextMenuId?: MenuId; + + /** + * Define extra context keys that will be defined in the context service + * for the editor. + */ + contextKeyValues?: Record; } class ModelData { From 7d4b23f21adb12d47f582388ce96f9e447908f77 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 20 Dec 2024 11:02:35 +0100 Subject: [PATCH 2/3] Move `getOuterEditor` near the EmbeddedCodeEditorWidget --- .../widget/codeEditor/embeddedCodeEditorWidget.ts | 10 +++++++++- .../gotoSymbol/browser/peek/referencesController.ts | 3 ++- src/vs/editor/contrib/peekView/browser/peekView.ts | 11 +---------- .../workbench/contrib/scm/browser/quickDiffWidget.ts | 3 ++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts index b716aa6110977..3852374d39496 100644 --- a/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget.ts @@ -13,7 +13,7 @@ import { ILanguageFeaturesService } from '../../../common/services/languageFeatu import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; @@ -61,3 +61,11 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { super.updateOptions(this._overwriteOptions); } } + +export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { + const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (editor instanceof EmbeddedCodeEditorWidget) { + return editor.getParentEditor(); + } + return editor; +} diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts index 175e4c8b49cce..48ba268a69905 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesController.ts @@ -14,7 +14,8 @@ import { Position } from '../../../../common/core/position.js'; import { Range } from '../../../../common/core/range.js'; import { IEditorContribution } from '../../../../common/editorCommon.js'; import { Location } from '../../../../common/languages.js'; -import { getOuterEditor, PeekContext } from '../../../peekView/browser/peekView.js'; +import { PeekContext } from '../../../peekView/browser/peekView.js'; +import { getOuterEditor } from '../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import * as nls from '../../../../../nls.js'; import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; diff --git a/src/vs/editor/contrib/peekView/browser/peekView.ts b/src/vs/editor/contrib/peekView/browser/peekView.ts index 17084e7ae6418..c774617241fa9 100644 --- a/src/vs/editor/contrib/peekView/browser/peekView.ts +++ b/src/vs/editor/contrib/peekView/browser/peekView.ts @@ -16,7 +16,6 @@ import * as objects from '../../../../base/common/objects.js'; import './media/peekViewWidget.css'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js'; -import { ICodeEditorService } from '../../../browser/services/codeEditorService.js'; import { EmbeddedCodeEditorWidget } from '../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { IEditorContribution } from '../../../common/editorCommon.js'; @@ -25,7 +24,7 @@ import * as nls from '../../../../nls.js'; import { createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; -import { createDecorator, IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { activeContrastBorder, contrastBorder, editorForeground, editorInfoForeground, registerColor } from '../../../../platform/theme/common/colorRegistry.js'; export const IPeekViewService = createDecorator('IPeekViewService'); @@ -79,14 +78,6 @@ class PeekContextController implements IEditorContribution { registerEditorContribution(PeekContextController.ID, PeekContextController, EditorContributionInstantiation.Eager); // eager because it needs to define a context key -export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { - const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); - if (editor instanceof EmbeddedCodeEditorWidget) { - return editor.getParentEditor(); - } - return editor; -} - export interface IPeekViewStyles extends IStyles { headerBackgroundColor?: Color; primaryHeadingColor?: Color; diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts index 8ac67f6cd539b..37ed63fb396f1 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts @@ -13,7 +13,7 @@ import { ISelectOptionItem } from '../../../../base/browser/ui/selectBox/selectB import { SelectActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { defaultSelectBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { IColorTheme, IThemeService } from '../../../../platform/theme/common/themeService.js'; -import { getOuterEditor, peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from '../../../../editor/contrib/peekView/browser/peekView.js'; +import { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground, PeekViewWidget } from '../../../../editor/contrib/peekView/browser/peekView.js'; import { editorBackground } from '../../../../platform/theme/common/colorRegistry.js'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from '../../../../platform/actions/common/actions.js'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; @@ -48,6 +48,7 @@ import { gotoNextLocation, gotoPreviousLocation } from '../../../../platform/the import { Codicon } from '../../../../base/common/codicons.js'; import { Color } from '../../../../base/common/color.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { getOuterEditor } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; export const isQuickDiffVisible = new RawContextKey('dirtyDiffVisible', false); From 9cb7a27bd0bdecd16003711015d0a5172d5a8804 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 20 Dec 2024 11:14:30 +0100 Subject: [PATCH 3/3] Make Tab and Escape work when focus is in the preview --- .../browser/controller/commands.ts | 28 +++++++++++++++---- .../controller/inlineCompletionContextKeys.ts | 3 ++ .../controller/inlineCompletionsController.ts | 14 +++++++++- .../view/inlineEdits/sideBySideDiff.ts | 9 ++++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts index f183f9f896fff..2fe48fc4eb9bc 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts @@ -11,7 +11,8 @@ import { Action2, MenuId } from '../../../../../platform/actions/common/actions. import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; -import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { EditorAction, EditorCommand, ServicesAccessor } from '../../../../browser/editorExtensions.js'; import { EditorContextKeys } from '../../../../common/editorContextKeys.js'; @@ -19,7 +20,6 @@ import { Context as SuggestContext } from '../../../suggest/browser/suggest.js'; import { inlineSuggestCommitId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; import { InlineCompletionsController } from './inlineCompletionsController.js'; -import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js'; export class ShowNextInlineSuggestionAction extends EditorAction { public static ID = showNextInlineSuggestionActionId; @@ -211,14 +211,21 @@ export class AcceptInlineCompletion extends EditorAction { }); } - public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { - const controller = InlineCompletionsController.get(editor); + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor); if (controller) { controller.model.get()?.accept(controller.editor); controller.editor.focus(); } } } +KeybindingsRegistry.registerKeybindingRule({ + id: inlineSuggestCommitId, + weight: 202, // greater than jump + primary: KeyCode.Tab, + when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor) +}); + export class JumpToNextInlineEdit extends EditorAction { constructor() { @@ -276,14 +283,23 @@ export class HideInlineCompletion extends EditorAction { }); } - public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise { - const controller = InlineCompletionsController.get(editor); + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor); transaction(tx => { controller?.model.get()?.stop('explicitCancel', tx); }); + controller?.editor.focus(); } } +KeybindingsRegistry.registerKeybindingRule({ + id: HideInlineCompletion.ID, + weight: -1, // very weak + primary: KeyCode.Escape, + secondary: [KeyMod.Shift | KeyCode.Escape], + when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor) +}); + export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 { public static ID = 'editor.action.inlineSuggest.toggleAlwaysShowToolbar'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts index 7fbd4dc19fc31..834adac93caa9 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys.ts @@ -5,6 +5,7 @@ import { RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; import { localize } from '../../../../../nls.js'; +import * as nls from '../../../../../nls.js'; export abstract class InlineCompletionContextKeys { @@ -19,4 +20,6 @@ export abstract class InlineCompletionContextKeys { public static readonly inlineEditVisible = new RawContextKey('inlineEditIsVisible', false, localize('inlineEditVisible', "Whether an inline edit is visible")); public static readonly tabShouldJumpToInlineEdit = new RawContextKey('tabShouldJumpToInlineEdit', false, localize('tabShouldJumpToInlineEdit', "Whether tab should jump to an inline edit.")); public static readonly tabShouldAcceptInlineEdit = new RawContextKey('tabShouldAcceptInlineEdit', false, localize('tabShouldAcceptInlineEdit', "Whether tab should accept the inline edit.")); + + public static readonly inInlineEditsPreviewEditor = new RawContextKey('inInlineEditsPreviewEditor', true, nls.localize('inInlineEditsPreviewEditor', "Whether the current code editor is showing an inline edits preview")); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 79fdfb85afdee..b2b4b8ca6c696 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -16,7 +16,7 @@ import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../.. import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; -import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { hotClassGetOriginalInstance } from '../../../../../platform/observable/common/wrapInHotClass.js'; import { CoreEditingCommands } from '../../../../browser/coreCommands.js'; @@ -36,6 +36,7 @@ import { ObservableContextKeyService } from '../utils.js'; import { inlineSuggestCommitId } from './commandIds.js'; import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js'; import { InlineCompletionsView } from '../view/inlineCompletionsView.js'; +import { getOuterEditor } from '../../../../browser/widget/codeEditor/embeddedCodeEditorWidget.js'; export class InlineCompletionsController extends Disposable { private static readonly _instances = new Set(); @@ -43,6 +44,17 @@ export class InlineCompletionsController extends Disposable { public static hot = createHotClass(InlineCompletionsController); public static ID = 'editor.contrib.inlineCompletionsController'; + /** + * Find the controller in the focused editor or in the outer editor (if applicable) + */ + public static getInFocusedEditorOrParent(accessor: ServicesAccessor): InlineCompletionsController | null { + const outerEditor = getOuterEditor(accessor); + if (!outerEditor) { + return null; + } + return InlineCompletionsController.get(outerEditor); + } + public static get(editor: ICodeEditor): InlineCompletionsController | null { return hotClassGetOriginalInstance(editor.getContribution(InlineCompletionsController.ID)); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index b34dc44ac1df7..042dc653cf90a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -28,11 +28,11 @@ import { Range } from '../../../../../common/core/range.js'; import { Command } from '../../../../../common/languages.js'; import { ITextModel } from '../../../../../common/model.js'; import { StickyScrollController } from '../../../../stickyScroll/browser/stickyScrollController.js'; +import { InlineCompletionContextKeys } from '../../controller/inlineCompletionContextKeys.js'; import { CustomizedMenuWorkbenchToolBar } from '../../hintsWidget/inlineCompletionsHintsWidget.js'; import { PathBuilder, StatusBarViewItem, getOffsetForPos, mapOutFalsy, maxContentWidthInRange, n } from './utils.js'; import { InlineEditWithChanges } from './viewAndDiffProducer.js'; - export const originalBackgroundColor = registerColor( 'inlineEdit.originalBackground', Color.transparent, @@ -271,7 +271,12 @@ export class InlineEditsSideBySideDiff extends Disposable { wordWrapOverride1: 'off', wordWrapOverride2: 'off', }, - { contributions: [], }, + { + contextKeyValues: { + [InlineCompletionContextKeys.inInlineEditsPreviewEditor.key]: true, + }, + contributions: [], + }, this._editor ));