Skip to content

Commit

Permalink
feat: run hover fix action
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricbet committed Sep 10, 2024
1 parent 250fe2e commit f6f5833
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di';
import { AppConfig } from '@opensumi/ide-core-browser';
import { CancellationToken, Disposable, ProblemFixRegistryToken } from '@opensumi/ide-core-common';
import { InlineChatIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
import {
AISerivceType,
ActionSourceEnum,
ActionTypeEnum,
Disposable,
IAIReporter,
ProblemFixRegistryToken,
} from '@opensumi/ide-core-common';
import { IEditor } from '@opensumi/ide-editor';
import { Range } from '@opensumi/ide-monaco';
import { HoverController } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/hover/browser/hover';
import {
HoverParticipantRegistry,
IEditorHoverRenderContext,
Expand All @@ -11,6 +21,9 @@ import {
MarkerHoverParticipant,
} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/hover/browser/markerHoverParticipant';


import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service';
import { InlineChatHandler } from '../../widget/inline-chat/inline-chat.handler';
import { IAIMonacoContribHandler } from '../base';

import { MarkerHoverParticipantComponent } from './problem-fix.component';
Expand All @@ -32,6 +45,9 @@ class AIMonacoHoverParticipant extends MarkerHoverParticipant {

@Injectable()
export class ProblemFixHandler extends IAIMonacoContribHandler {
@Autowired(InlineChatHandler)
private readonly inlineChatHandler: InlineChatHandler;

@Autowired(INJECTOR_TOKEN)
private readonly injector: Injector;

Expand All @@ -41,6 +57,16 @@ export class ProblemFixHandler extends IAIMonacoContribHandler {
@Autowired(ProblemFixRegistryToken)
private readonly problemFixProviderRegistry: ProblemFixProviderRegistry;

@Autowired(IAIReporter)
private readonly aiReporter: IAIReporter;

private aiNativeContextKey: AINativeContextKey;

mountEditor(editor: IEditor) {
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [editor.monacoEditor.contextKeyService]);
return super.mountEditor(editor);
}

doContribute() {
const disposable = new Disposable();

Expand All @@ -54,6 +80,11 @@ export class ProblemFixHandler extends IAIMonacoContribHandler {

disposable.addDispose(
this.problemFixService.onHoverFixTrigger((part) => {
const hoverController = this.editor?.monacoEditor.getContribution<HoverController>(HoverController.ID);
if (hoverController) {
hoverController.hideContentHover();
}

this.handleHoverFix(part);
}),
);
Expand All @@ -74,17 +105,56 @@ export class ProblemFixHandler extends IAIMonacoContribHandler {
}

const model = monacoEditor.getModel();

// 以 marker 的 range 为中心,向上取 2 行,向下取 3 行
const editRange = new Range(
Math.max(part.range.startLineNumber - 2, 0),
1,
Math.min(part.range.endLineNumber + 3, model!.getLineCount() ?? 0),
model!.getLineMaxColumn(part.range.endLineNumber + 3) ?? 0,
);

const context = {
marker: part.marker,
// 以 marker 的 range 为中心,向上取 2 行,向下取 3 行
editRange: new Range(
Math.max(part.range.startLineNumber - 2, 0),
1,
Math.min(part.range.endLineNumber + 3, model!.getLineCount() ?? 0),
model!.getLineMaxColumn(part.range.endLineNumber + 3) ?? 0,
),
editRange,
};

provider.provideFix(monacoEditor, context, CancellationToken.None);
monacoEditor.setSelection(editRange);

const contextKeyDisposed = new Disposable();
const inlineChatIsVisible = new Set([InlineChatIsVisible.raw]);

contextKeyDisposed.addDispose(
this.aiNativeContextKey.contextKeyService!.onDidChangeContext((e) => {
if (e.payload.affectsSome(inlineChatIsVisible)) {
const isVisible = this.aiNativeContextKey.inlineChatIsVisible.get();
if (isVisible) {
this.inlineChatHandler.runAction({
monacoEditor,
reporterFn: (): string => {
const relationId = this.aiReporter.start(AISerivceType.ProblemFix, {
message: ActionTypeEnum.HoverFix,
type: AISerivceType.InlineChat,
source: ActionTypeEnum.HoverFix,
actionSource: ActionSourceEnum.Hover,
actionType: ActionTypeEnum.HoverFix,
});
return relationId;
},
crossSelection: monacoEditor.getSelection()!,
providerPreview: () => provider.provideFix(monacoEditor, context, this.inlineChatHandler.cancelIndicator.token),
extraData: {
actionSource: ActionSourceEnum.Hover,
actionType: ActionTypeEnum.HoverFix,
},
});
}

contextKeyDisposed.dispose();
}
}),
);

this.addDispose(contextKeyDisposed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@ export class InlineChatHandler extends Disposable {
private aiInlineContentWidget: AIInlineContentWidget;
private aiInlineChatDisposable: Disposable = new Disposable();
private aiInlineChatOperationDisposable: Disposable = new Disposable();
private cancelIndicator = new CancellationTokenSource();
private _cancelIndicator = new CancellationTokenSource();
get cancelIndicator() {
return this._cancelIndicator;
}

private cancelToken() {
this.cancelIndicator.cancel();
this.cancelIndicator = new CancellationTokenSource();
public cancelToken() {
this._cancelIndicator.cancel();
this._cancelIndicator = new CancellationTokenSource();
}

private disposeAllWidget() {
Expand Down Expand Up @@ -228,6 +231,11 @@ export class InlineChatHandler extends Disposable {
return;
}

const crossSelection = this.getCrossSelection(monacoEditor);
if (!crossSelection) {
return;
}

const previewer = () => {
// 兼容 providerDiffPreviewStrategy api
const strategy = handler.providerDiffPreviewStrategy
Expand All @@ -237,11 +245,12 @@ export class InlineChatHandler extends Disposable {
return undefined;
}

return strategy.bind(this, monacoEditor, this.cancelIndicator.token);
return strategy.bind(this, monacoEditor, this._cancelIndicator.token);
};

this.runInlineChatAction({
this.runAction({
monacoEditor,
crossSelection,
reporterFn: () => {
const relationId = this.aiReporter.start(action.name, {
message: action.name,
Expand All @@ -253,7 +262,7 @@ export class InlineChatHandler extends Disposable {
});
return relationId;
},
execute: handler.execute ? handler.execute!.bind(this, monacoEditor, this.cancelIndicator.token) : undefined,
execute: handler.execute ? handler.execute!.bind(this, monacoEditor, this._cancelIndicator.token) : undefined,
providerPreview: previewer(),
extraData: {
actionSource: source === 'codeAction' ? ActionSourceEnum.CodeAction : ActionSourceEnum.InlineChat,
Expand All @@ -273,8 +282,14 @@ export class InlineChatHandler extends Disposable {

const strategy = await this.inlineChatFeatureRegistry.getInteractiveInputStrategyHandler()(monacoEditor, value);

this.runInlineChatAction({
const crossSelection = this.getCrossSelection(monacoEditor);
if (!crossSelection) {
return;
}

this.runAction({
monacoEditor,
crossSelection,
reporterFn: () => {
const relationId = this.aiReporter.start(AISerivceType.InlineChatInput, {
message: value,
Expand All @@ -286,11 +301,11 @@ export class InlineChatHandler extends Disposable {
},
execute:
handler.execute && strategy === ERunStrategy.EXECUTE
? handler.execute!.bind(this, monacoEditor, value, this.cancelIndicator.token)
? handler.execute!.bind(this, monacoEditor, value, this._cancelIndicator.token)
: undefined,
providerPreview:
handler.providePreviewStrategy && strategy === ERunStrategy.PREVIEW
? handler.providePreviewStrategy.bind(this, monacoEditor, value, this.cancelIndicator.token)
? handler.providePreviewStrategy.bind(this, monacoEditor, value, this._cancelIndicator.token)
: undefined,
extraData: {
actionSource: ActionSourceEnum.InlineChatInput,
Expand All @@ -301,6 +316,18 @@ export class InlineChatHandler extends Disposable {
);
}

private getCrossSelection(monacoEditor: monaco.ICodeEditor) {
const selection = monacoEditor.getSelection();
if (!selection) {
this.logger.error('No selection found, aborting inline chat action.');
return;
}

return selection
.setStartPosition(selection.startLineNumber, 1)
.setEndPosition(selection.endLineNumber, monacoEditor.getModel()!.getLineMaxColumn(selection.endLineNumber));
}

private convertInlineChatStatus(
status: EInlineChatStatus,
reportInfo: {
Expand Down Expand Up @@ -438,9 +465,6 @@ export class InlineChatHandler extends Disposable {
}
}

/**
* 重新生成代码
*/
private async handleDiffPreviewStrategy(params: {
monacoEditor: monaco.ICodeEditor;
strategy: (...arg: any[]) => MaybePromise<ChatResponse | InlineChatController>;
Expand All @@ -464,7 +488,7 @@ export class InlineChatHandler extends Disposable {

const startTime = Date.now();

if (this.cancelIndicator.token.isCancellationRequested) {
if (this._cancelIndicator.token.isCancellationRequested) {
this.convertInlineChatStatus(EInlineChatStatus.READY, {
relationId,
message: 'abort',
Expand Down Expand Up @@ -566,33 +590,25 @@ export class InlineChatHandler extends Disposable {
]);
}

private async runInlineChatAction(params: {
public async runAction(params: {
monacoEditor: monaco.ICodeEditor;
reporterFn: () => string;
crossSelection: monaco.Selection;
execute?: () => MaybePromise<void>;
providerPreview?: () => MaybePromise<ChatResponse | InlineChatController>;
extraData?: {
actionSource: string;
actionType: string;
};
}) {
const { monacoEditor, reporterFn, execute, providerPreview, extraData } = params;
const selection = monacoEditor.getSelection();
if (!selection) {
this.logger.error('No selection found, aborting inline chat action.');
return;
}
const { monacoEditor, crossSelection, reporterFn, execute, providerPreview, extraData } = params;

if (execute) {
await execute();
this.disposeAllWidget();
}

if (providerPreview) {
const crossSelection = selection
.setStartPosition(selection.startLineNumber, 1)
.setEndPosition(selection.endLineNumber, Number.MAX_SAFE_INTEGER);

const relationId = reporterFn();

await this.handleDiffPreviewStrategy({
Expand Down
5 changes: 5 additions & 0 deletions packages/core-common/src/types/ai-native/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export enum AISerivceType {
MergeConflict = 'mergeConflict',
Rename = 'rename',
TerminalAICommand = 'terminalAICommand',
ProblemFix = 'problemFix',
}

export enum ActionSourceEnum {
Expand All @@ -26,6 +27,8 @@ export enum ActionSourceEnum {
Terminal = 'terminal',
// 下拉补全 | 自动补全
Completion = 'completion',
// 编辑器内悬停操作
Hover = 'hover',
}

export enum ActionTypeEnum {
Expand Down Expand Up @@ -55,6 +58,8 @@ export enum ActionTypeEnum {
LineDiscard = 'lineDiscard',
// 生成代码后的行动点:重新生成
Regenerate = 'regenerate',
// 悬停的问题修复
HoverFix = 'hoverFix',
// 包含业务自定义的Action
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,14 @@ import {
AIBackSerivcePath,
CancelResponse,
CancellationToken,
ChatResponse,
ChatServiceToken,
ErrorResponse,
IAIBackService,
IPosition,
MaybePromise,
MergeConflictEditorMode,
ReplyResponse,
getDebugLogger,
} from '@opensumi/ide-core-common';
import { ChatResponse } from '@opensumi/ide-core-common';
import { ICodeEditor, NewSymbolName, NewSymbolNameTag } from '@opensumi/ide-monaco';
import { MarkdownString } from '@opensumi/monaco-editor-core/esm/vs/base/common/htmlContent';

Expand Down Expand Up @@ -392,14 +390,18 @@ export class AINativeContribution implements AINativeCoreContribution {

registerProblemFixFeature(registry: IProblemFixProviderRegistry): void {
registry.registerHoverFixProvider({
provideFix (
provideFix: async (
editor: ICodeEditor,
context: IProblemFixContext,
token: CancellationToken,
): MaybePromise<ChatResponse | InlineChatController> {
): Promise<ChatResponse | InlineChatController> => {
const { marker, editRange } = context;
// 返回一个 ChatResponse 或 InlineChatController 的 Promise
return Promise.resolve(new ReplyResponse('这是一个示例响应'));

const controller = new InlineChatController({ enableCodeblockRender: true });
const stream = await this.aiBackService.requestStream('', {}, token);
controller.mountReadable(stream);

return controller;
},
});
}
Expand Down

0 comments on commit f6f5833

Please sign in to comment.