Skip to content

Commit c18b79d

Browse files
authored
Merge branch 'main' into jl.tt
2 parents c32716c + 6131e9a commit c18b79d

95 files changed

Lines changed: 3518 additions & 938 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
6060
- name: Upload test results
6161
if: always()
62-
uses: actions/upload-artifact@v3
62+
uses: actions/upload-artifact@v4
6363
with:
6464
name: playwright-report
6565
path: tools/playwright/test-results

packages/ai-native/src/browser/ai-core.contextkeys.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
InlineCompletionIsTrigger,
66
InlineDiffPartialEditsIsVisible,
77
InlineHintWidgetIsVisible,
8+
InlineInputWidgetIsStreaming,
89
InlineInputWidgetIsVisible,
910
MultiLineEditsIsVisible,
1011
} from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
@@ -22,6 +23,7 @@ export class AINativeContextKey {
2223
public readonly inlineCompletionIsTrigger: IContextKey<boolean>;
2324
public readonly inlineHintWidgetIsVisible: IContextKey<boolean>;
2425
public readonly inlineInputWidgetIsVisible: IContextKey<boolean>;
26+
public readonly inlineInputWidgetIsStreaming: IContextKey<boolean>;
2527
public readonly inlineDiffPartialEditsIsVisible: IContextKey<boolean>;
2628
public readonly multiLineEditsIsVisible: IContextKey<boolean>;
2729
public get contextKeyService() {
@@ -34,6 +36,7 @@ export class AINativeContextKey {
3436
this.inlineCompletionIsTrigger = InlineCompletionIsTrigger.bind(this._contextKeyService);
3537
this.inlineHintWidgetIsVisible = InlineHintWidgetIsVisible.bind(this._contextKeyService);
3638
this.inlineInputWidgetIsVisible = InlineInputWidgetIsVisible.bind(this._contextKeyService);
39+
this.inlineInputWidgetIsStreaming = InlineInputWidgetIsStreaming.bind(this._contextKeyService);
3740
this.inlineDiffPartialEditsIsVisible = InlineDiffPartialEditsIsVisible.bind(this._contextKeyService);
3841
this.multiLineEditsIsVisible = MultiLineEditsIsVisible.bind(this._contextKeyService);
3942
}

packages/ai-native/src/browser/ai-core.contribution.ts

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
} from '@opensumi/ide-core-browser';
2828
import {
2929
AI_CHAT_VISIBLE,
30+
AI_INLINE_CHAT_INTERACTIVE_INPUT_CANCEL,
3031
AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE,
3132
AI_INLINE_CHAT_VISIBLE,
3233
AI_INLINE_COMPLETION_REPORTER,
@@ -37,6 +38,7 @@ import {
3738
InlineChatIsVisible,
3839
InlineDiffPartialEditsIsVisible,
3940
InlineHintWidgetIsVisible,
41+
InlineInputWidgetIsStreaming,
4042
InlineInputWidgetIsVisible,
4143
} from '@opensumi/ide-core-browser/lib/contextkey/ai-native';
4244
import { DesignLayoutConfig } from '@opensumi/ide-core-browser/lib/layout/constants';
@@ -56,8 +58,9 @@ import {
5658
runWhenIdle,
5759
} from '@opensumi/ide-core-common';
5860
import { DESIGN_MENU_BAR_RIGHT } from '@opensumi/ide-design';
59-
import { IEditor } from '@opensumi/ide-editor';
61+
import { IEditor, WorkbenchEditorService } from '@opensumi/ide-editor';
6062
import { BrowserEditorContribution, IEditorFeatureRegistry } from '@opensumi/ide-editor/lib/browser';
63+
import { WorkbenchEditorServiceImpl } from '@opensumi/ide-editor/lib/browser/workbench-editor.service';
6164
import { IMainLayoutService } from '@opensumi/ide-main-layout';
6265
import { ISettingRegistry, SettingContribution } from '@opensumi/ide-preferences';
6366
import { EditorContributionInstantiation } from '@opensumi/monaco-editor-core/esm/vs/editor/browser/editorExtensions';
@@ -101,11 +104,11 @@ import {
101104
} from './types';
102105
import { InlineChatEditorController } from './widget/inline-chat/inline-chat-editor.controller';
103106
import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry';
104-
import { AIInlineChatService } from './widget/inline-chat/inline-chat.service';
107+
import { InlineChatService } from './widget/inline-chat/inline-chat.service';
105108
import { InlineDiffController } from './widget/inline-diff/inline-diff.controller';
106109
import { InlineHintController } from './widget/inline-hint/inline-hint.controller';
107110
import { InlineInputController } from './widget/inline-input/inline-input.controller';
108-
import { InlineInputChatService } from './widget/inline-input/inline-input.service';
111+
import { InlineInputService } from './widget/inline-input/inline-input.service';
109112
import { InlineStreamDiffService } from './widget/inline-stream-diff/inline-stream-diff.service';
110113
import { SumiLightBulbWidget } from './widget/light-bulb';
111114

@@ -191,10 +194,10 @@ export class AINativeBrowserContribution
191194
private readonly chatProxyService: ChatProxyService;
192195

193196
@Autowired(IAIInlineChatService)
194-
private readonly aiInlineChatService: AIInlineChatService;
197+
private readonly aiInlineChatService: InlineChatService;
195198

196-
@Autowired(InlineInputChatService)
197-
private readonly inlineInputChatService: InlineInputChatService;
199+
@Autowired(InlineInputService)
200+
private readonly inlineInputService: InlineInputService;
198201

199202
@Autowired(InlineStreamDiffService)
200203
private readonly inlineStreamDiffService: InlineStreamDiffService;
@@ -205,6 +208,9 @@ export class AINativeBrowserContribution
205208
@Autowired(CodeActionSingleHandler)
206209
private readonly codeActionSingleHandler: CodeActionSingleHandler;
207210

211+
@Autowired(WorkbenchEditorService)
212+
private readonly workbenchEditorService: WorkbenchEditorServiceImpl;
213+
208214
constructor() {
209215
this.registerFeature();
210216
}
@@ -237,7 +243,7 @@ export class AINativeBrowserContribution
237243
EditorContributionInstantiation.BeforeFirstInteraction,
238244
);
239245

240-
if (this.inlineChatFeatureRegistry.getInteractiveInputHandler()) {
246+
if (this.inlineInputService.getInteractiveInputHandler()) {
241247
register(
242248
InlineHintController.ID,
243249
new SyncDescriptor(InlineHintController, [this.injector]),
@@ -418,14 +424,48 @@ export class AINativeBrowserContribution
418424
});
419425

420426
commands.registerCommand(AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE, {
421-
execute: (isVisible: boolean) => {
422-
if (isVisible) {
423-
this.inlineInputChatService.visible();
424-
} else {
425-
this.inlineInputChatService.hide();
427+
execute: async (isVisible: boolean) => {
428+
if (!isVisible) {
429+
this.inlineInputService.hide();
430+
return;
431+
}
432+
433+
// 每次在展示 inline input 的时候,先隐藏 inline chat
434+
this.commandService.executeCommand(AI_INLINE_CHAT_VISIBLE.id, false);
435+
436+
const editor = this.workbenchEditorService.currentCodeEditor;
437+
if (!editor) {
438+
return;
439+
}
440+
441+
const position = editor.monacoEditor.getPosition();
442+
if (!position) {
443+
return;
444+
}
445+
446+
const selection = editor.monacoEditor.getSelection();
447+
const isEmptyLine = position ? editor.monacoEditor.getModel()?.getLineLength(position.lineNumber) === 0 : false;
448+
449+
if (isEmptyLine) {
450+
this.inlineInputService.visibleByPosition(position);
451+
return;
426452
}
427453

428-
this.aiInlineChatService._onInteractiveInputVisible.fire(isVisible);
454+
if (selection && !selection.isEmpty()) {
455+
this.inlineInputService.visibleBySelection(selection);
456+
return;
457+
}
458+
459+
this.inlineInputService.visibleByNearestCodeBlock(position, editor.monacoEditor);
460+
},
461+
});
462+
463+
commands.registerCommand(AI_INLINE_CHAT_INTERACTIVE_INPUT_CANCEL, {
464+
execute: () => {
465+
const editor = this.workbenchEditorService.currentCodeEditor;
466+
if (editor) {
467+
InlineInputController.get(editor.monacoEditor)?.cancelToken();
468+
}
429469
},
430470
});
431471

@@ -516,12 +556,12 @@ export class AINativeBrowserContribution
516556
when: `editorFocus && ${InlineChatIsVisible.raw}`,
517557
});
518558

519-
if (this.inlineChatFeatureRegistry.getInteractiveInputHandler()) {
559+
if (this.inlineInputService.getInteractiveInputHandler()) {
520560
// 当 Inline Chat (浮动组件)展示时,通过 CMD K 唤起 Inline Input
521561
keybindings.registerKeybinding(
522562
{
523563
command: AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE.id,
524-
keybinding: 'ctrlcmd+k',
564+
keybinding: this.aiNativeConfigService.inlineChat.inputKeybinding,
525565
args: true,
526566
priority: 0,
527567
when: `editorFocus && (${InlineChatIsVisible.raw} || inlineSuggestionVisible)`,
@@ -536,11 +576,18 @@ export class AINativeBrowserContribution
536576
priority: 0,
537577
when: `editorFocus && ${InlineInputWidgetIsVisible.raw}`,
538578
});
579+
// 当 Inline Input 流式编辑时,通过 ESC 退出
580+
keybindings.registerKeybinding({
581+
command: AI_INLINE_CHAT_INTERACTIVE_INPUT_CANCEL.id,
582+
keybinding: 'esc',
583+
priority: 1,
584+
when: `editorFocus && ${InlineInputWidgetIsStreaming.raw}`,
585+
});
539586
// 当出现 CMD K 展示信息时,通过快捷键快速唤起 Inline Input
540587
keybindings.registerKeybinding(
541588
{
542589
command: AI_INLINE_CHAT_INTERACTIVE_INPUT_VISIBLE.id,
543-
keybinding: 'ctrlcmd+k',
590+
keybinding: this.aiNativeConfigService.inlineChat.inputKeybinding,
544591
args: true,
545592
priority: 0,
546593
when: `editorFocus && ${InlineHintWidgetIsVisible.raw} && ${InlineChatIsVisible.not}`,

packages/ai-native/src/browser/contrib/intelligent-completions/source/base.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ export abstract class BaseCodeEditsSource extends Disposable {
6969
private cancellationTokenSource = new CancellationTokenSource();
7070
private readonly relationID = observableValue<string | undefined>(this, undefined);
7171

72-
protected abstract doTrigger(...args: any[]): MaybePromise<void>;
73-
7472
public readonly codeEditsContextBean = disposableObservableValue<CodeEditsContextBean | undefined>(this, undefined);
7573
public abstract priority: number;
7674
public abstract mount(): IDisposable;
Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,102 @@
11
import { Injectable } from '@opensumi/di';
22
import { AINativeSettingSectionsId, ECodeEditsSourceTyping, IDisposable } from '@opensumi/ide-core-common';
3-
import { ICursorPositionChangedEvent, Position } from '@opensumi/ide-monaco';
3+
import { ICursorPositionChangedEvent, IModelContentChangedEvent } from '@opensumi/ide-monaco';
4+
import {
5+
autorunDelta,
6+
derivedHandleChanges,
7+
observableFromEvent,
8+
recomputeInitiallyAndOnChange,
9+
} from '@opensumi/ide-monaco/lib/common/observable';
410

511
import { BaseCodeEditsSource } from './base';
612

713
export interface ILineChangeData {
814
currentLineNumber: number;
915
preLineNumber?: number;
16+
change?: IModelContentChangedEvent;
1017
}
1118

1219
@Injectable({ multiple: true })
1320
export class LineChangeCodeEditsSource extends BaseCodeEditsSource {
1421
public priority = 2;
1522

16-
private prePosition = this.monacoEditor.getPosition();
17-
1823
public mount(): IDisposable {
24+
const modelContentChangeObs = observableFromEvent<IModelContentChangedEvent>(
25+
this,
26+
this.monacoEditor.onDidChangeModelContent,
27+
(event: IModelContentChangedEvent) => event,
28+
);
29+
const positionChangeObs = observableFromEvent<ICursorPositionChangedEvent>(
30+
this,
31+
this.monacoEditor.onDidChangeCursorPosition,
32+
(event: ICursorPositionChangedEvent) => event,
33+
);
34+
35+
const latestModelContentChangeObs = derivedHandleChanges(
36+
{
37+
owner: this,
38+
createEmptyChangeSummary: () => ({ change: undefined }),
39+
handleChange: (ctx, changeSummary: { change: IModelContentChangedEvent | undefined }) => {
40+
// 如果只是改了光标则设置 change 为空,避免获取到缓存的 change
41+
if (ctx.didChange(positionChangeObs)) {
42+
changeSummary.change = undefined;
43+
} else {
44+
changeSummary.change = modelContentChangeObs.get();
45+
}
46+
return true;
47+
},
48+
},
49+
(reader, changeSummary) => {
50+
positionChangeObs.read(reader);
51+
modelContentChangeObs.read(reader);
52+
return changeSummary.change;
53+
},
54+
);
55+
56+
this.addDispose(recomputeInitiallyAndOnChange(latestModelContentChangeObs));
57+
58+
let lastModelContent: IModelContentChangedEvent | undefined;
1959
this.addDispose(
20-
this.monacoEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
21-
const currentPosition = event.position;
22-
if (this.prePosition && this.prePosition.lineNumber !== currentPosition.lineNumber) {
23-
this.doTrigger(currentPosition);
24-
this.prePosition = currentPosition;
25-
}
60+
/**
61+
* 由于 monaco 的 changeModelContent 事件比 changeCursorPosition 事件先触发,所以这里需要拿上一次的值进行消费
62+
* 否则永远返回 undefined
63+
*/
64+
autorunDelta(latestModelContentChangeObs, ({ lastValue }) => {
65+
lastModelContent = lastValue;
2666
}),
2767
);
28-
return this;
29-
}
3068

31-
protected doTrigger(position: Position) {
32-
const isLineChangeEnabled = this.preferenceService.getValid(AINativeSettingSectionsId.CodeEditsLineChange, false);
69+
this.addDispose(
70+
autorunDelta(positionChangeObs, ({ lastValue, newValue }) => {
71+
const contentChange = lastModelContent;
3372

34-
if (!isLineChangeEnabled || !position) {
35-
return;
36-
}
73+
const isLineChangeEnabled = this.preferenceService.getValid(
74+
AINativeSettingSectionsId.CodeEditsLineChange,
75+
false,
76+
);
77+
if (!isLineChangeEnabled) {
78+
return false;
79+
}
3780

38-
this.setBean({
39-
typing: ECodeEditsSourceTyping.LineChange,
40-
position,
41-
data: {
42-
preLineNumber: this.prePosition?.lineNumber,
43-
currentLineNumber: position.lineNumber,
44-
},
45-
});
81+
const prePosition = lastValue?.position;
82+
const currentPosition = newValue?.position;
83+
if (prePosition && prePosition.lineNumber !== currentPosition?.lineNumber) {
84+
this.setBean({
85+
typing: ECodeEditsSourceTyping.LineChange,
86+
position: currentPosition,
87+
data: {
88+
preLineNumber: prePosition.lineNumber,
89+
currentLineNumber: currentPosition.lineNumber,
90+
change: contentChange,
91+
},
92+
});
93+
}
94+
95+
// 消费完之后设置为 undefined,避免下次获取到缓存的值
96+
lastModelContent = undefined;
97+
}),
98+
);
99+
100+
return this;
46101
}
47102
}

packages/ai-native/src/browser/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { LanguageParserService } from './languages/service';
4646
import { AINativePreferencesContribution } from './preferences';
4747
import { AINativeCoreContribution } from './types';
4848
import { InlineChatFeatureRegistry } from './widget/inline-chat/inline-chat.feature.registry';
49-
import { AIInlineChatService } from './widget/inline-chat/inline-chat.service';
49+
import { InlineChatService } from './widget/inline-chat/inline-chat.service';
5050
import { InlineDiffService } from './widget/inline-diff';
5151

5252
@Injectable()
@@ -90,7 +90,7 @@ export class AINativeModule extends BrowserModule {
9090
},
9191
{
9292
token: IAIInlineChatService,
93-
useClass: AIInlineChatService,
93+
useClass: InlineChatService,
9494
},
9595
{
9696
token: IChatManagerService,

packages/ai-native/src/browser/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
MaybePromise,
1414
MergeConflictEditorMode,
1515
} from '@opensumi/ide-core-common';
16-
import { ICodeEditor, IRange, ITextModel, NewSymbolNamesProvider, Position } from '@opensumi/ide-monaco';
16+
import { ICodeEditor, IRange, ISelection, ITextModel, NewSymbolNamesProvider, Position } from '@opensumi/ide-monaco';
1717
import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream';
1818
import { IMarker } from '@opensumi/monaco-editor-core/esm/vs/platform/markers/common/markers';
1919

@@ -44,7 +44,7 @@ interface IBaseInlineChatHandler<T extends any[]> {
4444

4545
export type IEditorInlineChatHandler = IBaseInlineChatHandler<[editor: ICodeEditor, token: CancellationToken]>;
4646
export type IInteractiveInputHandler = IBaseInlineChatHandler<
47-
[editor: ICodeEditor, value: string, token: CancellationToken]
47+
[editor: ICodeEditor, selection: ISelection, value: string, token: CancellationToken]
4848
>;
4949

5050
export enum ERunStrategy {

0 commit comments

Comments
 (0)