Skip to content

Commit

Permalink
feat: support problem fix api (#3999)
Browse files Browse the repository at this point in the history
* feat: support problem fix components

* chore: implement hover fix api

* feat: run hover fix action

* fix: improve code
  • Loading branch information
Ricbet authored Sep 11, 2024
1 parent 2bdc711 commit 4d68e23
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 27 deletions.
13 changes: 13 additions & 0 deletions packages/ai-native/src/browser/ai-core.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
CommandService,
InlineChatFeatureRegistryToken,
IntelligentCompletionsRegistryToken,
ProblemFixRegistryToken,
RenameCandidatesProviderRegistryToken,
ResolveConflictRegistryToken,
TerminalRegistryToken,
Expand Down Expand Up @@ -76,6 +77,7 @@ import { CodeActionHandler } from './contrib/code-action/code-action.handler';
import { AIInlineCompletionsProvider } from './contrib/inline-completions/completeProvider';
import { InlineCompletionHandler } from './contrib/inline-completions/inline-completions.handler';
import { AICompletionsService } from './contrib/inline-completions/service/ai-completions.service';
import { ProblemFixHandler } from './contrib/problem-fix/problem-fix.handler';
import { RenameHandler } from './contrib/rename/rename.handler';
import { AIRunToolbar } from './contrib/run-toolbar/run-toolbar';
import { AIChatTabRenderer, AILeftTabRenderer, AIRightTabRenderer } from './layout/tabbar.view';
Expand All @@ -85,6 +87,7 @@ import {
IChatFeatureRegistry,
IChatRenderRegistry,
IIntelligentCompletionsRegistry,
IProblemFixProviderRegistry,
IRenameCandidatesProviderRegistry,
IResolveConflictRegistry,
ITerminalProviderRegistry,
Expand Down Expand Up @@ -143,6 +146,9 @@ export class AINativeBrowserContribution
@Autowired(IntelligentCompletionsRegistryToken)
private readonly intelligentCompletionsRegistry: IIntelligentCompletionsRegistry;

@Autowired(ProblemFixRegistryToken)
private readonly problemFixProviderRegistry: IProblemFixProviderRegistry;

@Autowired(AINativeConfigService)
private readonly aiNativeConfigService: AINativeConfigService;

Expand Down Expand Up @@ -176,6 +182,9 @@ export class AINativeBrowserContribution
@Autowired(RenameHandler)
private readonly renameHandler: RenameHandler;

@Autowired(ProblemFixHandler)
private readonly problemfixHandler: ProblemFixHandler;

@Autowired(InlineCompletionHandler)
private readonly inlineCompletionHandler: InlineCompletionHandler;

Expand Down Expand Up @@ -223,6 +232,9 @@ export class AINativeBrowserContribution
if (this.aiNativeConfigService.capabilities.supportsRenameSuggestions) {
this.renameHandler.load();
}
if (this.aiNativeConfigService.capabilities.supportsProblemFix) {
this.problemfixHandler.load();
}
if (this.aiNativeConfigService.capabilities.supportsInlineCompletion) {
this.inlineCompletionHandler.load();
}
Expand All @@ -240,6 +252,7 @@ export class AINativeBrowserContribution
contribution.registerChatRender?.(this.chatRenderRegistry);
contribution.registerTerminalProvider?.(this.terminalProviderRegistry);
contribution.registerIntelligentCompletionFeature?.(this.intelligentCompletionsRegistry);
contribution.registerProblemFixFeature?.(this.problemFixProviderRegistry);
});
}

Expand Down
7 changes: 7 additions & 0 deletions packages/ai-native/src/browser/ai-editor.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BrowserCodeEditor, BrowserDiffEditor } from '@opensumi/ide-editor/lib/b
import { CodeActionHandler } from './contrib/code-action/code-action.handler';
import { InlineCompletionHandler } from './contrib/inline-completions/inline-completions.handler';
import { IntelligentCompletionsHandler } from './contrib/intelligent-completions/intelligent-completions.handler';
import { ProblemFixHandler } from './contrib/problem-fix/problem-fix.handler';
import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry';
import { InlineChatHandler } from './widget/inline-chat/inline-chat.handler';
import { InlineDiffHandler } from './widget/inline-diff/inline-diff.handler';
Expand Down Expand Up @@ -42,6 +43,9 @@ export class AIEditorContribution extends Disposable implements IEditorFeatureCo
@Autowired(InlineCompletionHandler)
private readonly inlineCompletionHandler: InlineCompletionHandler;

@Autowired(ProblemFixHandler)
private readonly problemfixHandler: ProblemFixHandler;

@Autowired(IntelligentCompletionsHandler)
private readonly intelligentCompletionsHandler: IntelligentCompletionsHandler;

Expand Down Expand Up @@ -192,6 +196,9 @@ export class AIEditorContribution extends Disposable implements IEditorFeatureCo
if (this.aiNativeConfigService.capabilities.supportsInlineChat) {
this.modelSessionDisposable.addDispose(this.codeActionHandler.mountEditor(editor));
}
if (this.aiNativeConfigService.capabilities.supportsProblemFix) {
this.modelSessionDisposable.addDispose(this.problemfixHandler.mountEditor(editor));
}

this.modelSessionDisposable.addDispose(this.inlineDiffHandler.mountEditor(editor));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import ReactDOM from 'react-dom/client';

import { Button } from '@opensumi/ide-components';
import { AppConfig, ConfigProvider, useInjectable } from '@opensumi/ide-core-browser';
import { localize } from '@opensumi/ide-core-common';
import { MarkerHover } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/hover/browser/markerHoverParticipant';

import styles from './problem-fix.module.less';
import { ProblemFixService } from './problem-fix.service';

interface IProblemFixComponentProps {
part: MarkerHover;
}

export const ProblemFixComponent = ({ part }: IProblemFixComponentProps) => {
const problemFixService = useInjectable(ProblemFixService);

const handleClick = React.useCallback(() => {
problemFixService.triggerHoverFix(part);
}, [problemFixService, part]);

return (
<Button size='small' onClick={handleClick}>
{localize('aiNative.inline.problem.fix.title')}
</Button>
);
};

export const MarkerHoverParticipantComponent = {
mount(container: DocumentFragment, hoverParts: MarkerHover[], configContext: AppConfig) {
container.childNodes.forEach((node, index) => {
const dom = document.createElement('div');
dom.className = styles.problem_fix_btn_container;

const part = hoverParts[index];

ReactDOM.createRoot(dom).render(
<ConfigProvider value={configContext}>
<ProblemFixComponent part={part} />
</ConfigProvider>,
);
node.appendChild(dom);
});
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Injectable } from '@opensumi/di';

import { IHoverFixHandler, IProblemFixProviderRegistry } from '../../types';

@Injectable()
export class ProblemFixProviderRegistry implements IProblemFixProviderRegistry {
private hoverFixProvider: IHoverFixHandler | undefined;

registerHoverFixProvider(provider: IHoverFixHandler): void {
this.hoverFixProvider = provider;
}

getHoverFixProvider(): IHoverFixHandler | undefined {
return this.hoverFixProvider;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di';
import { AppConfig } from '@opensumi/ide-core-browser';
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,
} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/hover/browser/hoverTypes';
import {
MarkerHover,
MarkerHoverParticipant,
} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/hover/browser/markerHoverParticipant';

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

import { MarkerHoverParticipantComponent } from './problem-fix.component';
import { ProblemFixProviderRegistry } from './problem-fix.feature.registry';
import { ProblemFixService } from './problem-fix.service';

class AIMonacoHoverParticipant extends MarkerHoverParticipant {
static injector: Injector;

override renderHoverParts(context: IEditorHoverRenderContext, hoverParts: MarkerHover[]) {
const disposable = super.renderHoverParts(context, hoverParts);

const { fragment } = context;
MarkerHoverParticipantComponent.mount(fragment, hoverParts, AIMonacoHoverParticipant.injector.get(AppConfig));

return disposable;
}
}

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

@Autowired(INJECTOR_TOKEN)
private readonly injector: Injector;

@Autowired(ProblemFixService)
private readonly problemFixService: ProblemFixService;

@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();

const provider = this.problemFixProviderRegistry.getHoverFixProvider();
if (!provider) {
return disposable;
}

// 先去掉 monaco 默认的 MarkerHoverParticipant
HoverParticipantRegistry._participants = HoverParticipantRegistry._participants.filter(
(participant) => participant !== MarkerHoverParticipant,
);

AIMonacoHoverParticipant.injector = this.injector;
HoverParticipantRegistry.register(AIMonacoHoverParticipant);

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

this.handleHoverFix(part, provider);
}),
);

return disposable;
}

private async handleHoverFix(part: MarkerHover, provider: IHoverFixHandler) {
const monacoEditor = this.editor?.monacoEditor;

if (!monacoEditor) {
return;
}

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,
editRange,
};

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
@@ -0,0 +1,5 @@
.problem_fix_btn_container {
margin-left: 8px;
margin-bottom: 8px;
height: 22px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@opensumi/di';
import { Emitter, Event } from '@opensumi/ide-core-common';
import { MarkerHover } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/hover/browser/markerHoverParticipant';

@Injectable()
export class ProblemFixService {
private readonly _onHoverFixTrigger = new Emitter<MarkerHover>();
public readonly onHoverFixTrigger: Event<MarkerHover> = this._onHoverFixTrigger.event;

public triggerHoverFix(isTrigger: MarkerHover) {
this._onHoverFixTrigger.fire(isTrigger);
}
}
11 changes: 10 additions & 1 deletion packages/ai-native/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
RenameCandidatesProviderRegistryToken,
ResolveConflictRegistryToken,
} from '@opensumi/ide-core-browser';
import { IntelligentCompletionsRegistryToken, TerminalRegistryToken } from '@opensumi/ide-core-common';
import {
IntelligentCompletionsRegistryToken,
ProblemFixRegistryToken,
TerminalRegistryToken,
} from '@opensumi/ide-core-common';

import { ChatProxyServiceToken, IChatAgentService, IChatInternalService, IChatManagerService } from '../common';

Expand All @@ -32,6 +36,7 @@ import { IntelligentCompletionsRegistry } from './contrib/intelligent-completion
import { InterfaceNavigationContribution } from './contrib/interface-navigation/interface-navigation.contribution';
import { MergeConflictContribution } from './contrib/merge-conflict';
import { ResolveConflictRegistry } from './contrib/merge-conflict/merge-conflict.feature.registry';
import { ProblemFixProviderRegistry } from './contrib/problem-fix/problem-fix.feature.registry';
import { RenameCandidatesProviderRegistry } from './contrib/rename/rename.feature.registry';
import { TerminalAIContribution } from './contrib/terminal/terminal-ai.contributon';
import { TerminalFeatureRegistry } from './contrib/terminal/terminal.feature.registry';
Expand Down Expand Up @@ -112,6 +117,10 @@ export class AINativeModule extends BrowserModule {
token: RenameCandidatesProviderRegistryToken,
useClass: RenameCandidatesProviderRegistry,
},
{
token: ProblemFixRegistryToken,
useClass: ProblemFixProviderRegistry,
},
{
token: TerminalRegistryToken,
useClass: TerminalFeatureRegistry,
Expand Down
Loading

0 comments on commit 4d68e23

Please sign in to comment.