Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement line change code edits api #4106

Merged
merged 5 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IRange, InlineCompletion } from '@opensumi/ide-monaco';

import type { ILinterErrorData } from './lint-error.source';
import type { ILinterErrorData } from './source/lint-error.source';

export interface IIntelligentCompletionsResult<T = any> {
readonly items: InlineCompletion[];
Expand All @@ -12,12 +12,18 @@ export interface IIntelligentCompletionsResult<T = any> {

export enum ECodeEditsSource {
LinterErrors = 'lint_errors',
LineChange = 'line_change',
}

export interface ICodeEditsContextBean {
typing: ECodeEditsSource.LinterErrors;
data: ILinterErrorData;
}
export type ICodeEditsContextBean =
| {
typing: ECodeEditsSource.LinterErrors;
data: ILinterErrorData;
}
| {
typing: ECodeEditsSource.LineChange;
data: unknown;
};

export interface ICodeEdit {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import {
mergeMultiLineDiffChanges,
wordChangesToLineChangesMap,
} from './diff-computer';
import { LintErrorCodeEditsSource } from './lint-error.source';

import { ICodeEditsResult } from '.';
import { CodeEditsResultValue } from './source/base';
import { LineChangeCodeEditsSource } from './source/line-change.source';
import { LintErrorCodeEditsSource } from './source/lint-error.source';

export class IntelligentCompletionsController extends BaseAIMonacoEditorController {
public static readonly ID = 'editor.contrib.ai.intelligent.completions';
Expand Down Expand Up @@ -171,7 +171,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}
}

public async fetchProvider(editsResult: ICodeEditsResult): Promise<void> {
private async fetchProvider(editsResult: CodeEditsResultValue): Promise<void> {
// 如果上一次补全结果还在,则不重复请求
const isVisible = this.aiNativeContextKey.multiLineEditsIsVisible.get();
if (isVisible) {
Expand All @@ -183,7 +183,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}
}

private applyInlineDecorations(completionModel: ICodeEditsResult) {
private applyInlineDecorations(completionModel: CodeEditsResultValue) {
const { items } = completionModel;
const { range, insertText } = items[0];

Expand Down Expand Up @@ -377,7 +377,19 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
}),
);

const lintErrorCodeEditsSource = this.injector.get(LintErrorCodeEditsSource, [this.monacoEditor, this.token]);
const lintErrorCodeEditsSource = this.injector.get(LintErrorCodeEditsSource, [this.monacoEditor]);
const lineChangeCodeEditsSource = this.injector.get(LineChangeCodeEditsSource, [this.monacoEditor]);

this.featureDisposable.addDispose(
autorun((reader) => {
const source = lintErrorCodeEditsSource.codeEditsResult.read(reader);
if (source) {
this.fetchProvider(source);
}
}),
);

Ricbet marked this conversation as resolved.
Show resolved Hide resolved
this.featureDisposable.addDispose(lintErrorCodeEditsSource.mount());
this.featureDisposable.addDispose(lineChangeCodeEditsSource.mount());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Autowired, Injectable, Optional } from '@opensumi/di';
import {
CancellationTokenSource,
Disposable,
IntelligentCompletionsRegistryToken,
MaybePromise,
} from '@opensumi/ide-core-common';
import { ICodeEditor, IPosition } from '@opensumi/ide-monaco';
import { disposableObservableValue, transaction } from '@opensumi/monaco-editor-core/esm/vs/base/common/observable';

import { ICodeEdit, ICodeEditsContextBean, ICodeEditsResult } from '../index';
import { IntelligentCompletionsRegistry } from '../intelligent-completions.feature.registry';

@Injectable({ multiple: true })
export abstract class BaseCodeEditsSource extends Disposable {
@Autowired(IntelligentCompletionsRegistryToken)
private readonly intelligentCompletionsRegistry: IntelligentCompletionsRegistry;

protected abstract doTrigger(...args: any[]): MaybePromise<void>;
Ricbet marked this conversation as resolved.
Show resolved Hide resolved

constructor(@Optional() protected readonly monacoEditor: ICodeEditor) {
super();
}

protected cancellationTokenSource = new CancellationTokenSource();

protected get model() {
return this.monacoEditor.getModel();
}
Ricbet marked this conversation as resolved.
Show resolved Hide resolved

protected get token() {
return this.cancellationTokenSource.token;
}

public cancelToken() {
this.cancellationTokenSource.cancel();
this.cancellationTokenSource = new CancellationTokenSource();
}
Ricbet marked this conversation as resolved.
Show resolved Hide resolved

public readonly codeEditsResult = disposableObservableValue<CodeEditsResultValue | undefined>(
'codeEditsResult',
undefined,
);

protected async launchProvider(editor: ICodeEditor, position: IPosition, bean: ICodeEditsContextBean): Promise<void> {
const provider = this.intelligentCompletionsRegistry.getCodeEditsProvider();
if (provider) {
const result = await provider(editor, position, bean, this.token);

if (result) {
const codeEditsResultValue = new CodeEditsResultValue(result);

transaction((tx) => {
this.codeEditsResult.set(codeEditsResultValue, tx);
});
}
}
}
}

export class CodeEditsResultValue extends Disposable {
constructor(private raw: ICodeEditsResult) {
super();
}

public get items(): ICodeEdit[] {
return this.raw.items;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable } from '@opensumi/di';
import { IDisposable } from '@opensumi/ide-core-common';
import { ICursorPositionChangedEvent } from '@opensumi/ide-monaco';

import { ECodeEditsSource } from '../index';

import { BaseCodeEditsSource } from './base';

@Injectable({ multiple: true })
export class LineChangeCodeEditsSource extends BaseCodeEditsSource {
public mount(): IDisposable {
let prePosition = this.monacoEditor.getPosition();

this.addDispose(
this.monacoEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
const currentPosition = event.position;
if (prePosition && prePosition.lineNumber !== currentPosition.lineNumber) {
this.doTrigger();
}
prePosition = currentPosition;
}),
Ricbet marked this conversation as resolved.
Show resolved Hide resolved
);
return this;
}

protected doTrigger() {
const position = this.monacoEditor.getPosition();
if (!position) {
return;
}

this.launchProvider(this.monacoEditor, position, {
typing: ECodeEditsSource.LineChange,
data: {},
});
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { Autowired, Injectable, Optional } from '@opensumi/di';
import {
CancellationToken,
Disposable,
IDisposable,
IntelligentCompletionsRegistryToken,
} from '@opensumi/ide-core-common';
import { ICodeEditor, ICursorPositionChangedEvent, IPosition, Position } from '@opensumi/ide-monaco';
import { Autowired, Injectable } from '@opensumi/di';
import { IDisposable } from '@opensumi/ide-core-common';
import { ICursorPositionChangedEvent, IPosition, Position } from '@opensumi/ide-monaco';
import { URI } from '@opensumi/ide-monaco/lib/browser/monaco-api';
import { IWorkspaceService } from '@opensumi/ide-workspace';
import { StandaloneServices } from '@opensumi/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices';
Expand All @@ -16,10 +11,9 @@ import {
MarkerSeverity,
} from '@opensumi/monaco-editor-core/esm/vs/platform/markers/common/markers';

import { IntelligentCompletionsController } from './intelligent-completions.controller';
import { IntelligentCompletionsRegistry } from './intelligent-completions.feature.registry';
import { ECodeEditsSource } from '..';

Ricbet marked this conversation as resolved.
Show resolved Hide resolved
import { ECodeEditsSource } from '.';
import { BaseCodeEditsSource } from './base';

export interface ILinterErrorData {
relativeWorkspacePath: string;
Expand Down Expand Up @@ -59,24 +53,10 @@ namespace MarkerErrorData {
}

@Injectable({ multiple: true })
export class LintErrorCodeEditsSource extends Disposable {
@Autowired(IntelligentCompletionsRegistryToken)
private readonly intelligentCompletionsRegistry: IntelligentCompletionsRegistry;

export class LintErrorCodeEditsSource extends BaseCodeEditsSource {
@Autowired(IWorkspaceService)
private readonly workspaceService: IWorkspaceService;

private get model() {
return this.monacoEditor.getModel();
}

constructor(
@Optional() private readonly monacoEditor: ICodeEditor,
@Optional() private readonly token: CancellationToken,
) {
super();
}

public mount(): IDisposable {
let prePosition = this.monacoEditor.getPosition();

Expand All @@ -93,7 +73,7 @@ export class LintErrorCodeEditsSource extends Disposable {
return this;
}

private async doTrigger(position: Position) {
protected async doTrigger(position: Position) {
if (!this.model) {
return;
}
Expand All @@ -105,26 +85,15 @@ export class LintErrorCodeEditsSource extends Disposable {
markers = markers.filter((marker) => Math.abs(marker.startLineNumber - position.lineNumber) <= 1);

if (markers.length) {
const provider = this.intelligentCompletionsRegistry.getCodeEditsProvider();
if (provider) {
const relativeWorkspacePath = await this.workspaceService.asRelativePath(resource.path);
const result = await provider(
this.monacoEditor,
position,
{
typing: ECodeEditsSource.LinterErrors,
data: {
relativeWorkspacePath: relativeWorkspacePath?.path ?? resource.path,
errors: markers.map((marker) => MarkerErrorData.toData(marker)),
},
},
this.token,
);
const relativeWorkspacePath = await this.workspaceService.asRelativePath(resource.path);

if (result) {
IntelligentCompletionsController.get(this.monacoEditor)?.fetchProvider(result);
}
}
this.launchProvider(this.monacoEditor, position, {
typing: ECodeEditsSource.LinterErrors,
data: {
relativeWorkspacePath: relativeWorkspacePath?.path ?? resource.path,
errors: markers.map((marker) => MarkerErrorData.toData(marker)),
},
});
}
}
}
Loading