Skip to content

Commit

Permalink
feat: support accept & esc
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricbet committed Jul 31, 2024
1 parent b8fbb61 commit 8bd600c
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { IDiffChangeResult } from '@opensumi/ide-ai-native/lib/browser/contrib/intelligent-completions/diff-computer';
import {
GHOST_TEXT,
GHOST_TEXT_DESCRIPTION,
MultiLineDecorationModel,
} from '@opensumi/ide-ai-native/lib/browser/contrib/intelligent-completions/multi-line.decoration';
import {
ICodeEditor,
IEditorDecorationsCollection,
IModelDeltaDecoration,
IPosition,
ITextModel,
Position,
Range,
} from '@opensumi/ide-monaco';
import { ICodeEditor, IPosition } from '@opensumi/ide-monaco';
import { monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api';

import { IDiffChangeResult } from '../../../../lib/browser/contrib/intelligent-completions/diff-computer';
import { EnhanceDecorationsCollection } from '../../../../src/browser/model/enhanceDecorationsCollection';

describe('MultiLineDecorationModel', () => {
let editor: ICodeEditor;
let decorationsCollection: IEditorDecorationsCollection;
let decorationsCollection: EnhanceDecorationsCollection;
let multiLineDecorationModel: MultiLineDecorationModel;

beforeEach(() => {
Expand Down
20 changes: 16 additions & 4 deletions packages/ai-native/src/browser/ai-core.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,14 @@ export class AINativeBrowserContribution

this.contributions.getContributions().forEach((contribution) => {
const contributions = [
{ key: contribution.registerInlineChatFeature?.bind(contribution), registry: this.inlineChatFeatureRegistry },
{ key: contribution.registerChatFeature?.bind(contribution), registry: this.chatFeatureRegistry },
{
key: contribution.registerInlineChatFeature?.bind(contribution),
registry: this.inlineChatFeatureRegistry,
},
{
key: contribution.registerChatFeature?.bind(contribution),
registry: this.chatFeatureRegistry,
},
{
key: contribution.registerResolveConflictFeature?.bind(contribution),
registry: this.resolveConflictRegistry,
Expand All @@ -247,8 +253,14 @@ export class AINativeBrowserContribution
key: contribution.registerRenameProvider?.bind(contribution),
registry: this.renameCandidatesProviderRegistry,
},
{ key: contribution.registerChatRender?.bind(contribution), registry: this.chatRenderRegistry },
{ key: contribution.registerTerminalProvider?.bind(contribution), registry: this.terminalProviderRegistry },
{
key: contribution.registerChatRender?.bind(contribution),
registry: this.chatRenderRegistry,
},
{
key: contribution.registerTerminalProvider?.bind(contribution),
registry: this.terminalProviderRegistry,
},
{
key: contribution.registerIntelligentCompletionFeature?.bind(contribution),
registry: this.intelligentCompletionsRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
InlineDiffPartialEditsIsVisible,
InlineHintWidgetIsVisible,
InlineInputWidgetIsVisible,
MultiLineCompletionsIsVisible,
} from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
import { ContextKeyService } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/browser/contextKeyService';
import { IContextKeyServiceTarget } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
Expand All @@ -22,6 +23,7 @@ export class AINativeContextKey {
public readonly inlineHintWidgetIsVisible: IContextKey<boolean>;
public readonly inlineInputWidgetIsVisible: IContextKey<boolean>;
public readonly inlineDiffPartialEditsIsVisible: IContextKey<boolean>;
public readonly multiLineCompletionsIsVisible: IContextKey<boolean>;

constructor(@Optional() dom?: HTMLElement | IContextKeyServiceTarget | ContextKeyService) {
this._contextKeyService = this.globalContextKeyService.createScoped(dom);
Expand All @@ -30,5 +32,6 @@ export class AINativeContextKey {
this.inlineHintWidgetIsVisible = InlineHintWidgetIsVisible.bind(this._contextKeyService);
this.inlineInputWidgetIsVisible = InlineInputWidgetIsVisible.bind(this._contextKeyService);
this.inlineDiffPartialEditsIsVisible = InlineDiffPartialEditsIsVisible.bind(this._contextKeyService);
this.multiLineCompletionsIsVisible = MultiLineCompletionsIsVisible.bind(this._contextKeyService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import debounce from 'lodash/debounce';
import { Autowired, Injectable } from '@opensumi/di';
import { IDisposable } from '@opensumi/ide-core-browser';
import { AI_INLINE_COMPLETION_VISIBLE } from '@opensumi/ide-core-browser/lib/ai-native/command';
import { CommandServiceImpl, Disposable, IEventBus, Sequencer, runWhenIdle } from '@opensumi/ide-core-common';
import { CommandRegistry, CommandRegistryImpl, CommandService } from '@opensumi/ide-core-common';
import {
CommandService,
CommandServiceImpl,
Disposable,
IEventBus,
Sequencer,
runWhenIdle,
} from '@opensumi/ide-core-common';
import { EditorSelectionChangeEvent, IEditor } from '@opensumi/ide-editor/lib/browser';
import { InlineCompletions, Position, Range } from '@opensumi/ide-monaco';
import { monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api';
Expand All @@ -24,9 +30,6 @@ export class InlineCompletionHandler extends IAIMonacoContribHandler {
@Autowired(CommandService)
private commandService: CommandServiceImpl;

@Autowired(CommandRegistry)
private commandRegistry: CommandRegistryImpl;

@Autowired(AIInlineCompletionsProvider)
private readonly aiInlineCompletionsProvider: AIInlineCompletionsProvider;

Expand Down Expand Up @@ -135,8 +138,6 @@ export class InlineCompletionHandler extends IAIMonacoContribHandler {
return;
}

let resultList: InlineCompletions;

/**
* 如果新字符在 inline completion 的 ghost text 内,则走缓存,不重新请求
*/
Expand All @@ -153,7 +154,7 @@ export class InlineCompletionHandler extends IAIMonacoContribHandler {
}
}

resultList = await this.sequencer.queue(() =>
const resultList: InlineCompletions = await this.sequencer.queue(() =>
this.aiInlineCompletionsProvider.provideInlineCompletionItems(model, position, context, token),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Autowired } from '@opensumi/di';
import { Key } from '@opensumi/ide-core-browser';
import {
ClientAppContribution,
KeybindingContribution,
KeybindingRegistry,
KeybindingScope,
} from '@opensumi/ide-core-browser';
import {
AI_MULTI_LINE_COMPLETION_ACCEPT,
AI_MULTI_LINE_COMPLETION_HIDE,
} from '@opensumi/ide-core-browser/lib/ai-native/command';
import { MultiLineCompletionsIsVisible } from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
import { CommandContribution, CommandRegistry, Domain } from '@opensumi/ide-core-common';

import { IntelligentCompletionsHandler } from './intelligent-completions.handler';

@Domain(ClientAppContribution, KeybindingContribution, CommandContribution)
export class IntelligentCompletionsContribution

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / build-windows

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / unittest (macos-latest, 18.x, node)

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / ubuntu-latest, Node.js 20.x

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / build (macos-latest, 20.x)

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / unittest (macos-latest, 18.x, jsdom)

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 20.x)

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / unittest (ubuntu-latest, 18.x, node)

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.

Check failure on line 19 in packages/ai-native/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts

View workflow job for this annotation

GitHub Actions / unittest (ubuntu-latest, 18.x, jsdom)

Type 'IntelligentCompletionsContribution' has no properties in common with type 'ClientAppContribution'.
implements ClientAppContribution, KeybindingContribution, CommandContribution
{
@Autowired(IntelligentCompletionsHandler)
private readonly intelligentCompletionsHandler: IntelligentCompletionsHandler;

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(AI_MULTI_LINE_COMPLETION_HIDE, {
execute: () => {
this.intelligentCompletionsHandler.hide();
},
});

commands.registerCommand(AI_MULTI_LINE_COMPLETION_ACCEPT, {
execute: () => {
this.intelligentCompletionsHandler.accept();
},
});
}

registerKeybindings(keybindings: KeybindingRegistry): void {
keybindings.registerKeybinding({
command: AI_MULTI_LINE_COMPLETION_HIDE.id,
keybinding: Key.ESCAPE.code,
when: MultiLineCompletionsIsVisible.raw,
priority: 100,
});

keybindings.registerKeybinding(
{
command: AI_MULTI_LINE_COMPLETION_ACCEPT.id,
keybinding: Key.TAB.code,
when: MultiLineCompletionsIsVisible.raw,
},
KeybindingScope.USER,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Autowired, Injectable } from '@opensumi/di';
import { Autowired, INJECTOR_TOKEN, Injectable, Injector } from '@opensumi/di';
import {
CancellationTokenSource,
Disposable,
Expand All @@ -7,15 +7,21 @@ import {
IntelligentCompletionsRegistryToken,
} from '@opensumi/ide-core-common';
import { IEditor } from '@opensumi/ide-editor';
import { CursorChangeReason, ICursorSelectionChangedEvent } from '@opensumi/ide-monaco';
import { CursorChangeReason, ICursorSelectionChangedEvent, Position } from '@opensumi/ide-monaco';
import { EditOperation } from '@opensumi/monaco-editor-core/esm/vs/editor/common/core/editOperation';
import { TextEditorSelectionSource } from '@opensumi/monaco-editor-core/esm/vs/platform/editor/common/editor';

import { AINativeContextKey } from '../../contextkey/ai-native.contextkey.service';

import { MultiLineDiffComputer } from './diff-computer';
import { IntelligentCompletionsRegistry } from './intelligent-completions.feature.registry';
import { MultiLineDecorationModel } from './multi-line.decoration';

@Injectable()
export class IntelligentCompletionsHandler extends Disposable {
@Autowired(INJECTOR_TOKEN)
private readonly injector: Injector;

@Autowired(IntelligentCompletionsRegistryToken)
private intelligentCompletionsRegistry: IntelligentCompletionsRegistry;

Expand All @@ -30,8 +36,9 @@ export class IntelligentCompletionsHandler extends Disposable {
private multiLineDecorationModel: MultiLineDecorationModel;

private editor: IEditor;
private aiNativeContextKey: AINativeContextKey;

private async update() {
private async fetch() {
const provider = this.intelligentCompletionsRegistry.getProvider();
if (!provider) {
return;
Expand All @@ -46,6 +53,10 @@ export class IntelligentCompletionsHandler extends Disposable {

const { items } = intelligentCompletionModel;

if (items.length === 0) {
return;
}

const { belowRadius, aboveRadius, content } = items[0];

const originalContent = model?.getValueInRange({
Expand All @@ -55,19 +66,9 @@ export class IntelligentCompletionsHandler extends Disposable {
endColumn: model.getLineMaxColumn(position.lineNumber + (belowRadius || 0)),
});

let diffComputerResult = this.multiLineDiffComputer.diff(originalContent!, content);
const diffComputerResult = this.multiLineDiffComputer.diff(originalContent!, content);

// console.log('intelligentCompletionModel:>>> intelligentCompletionDiffComputerResult \n ', diffComputerResult);
// console.log('intelligentCompletionModel:>>> intelligentCompletionDiffComputerResult: >> provider content: \n ', content);
if (diffComputerResult) {
diffComputerResult = diffComputerResult.map((result) => {
if (result.removed) {
result.added = undefined;
result.removed = undefined;
}
return result;
});

const inlineModifications = this.multiLineDecorationModel.applyInlineDecorations(
monacoEditor,
diffComputerResult,
Expand All @@ -76,43 +77,69 @@ export class IntelligentCompletionsHandler extends Disposable {
);

if (inlineModifications) {
this.aiNativeContextKey.multiLineCompletionsIsVisible.set(true);
this.multiLineDecorationModel.updateLineModificationDecorations(inlineModifications);
} else {
this.aiNativeContextKey.multiLineCompletionsIsVisible.reset();
this.multiLineDecorationModel.clearDecorations();
}
// console.log('intelligentCompletionModel:>>> decorationModel 。 decorationModel \n ', inlineModifications);
}
}

public hide() {
this.cancelToken();
this.aiNativeContextKey.multiLineCompletionsIsVisible.reset();
this.multiLineDecorationModel.clearDecorations();
}

public accept() {
const edits = this.multiLineDecorationModel.getEdits();

this.editor.monacoEditor.pushUndoStop();
this.editor.monacoEditor.executeEdits(
'multiLineCompletions.accept',
edits.map((edit) =>
EditOperation.insert(
Position.lift({ lineNumber: edit.range.startLineNumber, column: edit.range.startColumn }),
edit.text,
),
),
);

this.hide();
}

public registerFeature(editor: IEditor): IDisposable {
this.editor = editor;
const { monacoEditor } = editor;

this.multiLineDecorationModel = new MultiLineDecorationModel(monacoEditor);

const stop = () => {
this.cancelToken();
this.multiLineDecorationModel.clearDecorations();
};
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [monacoEditor.contextKeyService]);

/**
* 触发时机与 inline completion 保持一致
*/
this.addDispose([
monacoEditor.onDidType(() => {
this.update();
Event.debounce(
monacoEditor.onDidType,
() => {},
16 * 3,
)(() => {
// 取消上一次正在请求中的补全
this.cancelToken();
this.fetch();
}),

Event.any<any>(
monacoEditor.onDidChangeModel,
monacoEditor.onDidBlurEditorWidget,
// monacoEditor.onDidBlurEditorWidget,
)(() => {
stop();
this.hide();
}),

monacoEditor.onDidChangeCursorSelection((event: ICursorSelectionChangedEvent) => {
if (event.reason === CursorChangeReason.Explicit || event.source === TextEditorSelectionSource.PROGRAMMATIC) {
stop();
this.hide();
}
}),
]);
Expand Down
Loading

0 comments on commit 8bd600c

Please sign in to comment.