From 102451f36becb8cd63252326cdeac9e23d4177b7 Mon Sep 17 00:00:00 2001 From: "qingyi.xjh" Date: Wed, 12 Feb 2025 16:02:04 +0800 Subject: [PATCH 1/3] feat: support code edits keybinding --- .../intelligent-completions.contribution.ts | 36 +++++++++++++++---- .../intelligent-completions.controller.ts | 14 +++++++- .../src/ai-native/ai-config.service.ts | 22 +++++++++++- .../core-browser/src/ai-native/command.ts | 12 ++++--- .../core-common/src/types/ai-native/index.ts | 11 ++++++ 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts index a879364828..aeb930dd46 100644 --- a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts @@ -1,5 +1,6 @@ import { Autowired } from '@opensumi/di'; import { + AINativeConfigService, ClientAppContribution, Key, KeybindingContribution, @@ -7,13 +8,15 @@ import { KeybindingScope, } from '@opensumi/ide-core-browser'; import { - AI_MULTI_LINE_COMPLETION_ACCEPT, - AI_MULTI_LINE_COMPLETION_DISCARD, + AI_CODE_EDITS_ACCEPT, + AI_CODE_EDITS_DISCARD, + AI_CODE_EDITS_TRIGGER, } from '@opensumi/ide-core-browser/lib/ai-native/command'; import { MultiLineEditsIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native'; import { CommandContribution, CommandRegistry, Domain } from '@opensumi/ide-core-common'; import { WorkbenchEditorService } from '@opensumi/ide-editor'; import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service'; +import { transaction } from '@opensumi/ide-monaco/lib/common/observable'; import { IntelligentCompletionsController } from './intelligent-completions.controller'; @@ -22,8 +25,11 @@ export class IntelligentCompletionsContribution implements KeybindingContributio @Autowired(WorkbenchEditorService) private readonly workbenchEditorService: WorkbenchEditorServiceImpl; + @Autowired(AINativeConfigService) + private readonly aiNativeConfigService: AINativeConfigService; + registerCommands(commands: CommandRegistry): void { - commands.registerCommand(AI_MULTI_LINE_COMPLETION_DISCARD, { + commands.registerCommand(AI_CODE_EDITS_DISCARD, { execute: () => { const editor = this.workbenchEditorService.currentCodeEditor; if (editor) { @@ -32,7 +38,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio }, }); - commands.registerCommand(AI_MULTI_LINE_COMPLETION_ACCEPT, { + commands.registerCommand(AI_CODE_EDITS_ACCEPT, { execute: () => { const editor = this.workbenchEditorService.currentCodeEditor; if (editor) { @@ -40,11 +46,24 @@ export class IntelligentCompletionsContribution implements KeybindingContributio } }, }); + + commands.registerCommand(AI_CODE_EDITS_TRIGGER, { + execute: () => { + const editor = this.workbenchEditorService.currentCodeEditor; + if (editor) { + transaction((tx) => { + IntelligentCompletionsController.get(editor.monacoEditor)?.trigger(tx); + }); + } + }, + }); } registerKeybindings(keybindings: KeybindingRegistry): void { + const { codeEdits } = this.aiNativeConfigService; + keybindings.registerKeybinding({ - command: AI_MULTI_LINE_COMPLETION_DISCARD.id, + command: AI_CODE_EDITS_DISCARD.id, keybinding: Key.ESCAPE.code, when: MultiLineEditsIsVisible.raw, priority: 100, @@ -52,11 +71,16 @@ export class IntelligentCompletionsContribution implements KeybindingContributio keybindings.registerKeybinding( { - command: AI_MULTI_LINE_COMPLETION_ACCEPT.id, + command: AI_CODE_EDITS_ACCEPT.id, keybinding: Key.TAB.code, when: MultiLineEditsIsVisible.raw, }, KeybindingScope.USER, ); + + keybindings.registerKeybinding({ + command: AI_CODE_EDITS_TRIGGER.id, + keybinding: codeEdits.triggerKeybinding, + }); } } diff --git a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.controller.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.controller.ts index feb727cf9f..5d8f10176f 100644 --- a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.controller.ts +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.controller.ts @@ -13,10 +13,13 @@ import { import { Emitter, ICodeEditor, ICursorPositionChangedEvent, IRange, ITextModel, Range } from '@opensumi/ide-monaco'; import { IObservable, + IObservableSignal, ISettableObservable, + ITransaction, autorun, autorunWithStoreHandleChanges, derived, + observableSignal, observableValue, transaction, } from '@opensumi/ide-monaco/lib/common/observable'; @@ -85,11 +88,13 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll private aiNativeContextKey: AINativeContextKey; private rewriteWidget: RewriteWidget | null; private whenMultiLineEditsVisibleDisposable: Disposable; + private codeEditsTriggerSignal: IObservableSignal; public mount(): IDisposable { this.handlerAlwaysVisiblePreference(); this.codeEditsResult = observableValue(this, undefined); + this.codeEditsTriggerSignal = observableSignal(this); this.whenMultiLineEditsVisibleDisposable = new Disposable(); this.multiLineDecorationModel = new MultiLineDecorationModel(this.monacoEditor); @@ -393,6 +398,10 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll this.hide(); }); + public trigger(tx: ITransaction): void { + this.codeEditsTriggerSignal.trigger(tx); + } + private registerFeature(monacoEditor: ICodeEditor): void { this.featureDisposable.addDispose( Event.any( @@ -432,16 +441,19 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll autorunWithStoreHandleChanges( { createEmptyChangeSummary: () => ({}), - handleChange: (context, changeSummary) => { + handleChange: (context) => { if (context.didChange(this.codeEditsSourceCollection.codeEditsContextBean)) { // 如果上一次补全结果还在,则不重复请求 const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get(); return !isVisible; + } else if (context.didChange(this.codeEditsTriggerSignal)) { + return true; } return false; }, }, async (reader, _, store) => { + this.codeEditsTriggerSignal.read(reader); const context = this.codeEditsSourceCollection.codeEditsContextBean.read(reader); const provider = this.intelligentCompletionsRegistry.getCodeEditsProvider(); diff --git a/packages/core-browser/src/ai-native/ai-config.service.ts b/packages/core-browser/src/ai-native/ai-config.service.ts index 8d66f001db..fe52f24db8 100644 --- a/packages/core-browser/src/ai-native/ai-config.service.ts +++ b/packages/core-browser/src/ai-native/ai-config.service.ts @@ -1,5 +1,10 @@ import { Autowired, Injectable } from '@opensumi/di'; -import { IAINativeCapabilities, IAINativeConfig, IAINativeInlineChatConfig } from '@opensumi/ide-core-common'; +import { + IAINativeCapabilities, + IAINativeCodeEditsConfig, + IAINativeConfig, + IAINativeInlineChatConfig, +} from '@opensumi/ide-core-common'; import { AILogoAvatar } from '../components/ai-native'; import { LayoutViewSizeConfig } from '../layout/constants'; @@ -28,6 +33,10 @@ const DEFAULT_INLINE_CHAT_CONFIG: Required = { logo: AILogoAvatar, }; +const DEFAULT_CODE_EDITS_CONFIG: Required = { + triggerKeybinding: 'alt+\\', +}; + @Injectable() export class AINativeConfigService implements IAINativeConfig { @Autowired(AppConfig) @@ -40,6 +49,7 @@ export class AINativeConfigService implements IAINativeConfig { private internalCapabilities = DEFAULT_CAPABILITIES; private internalInlineChat = DEFAULT_INLINE_CHAT_CONFIG; + private internalCodeEdits = DEFAULT_CODE_EDITS_CONFIG; public get capabilities(): Required { if (!this.aiModuleLoaded) { @@ -65,6 +75,16 @@ export class AINativeConfigService implements IAINativeConfig { return this.internalInlineChat; } + public get codeEdits(): Required { + const { AINativeConfig } = this.appConfig; + + if (AINativeConfig?.codeEdits) { + return { ...this.internalCodeEdits, ...AINativeConfig.codeEdits }; + } + + return this.internalCodeEdits; + } + setAINativeModuleLoaded(value: boolean): void { this.aiModuleLoaded = value; } diff --git a/packages/core-browser/src/ai-native/command.ts b/packages/core-browser/src/ai-native/command.ts index 2d7c759563..01999f17ad 100644 --- a/packages/core-browser/src/ai-native/command.ts +++ b/packages/core-browser/src/ai-native/command.ts @@ -30,10 +30,14 @@ export const AI_CODE_ACTION = { id: 'ai.code.action', }; -export const AI_MULTI_LINE_COMPLETION_DISCARD = { - id: 'ai.multiLine.completion.discard', +export const AI_CODE_EDITS_DISCARD = { + id: 'ai.codeEdits.discard', }; -export const AI_MULTI_LINE_COMPLETION_ACCEPT = { - id: 'ai.multiLine.completion.accept', +export const AI_CODE_EDITS_ACCEPT = { + id: 'ai.codeEdits.accept', +}; + +export const AI_CODE_EDITS_TRIGGER = { + id: 'ai.codeEdits.trigger', }; diff --git a/packages/core-common/src/types/ai-native/index.ts b/packages/core-common/src/types/ai-native/index.ts index ac5548ab09..29da5a3949 100644 --- a/packages/core-common/src/types/ai-native/index.ts +++ b/packages/core-common/src/types/ai-native/index.ts @@ -81,6 +81,13 @@ export interface IAINativeInlineChatConfig { logo?: string | React.ReactNode | React.ComponentType; } +export interface IAINativeCodeEditsConfig { + /** + * 触发 code edits 的快捷键 + */ + triggerKeybinding?: string; +} + export interface IAINativeConfig { capabilities?: IAINativeCapabilities; /** @@ -91,6 +98,10 @@ export interface IAINativeConfig { * inline chat 配置 */ inlineChat?: IAINativeInlineChatConfig; + /** + * code edits 配置 + */ + codeEdits?: IAINativeCodeEditsConfig; } export enum ECompletionType { From 0e26bbd00facd61746c5c8e3e8c33d6bcda447a5 Mon Sep 17 00:00:00 2001 From: "qingyi.xjh" Date: Wed, 12 Feb 2025 16:11:03 +0800 Subject: [PATCH 2/3] fix: when --- .../intelligent-completions.contribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts index aeb930dd46..36ffa9521b 100644 --- a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts @@ -81,6 +81,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio keybindings.registerKeybinding({ command: AI_CODE_EDITS_TRIGGER.id, keybinding: codeEdits.triggerKeybinding, + when: 'editorFocus', }); } } From a6a755ef2c7566383bec262a2e43d5695da8af12 Mon Sep 17 00:00:00 2001 From: "qingyi.xjh" Date: Wed, 12 Feb 2025 17:02:23 +0800 Subject: [PATCH 3/3] chore: improve command --- .../intelligent-completions.contribution.ts | 18 ++++++-------- .../core-browser/src/ai-native/command.ts | 24 ++++++++++--------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts index 36ffa9521b..126f8b84c0 100644 --- a/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts @@ -7,11 +7,7 @@ import { KeybindingRegistry, KeybindingScope, } from '@opensumi/ide-core-browser'; -import { - AI_CODE_EDITS_ACCEPT, - AI_CODE_EDITS_DISCARD, - AI_CODE_EDITS_TRIGGER, -} from '@opensumi/ide-core-browser/lib/ai-native/command'; +import { AI_CODE_EDITS_COMMANDS } from '@opensumi/ide-core-browser/lib/ai-native/command'; import { MultiLineEditsIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native'; import { CommandContribution, CommandRegistry, Domain } from '@opensumi/ide-core-common'; import { WorkbenchEditorService } from '@opensumi/ide-editor'; @@ -29,7 +25,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio private readonly aiNativeConfigService: AINativeConfigService; registerCommands(commands: CommandRegistry): void { - commands.registerCommand(AI_CODE_EDITS_DISCARD, { + commands.registerCommand(AI_CODE_EDITS_COMMANDS.DISCARD, { execute: () => { const editor = this.workbenchEditorService.currentCodeEditor; if (editor) { @@ -38,7 +34,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio }, }); - commands.registerCommand(AI_CODE_EDITS_ACCEPT, { + commands.registerCommand(AI_CODE_EDITS_COMMANDS.ACCEPT, { execute: () => { const editor = this.workbenchEditorService.currentCodeEditor; if (editor) { @@ -47,7 +43,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio }, }); - commands.registerCommand(AI_CODE_EDITS_TRIGGER, { + commands.registerCommand(AI_CODE_EDITS_COMMANDS.TRIGGER, { execute: () => { const editor = this.workbenchEditorService.currentCodeEditor; if (editor) { @@ -63,7 +59,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio const { codeEdits } = this.aiNativeConfigService; keybindings.registerKeybinding({ - command: AI_CODE_EDITS_DISCARD.id, + command: AI_CODE_EDITS_COMMANDS.DISCARD.id, keybinding: Key.ESCAPE.code, when: MultiLineEditsIsVisible.raw, priority: 100, @@ -71,7 +67,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio keybindings.registerKeybinding( { - command: AI_CODE_EDITS_ACCEPT.id, + command: AI_CODE_EDITS_COMMANDS.ACCEPT.id, keybinding: Key.TAB.code, when: MultiLineEditsIsVisible.raw, }, @@ -79,7 +75,7 @@ export class IntelligentCompletionsContribution implements KeybindingContributio ); keybindings.registerKeybinding({ - command: AI_CODE_EDITS_TRIGGER.id, + command: AI_CODE_EDITS_COMMANDS.TRIGGER.id, keybinding: codeEdits.triggerKeybinding, when: 'editorFocus', }); diff --git a/packages/core-browser/src/ai-native/command.ts b/packages/core-browser/src/ai-native/command.ts index 01999f17ad..0bf42bac46 100644 --- a/packages/core-browser/src/ai-native/command.ts +++ b/packages/core-browser/src/ai-native/command.ts @@ -30,14 +30,16 @@ export const AI_CODE_ACTION = { id: 'ai.code.action', }; -export const AI_CODE_EDITS_DISCARD = { - id: 'ai.codeEdits.discard', -}; - -export const AI_CODE_EDITS_ACCEPT = { - id: 'ai.codeEdits.accept', -}; - -export const AI_CODE_EDITS_TRIGGER = { - id: 'ai.codeEdits.trigger', -}; +export namespace AI_CODE_EDITS_COMMANDS { + export const DISCARD = { + id: 'ai.codeEdits.discard', + }; + + export const ACCEPT = { + id: 'ai.codeEdits.accept', + }; + + export const TRIGGER = { + id: 'ai.codeEdits.trigger', + }; +}