Skip to content

Commit

Permalink
feat: support trigger source code edits
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricbet committed Feb 20, 2025
1 parent feca922 commit e7a032a
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -25,6 +27,7 @@ export interface ICodeEditsContextBean {
[ECodeEditsSourceTyping.LinterErrors]?: ILinterErrorData;
[ECodeEditsSourceTyping.LineChange]?: ILineChangeData;
[ECodeEditsSourceTyping.Typing]?: IModelContentChangedEvent;
[ECodeEditsSourceTyping.Trigger]?: ITriggerData;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
import { Emitter, ICodeEditor, ICursorPositionChangedEvent, IRange, ITextModel, Range } from '@opensumi/ide-monaco';
import {
IObservable,
IObservableSignal,
ISettableObservable,
ITransaction,
autorun,
Expand All @@ -28,7 +27,6 @@ import {
derivedHandleChanges,
derivedOpts,
observableFromEvent,
observableSignal,
observableValue,
transaction,
} from '@opensumi/ide-monaco/lib/common/observable';
Expand All @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -96,20 +95,17 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
private codeEditsSourceCollection: CodeEditsSourceCollection;
private aiNativeContextKey: AINativeContextKey;
private rewriteWidget: RewriteWidget | null;
private codeEditsTriggerSignal: IObservableSignal<void>;
private multiLineEditsIsVisibleObs: IObservable<boolean>;

public mount(): IDisposable {
this.handlerAlwaysVisiblePreference();

this.codeEditsResult = observableValue<CodeEditsResultValue | undefined>(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,
]);

Expand Down Expand Up @@ -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<void>).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<void>).fire();
}),
);
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string | undefined>(this, undefined);
Expand Down Expand Up @@ -143,21 +143,26 @@ export class CodeEditsSourceCollection extends Disposable {
@Autowired(INJECTOR_TOKEN)
private readonly injector: Injector;

private sources: BaseCodeEditsSource[] = [];
public readonly codeEditsContextBean = disposableObservableValue<CodeEditsContextBean | undefined>(this, undefined);

public getSource(source: ConstructorOf<BaseCodeEditsSource>): BaseCodeEditsSource | undefined {
return this.sources.find((s) => s instanceof source);
}

constructor(
private readonly constructorSources: ConstructorOf<BaseCodeEditsSource>[],
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(
Expand All @@ -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);
Expand Down Expand Up @@ -197,7 +202,6 @@ export class CodeEditsSourceCollection extends Disposable {
}

transaction((tx) => {
// 只通知最高优先级的结果
this.codeEditsContextBean.set(contextBean, tx);
});
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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<void> = 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;
}
}
2 changes: 2 additions & 0 deletions packages/core-common/src/types/ai-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,5 +425,7 @@ export enum ECodeEditsSourceTyping {
LinterErrors = 'lint_errors',
LineChange = 'line_change',
Typing = 'typing',
// 主动触发
Trigger = 'trigger',
}
// ## Code Edits ends ##

0 comments on commit e7a032a

Please sign in to comment.