diff --git a/packages/ai-native/src/browser/contrib/intelligent-completions/index.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/index.ts index 56dce0cf34..11e4f40cf1 100644 --- a/packages/ai-native/src/browser/contrib/intelligent-completions/index.ts +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/index.ts @@ -1,6 +1,8 @@ import { Disposable, ECodeEditsSourceTyping } from '@opensumi/ide-core-common'; import { IModelContentChangedEvent, IPosition, IRange, InlineCompletion } from '@opensumi/ide-monaco'; +import { ITriggerData } from './source/trigger.source'; + import type { ILineChangeData } from './source/line-change.source'; import type { ILinterErrorData } from './source/lint-error.source'; @@ -25,6 +27,7 @@ export interface ICodeEditsContextBean { [ECodeEditsSourceTyping.LinterErrors]?: ILinterErrorData; [ECodeEditsSourceTyping.LineChange]?: ILineChangeData; [ECodeEditsSourceTyping.Typing]?: IModelContentChangedEvent; + [ECodeEditsSourceTyping.Trigger]?: ITriggerData; }; } 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 126f8b84c0..799a5ee161 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 @@ -74,10 +74,13 @@ export class IntelligentCompletionsContribution implements KeybindingContributio KeybindingScope.USER, ); - keybindings.registerKeybinding({ - command: AI_CODE_EDITS_COMMANDS.TRIGGER.id, - keybinding: codeEdits.triggerKeybinding, - when: 'editorFocus', - }); + keybindings.registerKeybinding( + { + command: AI_CODE_EDITS_COMMANDS.TRIGGER.id, + keybinding: codeEdits.triggerKeybinding, + when: 'editorFocus', + }, + KeybindingScope.USER, + ); } } 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 2bad49228d..cb8b33648c 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 @@ -19,7 +19,6 @@ import { import { Emitter, ICodeEditor, ICursorPositionChangedEvent, IRange, ITextModel, Range } from '@opensumi/ide-monaco'; import { IObservable, - IObservableSignal, ISettableObservable, ITransaction, autorun, @@ -28,7 +27,6 @@ import { derivedHandleChanges, derivedOpts, observableFromEvent, - observableSignal, observableValue, transaction, } from '@opensumi/ide-monaco/lib/common/observable'; @@ -38,7 +36,7 @@ import { inlineSuggestCommitId } from '@opensumi/monaco-editor-core/esm/vs/edito import { InlineCompletionContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys'; import { InlineCompletionsController } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; import { - SuggestItemInfo, + ObservableSuggestWidgetAdapter, SuggestWidgetAdaptor, } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/model/suggestWidgetAdapter'; import { ContextKeyExpr } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey'; @@ -59,6 +57,7 @@ import { IntelligentCompletionsRegistry } from './intelligent-completions.featur import { CodeEditsSourceCollection } from './source/base'; import { LineChangeCodeEditsSource } from './source/line-change.source'; import { LintErrorCodeEditsSource } from './source/lint-error.source'; +import { TriggerCodeEditsSource } from './source/trigger.source'; import { TypingCodeEditsSource } from './source/typing.source'; import { CodeEditsResultValue, VALID_TIME } from './index'; @@ -96,20 +95,17 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll private codeEditsSourceCollection: CodeEditsSourceCollection; private aiNativeContextKey: AINativeContextKey; private rewriteWidget: RewriteWidget | null; - private codeEditsTriggerSignal: IObservableSignal; private multiLineEditsIsVisibleObs: IObservable; public mount(): IDisposable { this.handlerAlwaysVisiblePreference(); this.codeEditsResult = observableValue(this, undefined); - this.codeEditsTriggerSignal = observableSignal(this); - this.multiLineDecorationModel = new MultiLineDecorationModel(this.monacoEditor); this.additionsDeletionsDecorationModel = new AdditionsDeletionsDecorationModel(this.monacoEditor); this.aiNativeContextKey = this.injector.get(AINativeContextKey, [this.monacoEditor.contextKeyService]); this.codeEditsSourceCollection = this.injector.get(CodeEditsSourceCollection, [ - [LintErrorCodeEditsSource, LineChangeCodeEditsSource, TypingCodeEditsSource], + [LintErrorCodeEditsSource, LineChangeCodeEditsSource, TypingCodeEditsSource, TriggerCodeEditsSource], this.monacoEditor, ]); @@ -165,15 +161,24 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll const model = inlineCompletionsController.model.read(reader); model?.inlineCompletionState.read(reader); - const suggestWidgetSelectedItem = inlineCompletionsController['_suggestWidgetSelectedItem'] as IObservable< - SuggestItemInfo | undefined - >; - const selectedItem = suggestWidgetSelectedItem.get(); - if (selectedItem) { - const suggestWidgetAdaptor = inlineCompletionsController['_suggestWidgetAdaptor'] as SuggestWidgetAdaptor; - suggestWidgetAdaptor['_currentSuggestItemInfo'] = undefined; - (suggestWidgetAdaptor['_onDidSelectedItemChange'] as Emitter).fire(); + const observableSuggestWidgetAdapter = inlineCompletionsController[ + '_suggestWidgetAdapter' + ] as ObservableSuggestWidgetAdapter; + if (!observableSuggestWidgetAdapter) { + return; + } + + const selectedItem = observableSuggestWidgetAdapter.selectedItem.get(); + if (!selectedItem) { + return; } + + const suggestWidgetAdaptor = observableSuggestWidgetAdapter[ + '_suggestWidgetAdaptor' + ] as SuggestWidgetAdaptor; + + suggestWidgetAdaptor['_currentSuggestItemInfo'] = undefined; + (suggestWidgetAdaptor['_onDidSelectedItemChange'] as Emitter).fire(); }), ); } @@ -411,7 +416,10 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll }); public trigger(tx: ITransaction): void { - this.codeEditsTriggerSignal.trigger(tx); + const triggerSource = this.codeEditsSourceCollection.getSource(TriggerCodeEditsSource) as TriggerCodeEditsSource; + if (triggerSource) { + triggerSource.triggerSignal.trigger(tx); + } } private registerFeature(monacoEditor: ICodeEditor): void { @@ -463,14 +471,11 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll // 如果上一次补全结果还在,则不重复请求 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/ai-native/src/browser/contrib/intelligent-completions/source/base.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/source/base.ts index 2402ef3713..a7a5fdc5c4 100644 --- a/packages/ai-native/src/browser/contrib/intelligent-completions/source/base.ts +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/source/base.ts @@ -72,10 +72,10 @@ export class CodeEditsContextBean extends Disposable { @Injectable({ multiple: true }) export abstract class BaseCodeEditsSource extends Disposable { @Autowired(IAIReporter) - private aiReporter: IAIReporter; + private readonly aiReporter: IAIReporter; @Autowired(PreferenceService) - protected preferenceService: PreferenceService; + protected readonly preferenceService: PreferenceService; private cancellationTokenSource = new CancellationTokenSource(); private readonly relationID = observableValue(this, undefined); @@ -143,21 +143,26 @@ export class CodeEditsSourceCollection extends Disposable { @Autowired(INJECTOR_TOKEN) private readonly injector: Injector; + private sources: BaseCodeEditsSource[] = []; public readonly codeEditsContextBean = disposableObservableValue(this, undefined); + public getSource(source: ConstructorOf): BaseCodeEditsSource | undefined { + return this.sources.find((s) => s instanceof source); + } + constructor( private readonly constructorSources: ConstructorOf[], private readonly monacoEditor: ICodeEditor, ) { super(); - const sources = this.constructorSources.map((source) => this.injector.get(source, [this.monacoEditor])); + this.sources = this.constructorSources.map((source) => this.injector.get(source, [this.monacoEditor])); - sources.forEach((source) => this.addDispose(source.mount())); + this.sources.forEach((source) => this.addDispose(source.mount())); // 观察所有 source 的 codeEditsContextBean const observerCodeEditsContextBean = derived((reader) => ({ - codeEditsContextBean: new Map(sources.map((source) => [source, source.codeEditsContextBean.read(reader)])), + codeEditsContextBean: new Map(this.sources.map((source) => [source, source.codeEditsContextBean.read(reader)])), })); this.addDispose( @@ -166,7 +171,7 @@ export class CodeEditsSourceCollection extends Disposable { debouncedObservable2(observerCodeEditsContextBean, 0), ({ lastValue, newValue }) => { // 只拿最新的订阅值,如果 uid 相同,表示该值没有变化,就不用往下通知了 - const lastSources = sources.filter((source) => { + const lastSources = this.sources.filter((source) => { const newBean = newValue?.codeEditsContextBean.get(source); const lastBean = lastValue?.codeEditsContextBean?.get(source); return newBean && (!lastBean || newBean.uid !== lastBean.uid); @@ -197,7 +202,6 @@ export class CodeEditsSourceCollection extends Disposable { } transaction((tx) => { - // 只通知最高优先级的结果 this.codeEditsContextBean.set(contextBean, tx); }); }, diff --git a/packages/ai-native/src/browser/contrib/intelligent-completions/source/trigger.source.ts b/packages/ai-native/src/browser/contrib/intelligent-completions/source/trigger.source.ts new file mode 100644 index 0000000000..cd2211e81c --- /dev/null +++ b/packages/ai-native/src/browser/contrib/intelligent-completions/source/trigger.source.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@opensumi/di'; +import { ECodeEditsSourceTyping, IDisposable } from '@opensumi/ide-core-common'; +import { Position } from '@opensumi/ide-monaco'; +import { + IObservableSignal, + derived, + observableSignal, + runOnChangeWithStore, +} from '@opensumi/ide-monaco/lib/common/observable'; + +import { BaseCodeEditsSource } from './base'; + +export interface ITriggerData { + position: Position | null; +} + +@Injectable({ multiple: true }) +export class TriggerCodeEditsSource extends BaseCodeEditsSource { + // 主动触发的优先级是最高的 + public priority = Number.MAX_SAFE_INTEGER; + + public triggerSignal: IObservableSignal = observableSignal(this); + + public mount(): IDisposable { + this.addDispose( + runOnChangeWithStore( + derived((reader) => { + this.triggerSignal.read(reader); + return {}; + }), + () => { + const position = this.monacoEditor.getPosition(); + this.setBean({ + typing: ECodeEditsSourceTyping.Trigger, + data: { + [ECodeEditsSourceTyping.Trigger]: { position }, + }, + }); + }, + ), + ); + return this; + } +} diff --git a/packages/core-common/src/types/ai-native/index.ts b/packages/core-common/src/types/ai-native/index.ts index 568e59a106..a424df4135 100644 --- a/packages/core-common/src/types/ai-native/index.ts +++ b/packages/core-common/src/types/ai-native/index.ts @@ -425,5 +425,7 @@ export enum ECodeEditsSourceTyping { LinterErrors = 'lint_errors', LineChange = 'line_change', Typing = 'typing', + // 主动触发 + Trigger = 'trigger', } // ## Code Edits ends ##