@opensumi/ide-ai-native 3.7.2-next-1740450374.0 → 3.8.1-next-1740452092.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +4 -0
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat.module.less +2 -2
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +14 -19
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/chat-history.css +1 -1
- package/lib/browser/components/components.module.less +2 -2
- package/lib/browser/contrib/intelligent-completions/index.d.ts +12 -7
- package/lib/browser/contrib/intelligent-completions/index.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/index.js +13 -2
- package/lib/browser/contrib/intelligent-completions/index.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.d.ts +2 -7
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.js +6 -106
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/base.d.ts +18 -0
- package/lib/browser/contrib/intelligent-completions/view/base.d.ts.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/base.js +18 -0
- package/lib/browser/contrib/intelligent-completions/view/base.js.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.d.ts +19 -0
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.d.ts.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.js +67 -0
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.js.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/default.d.ts +11 -0
- package/lib/browser/contrib/intelligent-completions/view/default.d.ts.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/default.js +104 -0
- package/lib/browser/contrib/intelligent-completions/view/default.js.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/legacy.d.ts +16 -0
- package/lib/browser/contrib/intelligent-completions/view/legacy.d.ts.map +1 -0
- package/lib/browser/contrib/intelligent-completions/view/legacy.js +126 -0
- package/lib/browser/contrib/intelligent-completions/view/legacy.js.map +1 -0
- package/lib/browser/layout/layout.module.less +1 -1
- package/lib/browser/mcp/tools/components/index.module.less +4 -0
- package/lib/browser/preferences/schema.d.ts.map +1 -1
- package/lib/browser/preferences/schema.js +7 -0
- package/lib/browser/preferences/schema.js.map +1 -1
- package/lib/browser/widget/inline-hint/inline-hint.controller.d.ts.map +1 -1
- package/lib/browser/widget/inline-hint/inline-hint.controller.js +4 -2
- package/lib/browser/widget/inline-hint/inline-hint.controller.js.map +1 -1
- package/package.json +23 -23
- package/src/browser/ai-core.contribution.ts +4 -0
- package/src/browser/chat/chat.module.less +2 -2
- package/src/browser/chat/chat.view.tsx +29 -30
- package/src/browser/components/chat-history.css +1 -1
- package/src/browser/components/components.module.less +2 -2
- package/src/browser/contrib/intelligent-completions/index.ts +27 -8
- package/src/browser/contrib/intelligent-completions/intelligent-completions.controller.ts +10 -165
- package/src/browser/contrib/intelligent-completions/view/base.ts +31 -0
- package/src/browser/contrib/intelligent-completions/view/code-edits-previewer.ts +69 -0
- package/src/browser/contrib/intelligent-completions/view/default.ts +146 -0
- package/src/browser/contrib/intelligent-completions/view/legacy.ts +182 -0
- package/src/browser/layout/layout.module.less +1 -1
- package/src/browser/mcp/tools/components/index.module.less +4 -0
- package/src/browser/preferences/schema.ts +8 -0
- package/src/browser/widget/inline-hint/inline-hint.controller.ts +5 -1
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
IntelligentCompletionsRegistryToken,
|
|
17
17
|
runWhenIdle,
|
|
18
18
|
} from '@opensumi/ide-core-common';
|
|
19
|
-
import { Emitter, ICodeEditor, ICursorPositionChangedEvent,
|
|
19
|
+
import { Emitter, ICodeEditor, ICursorPositionChangedEvent, ITextModel } from '@opensumi/ide-monaco';
|
|
20
20
|
import {
|
|
21
21
|
IObservable,
|
|
22
22
|
ISettableObservable,
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
observableValue,
|
|
31
31
|
transaction,
|
|
32
32
|
} from '@opensumi/ide-monaco/lib/common/observable';
|
|
33
|
-
import { empty } from '@opensumi/ide-utils/lib/strings';
|
|
34
33
|
import { EditorContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/common/editorContextKeys';
|
|
35
34
|
import { inlineSuggestCommitId } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/controller/commandIds';
|
|
36
35
|
import { InlineCompletionContextKeys } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionContextKeys';
|
|
@@ -42,23 +41,15 @@ import {
|
|
|
42
41
|
import { ContextKeyExpr } from '@opensumi/monaco-editor-core/esm/vs/platform/contextkey/common/contextkey';
|
|
43
42
|
|
|
44
43
|
import { AINativeContextKey } from '../../ai-core.contextkeys';
|
|
45
|
-
import { REWRITE_DECORATION_INLINE_ADD, RewriteWidget } from '../../widget/rewrite/rewrite-widget';
|
|
46
44
|
import { BaseAIMonacoEditorController } from '../base';
|
|
47
45
|
|
|
48
|
-
import { AdditionsDeletionsDecorationModel } from './decoration/additions-deletions.decoration';
|
|
49
|
-
import { MultiLineDecorationModel } from './decoration/multi-line.decoration';
|
|
50
|
-
import {
|
|
51
|
-
IMultiLineDiffChangeResult,
|
|
52
|
-
computeMultiLineDiffChanges,
|
|
53
|
-
mergeMultiLineDiffChanges,
|
|
54
|
-
wordChangesToLineChangesMap,
|
|
55
|
-
} from './diff-computer';
|
|
56
46
|
import { IntelligentCompletionsRegistry } from './intelligent-completions.feature.registry';
|
|
57
47
|
import { CodeEditsSourceCollection } from './source/base';
|
|
58
48
|
import { LineChangeCodeEditsSource } from './source/line-change.source';
|
|
59
49
|
import { LintErrorCodeEditsSource } from './source/lint-error.source';
|
|
60
50
|
import { TriggerCodeEditsSource } from './source/trigger.source';
|
|
61
51
|
import { TypingCodeEditsSource } from './source/typing.source';
|
|
52
|
+
import { CodeEditsPreviewer } from './view/code-edits-previewer';
|
|
62
53
|
|
|
63
54
|
import { CodeEditsResultValue, VALID_TIME } from './index';
|
|
64
55
|
|
|
@@ -90,20 +81,18 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
|
|
|
90
81
|
}
|
|
91
82
|
|
|
92
83
|
private codeEditsResult: ISettableObservable<CodeEditsResultValue | undefined>;
|
|
93
|
-
private
|
|
94
|
-
|
|
84
|
+
private multiLineEditsIsVisibleObs: IObservable<boolean>;
|
|
85
|
+
|
|
95
86
|
private codeEditsSourceCollection: CodeEditsSourceCollection;
|
|
96
87
|
private aiNativeContextKey: AINativeContextKey;
|
|
97
|
-
private
|
|
98
|
-
private multiLineEditsIsVisibleObs: IObservable<boolean>;
|
|
88
|
+
private codeEditsPreviewer: CodeEditsPreviewer;
|
|
99
89
|
|
|
100
90
|
public mount(): IDisposable {
|
|
101
91
|
this.handlerAlwaysVisiblePreference();
|
|
102
92
|
|
|
103
93
|
this.codeEditsResult = observableValue<CodeEditsResultValue | undefined>(this, undefined);
|
|
104
|
-
this.multiLineDecorationModel = new MultiLineDecorationModel(this.monacoEditor);
|
|
105
|
-
this.additionsDeletionsDecorationModel = new AdditionsDeletionsDecorationModel(this.monacoEditor);
|
|
106
94
|
this.aiNativeContextKey = this.injector.get(AINativeContextKey, [this.monacoEditor.contextKeyService]);
|
|
95
|
+
this.codeEditsPreviewer = this.injector.get(CodeEditsPreviewer, [this.monacoEditor, this.aiNativeContextKey]);
|
|
107
96
|
this.codeEditsSourceCollection = this.injector.get(CodeEditsSourceCollection, [
|
|
108
97
|
[LintErrorCodeEditsSource, LineChangeCodeEditsSource, TypingCodeEditsSource, TriggerCodeEditsSource],
|
|
109
98
|
this.monacoEditor,
|
|
@@ -214,125 +203,10 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
|
|
|
214
203
|
);
|
|
215
204
|
}
|
|
216
205
|
|
|
217
|
-
private destroyRewriteWidget() {
|
|
218
|
-
if (this.rewriteWidget) {
|
|
219
|
-
this.rewriteWidget.dispose();
|
|
220
|
-
this.rewriteWidget = null;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private applyInlineDecorations(completionModel: CodeEditsResultValue) {
|
|
225
|
-
const { items } = completionModel;
|
|
226
|
-
const { range, insertText } = items[0];
|
|
227
|
-
|
|
228
|
-
// code edits 必须提供 range
|
|
229
|
-
if (!range) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const position = this.monacoEditor.getPosition()!;
|
|
234
|
-
const model = this.monacoEditor.getModel();
|
|
235
|
-
const insertTextString = insertText.toString();
|
|
236
|
-
const originalContent = model?.getValueInRange(range);
|
|
237
|
-
const eol = this.model.getEOL();
|
|
238
|
-
|
|
239
|
-
const changes = computeMultiLineDiffChanges(
|
|
240
|
-
originalContent!,
|
|
241
|
-
insertTextString,
|
|
242
|
-
this.monacoEditor,
|
|
243
|
-
range.startLineNumber,
|
|
244
|
-
eol,
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
if (!changes) {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const { singleLineCharChanges, charChanges, wordChanges, isOnlyAddingToEachWord } = changes;
|
|
252
|
-
|
|
253
|
-
// 限制 changes 数量,超过这个数量直接显示智能重写
|
|
254
|
-
const maxCharChanges = 20;
|
|
255
|
-
const maxWordChanges = 20;
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
range &&
|
|
259
|
-
isOnlyAddingToEachWord &&
|
|
260
|
-
charChanges.length <= maxCharChanges &&
|
|
261
|
-
wordChanges.length <= maxWordChanges
|
|
262
|
-
) {
|
|
263
|
-
const modificationsResult = this.multiLineDecorationModel.applyInlineDecorations(
|
|
264
|
-
this.monacoEditor,
|
|
265
|
-
mergeMultiLineDiffChanges(singleLineCharChanges, eol),
|
|
266
|
-
range.startLineNumber,
|
|
267
|
-
position,
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
this.aiNativeContextKey.multiLineEditsIsVisible.reset();
|
|
271
|
-
this.multiLineDecorationModel.clearDecorations();
|
|
272
|
-
|
|
273
|
-
if (!modificationsResult) {
|
|
274
|
-
this.renderRewriteWidget(wordChanges, model, range, insertTextString);
|
|
275
|
-
} else if (modificationsResult && modificationsResult.inlineMods) {
|
|
276
|
-
this.aiNativeContextKey.multiLineEditsIsVisible.set(true);
|
|
277
|
-
this.multiLineDecorationModel.updateLineModificationDecorations(modificationsResult.inlineMods);
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
this.additionsDeletionsDecorationModel.updateDeletionsDecoration(wordChanges, range, eol);
|
|
281
|
-
this.renderRewriteWidget(wordChanges, model, range, insertTextString);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private async renderRewriteWidget(
|
|
286
|
-
wordChanges: IMultiLineDiffChangeResult[],
|
|
287
|
-
model: ITextModel | null,
|
|
288
|
-
range: IRange,
|
|
289
|
-
insertTextString: string,
|
|
290
|
-
) {
|
|
291
|
-
this.destroyRewriteWidget();
|
|
292
|
-
|
|
293
|
-
const cursorPosition = this.monacoEditor.getPosition();
|
|
294
|
-
if (!cursorPosition) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
this.rewriteWidget = this.injector.get(RewriteWidget, [this.monacoEditor]);
|
|
299
|
-
|
|
300
|
-
const startOffset = this.model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn });
|
|
301
|
-
const endOffset = this.model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn });
|
|
302
|
-
const allText = this.model.getValue();
|
|
303
|
-
// 这里是为了能在 rewrite widget 的 editor 当中完整的复用代码高亮与语法检测的能力
|
|
304
|
-
const newVirtualContent = allText.substring(0, startOffset) + insertTextString + allText.substring(endOffset);
|
|
305
|
-
|
|
306
|
-
const lineChangesMap = wordChangesToLineChangesMap(wordChanges, range, model);
|
|
307
|
-
|
|
308
|
-
await this.rewriteWidget.defered.promise;
|
|
309
|
-
|
|
310
|
-
this.aiNativeContextKey.multiLineEditsIsVisible.set(true);
|
|
311
|
-
|
|
312
|
-
const allLineChanges = Object.values(lineChangesMap).map((lineChanges) => ({
|
|
313
|
-
changes: lineChanges
|
|
314
|
-
.map((change) => change.filter((item) => item.value.trim() !== empty))
|
|
315
|
-
.filter((change) => change.length > 0),
|
|
316
|
-
}));
|
|
317
|
-
|
|
318
|
-
this.rewriteWidget.setInsertText(insertTextString);
|
|
319
|
-
this.rewriteWidget.show({ position: cursorPosition });
|
|
320
|
-
this.rewriteWidget.setEditArea(range);
|
|
321
|
-
|
|
322
|
-
if (allLineChanges.every(({ changes }) => changes.every((change) => change.every(({ removed }) => removed)))) {
|
|
323
|
-
// 处理全是删除的情况
|
|
324
|
-
this.rewriteWidget.renderTextLineThrough(allLineChanges);
|
|
325
|
-
} else {
|
|
326
|
-
this.rewriteWidget.renderVirtualEditor(newVirtualContent, wordChanges);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
206
|
public hide() {
|
|
331
207
|
this.cancelToken();
|
|
332
208
|
this.aiNativeContextKey.multiLineEditsIsVisible.reset();
|
|
333
|
-
this.
|
|
334
|
-
this.additionsDeletionsDecorationModel.clearDeletionsDecorations();
|
|
335
|
-
this.destroyRewriteWidget();
|
|
209
|
+
this.codeEditsPreviewer.hide();
|
|
336
210
|
}
|
|
337
211
|
|
|
338
212
|
private readonly reportData = derived(this, (reader) => {
|
|
@@ -382,6 +256,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
|
|
|
382
256
|
report?.('isValid', false);
|
|
383
257
|
}
|
|
384
258
|
|
|
259
|
+
this.codeEditsPreviewer.discard();
|
|
385
260
|
this.hide();
|
|
386
261
|
return isValid;
|
|
387
262
|
},
|
|
@@ -391,27 +266,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
|
|
|
391
266
|
const report = this.reportData.read(reader);
|
|
392
267
|
report?.('isReceive');
|
|
393
268
|
|
|
394
|
-
this.
|
|
395
|
-
|
|
396
|
-
if (this.rewriteWidget) {
|
|
397
|
-
this.rewriteWidget.accept();
|
|
398
|
-
|
|
399
|
-
const virtualEditor = this.rewriteWidget.getVirtualEditor();
|
|
400
|
-
// 采纳完之后将 virtualEditor 的 decorations 重新映射在 editor 上
|
|
401
|
-
if (virtualEditor) {
|
|
402
|
-
const editArea = this.rewriteWidget.getEditArea();
|
|
403
|
-
const decorations = virtualEditor.getDecorationsInRange(Range.lift(editArea));
|
|
404
|
-
const preAddedDecorations = decorations?.filter(
|
|
405
|
-
(decoration) => decoration.options.description === REWRITE_DECORATION_INLINE_ADD,
|
|
406
|
-
);
|
|
407
|
-
if (preAddedDecorations) {
|
|
408
|
-
this.additionsDeletionsDecorationModel.updateAdditionsDecoration(
|
|
409
|
-
preAddedDecorations.map((decoration) => decoration.range),
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
269
|
+
this.codeEditsPreviewer.accept();
|
|
415
270
|
this.hide();
|
|
416
271
|
});
|
|
417
272
|
|
|
@@ -423,16 +278,6 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
|
|
|
423
278
|
}
|
|
424
279
|
|
|
425
280
|
private registerFeature(monacoEditor: ICodeEditor): void {
|
|
426
|
-
this.featureDisposable.addDispose(
|
|
427
|
-
Event.any<any>(
|
|
428
|
-
monacoEditor.onDidChangeCursorPosition,
|
|
429
|
-
monacoEditor.onDidChangeModelContent,
|
|
430
|
-
monacoEditor.onDidBlurEditorWidget,
|
|
431
|
-
)(() => {
|
|
432
|
-
this.additionsDeletionsDecorationModel.clearAdditionsDecorations();
|
|
433
|
-
}),
|
|
434
|
-
);
|
|
435
|
-
|
|
436
281
|
// 监听当前光标位置的变化,如果超出 range 区域则表示弃用
|
|
437
282
|
this.featureDisposable.addDispose(
|
|
438
283
|
this.monacoEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
|
|
@@ -505,7 +350,7 @@ export class IntelligentCompletionsController extends BaseAIMonacoEditorControll
|
|
|
505
350
|
}
|
|
506
351
|
|
|
507
352
|
try {
|
|
508
|
-
this.
|
|
353
|
+
this.codeEditsPreviewer.render(completionModel);
|
|
509
354
|
} catch (error) {
|
|
510
355
|
this.logger.warn('IntelligentCompletionsController applyInlineDecorations error', error);
|
|
511
356
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Injector } from '@opensumi/di';
|
|
2
|
+
import { Disposable } from '@opensumi/ide-core-common';
|
|
3
|
+
import { ICodeEditor } from '@opensumi/ide-monaco';
|
|
4
|
+
import {
|
|
5
|
+
ObservableCodeEditor,
|
|
6
|
+
observableCodeEditor,
|
|
7
|
+
} from '@opensumi/monaco-editor-core/esm/vs/editor/browser/observableCodeEditor';
|
|
8
|
+
|
|
9
|
+
import { CodeEditsResultValue } from '../index';
|
|
10
|
+
|
|
11
|
+
export abstract class BaseCodeEditsView extends Disposable {
|
|
12
|
+
protected editorObs: ObservableCodeEditor;
|
|
13
|
+
|
|
14
|
+
public modelId: string;
|
|
15
|
+
|
|
16
|
+
constructor(protected readonly monacoEditor: ICodeEditor, protected readonly injector: Injector) {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
this.editorObs = observableCodeEditor(this.monacoEditor);
|
|
20
|
+
this.mount();
|
|
21
|
+
|
|
22
|
+
this.addDispose({ dispose: () => this.hide() });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected mount(): void {}
|
|
26
|
+
|
|
27
|
+
abstract render(completionModel: CodeEditsResultValue): void;
|
|
28
|
+
abstract hide(): void;
|
|
29
|
+
abstract accept(): void;
|
|
30
|
+
abstract discard(): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Autowired, INJECTOR_TOKEN, Injectable, Injector, Optional } from '@opensumi/di';
|
|
2
|
+
import { PreferenceService } from '@opensumi/ide-core-browser';
|
|
3
|
+
import { AINativeSettingSectionsId, Disposable } from '@opensumi/ide-core-common';
|
|
4
|
+
import { ICodeEditor } from '@opensumi/ide-monaco';
|
|
5
|
+
import { IObservable, IReader, autorun, observableFromEvent } from '@opensumi/ide-monaco/lib/common/observable';
|
|
6
|
+
|
|
7
|
+
import { AINativeContextKey } from '../../../ai-core.contextkeys';
|
|
8
|
+
import { CodeEditsRenderType, CodeEditsResultValue } from '../index';
|
|
9
|
+
|
|
10
|
+
import { BaseCodeEditsView } from './base';
|
|
11
|
+
import { DefaultCodeEditsView } from './default';
|
|
12
|
+
import { LegacyCodeEditsView } from './legacy';
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class CodeEditsPreviewer extends Disposable {
|
|
16
|
+
@Autowired(INJECTOR_TOKEN)
|
|
17
|
+
protected readonly injector: Injector;
|
|
18
|
+
|
|
19
|
+
@Autowired(PreferenceService)
|
|
20
|
+
private readonly preferenceService: PreferenceService;
|
|
21
|
+
|
|
22
|
+
private readonly renderTypeObs: IObservable<CodeEditsRenderType>;
|
|
23
|
+
private view: BaseCodeEditsView | undefined;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
@Optional() protected readonly monacoEditor: ICodeEditor,
|
|
27
|
+
@Optional() protected readonly aiNativeContextKey: AINativeContextKey,
|
|
28
|
+
) {
|
|
29
|
+
super();
|
|
30
|
+
|
|
31
|
+
this.renderTypeObs = observableFromEvent(this, this.preferenceService.onPreferenceChanged, () =>
|
|
32
|
+
this.preferenceService.getValid(AINativeSettingSectionsId.CodeEditsRenderType, CodeEditsRenderType.Default),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
this.addDispose(
|
|
36
|
+
autorun((reader: IReader) => {
|
|
37
|
+
const renderType = this.renderTypeObs.read(reader);
|
|
38
|
+
if (this.view) {
|
|
39
|
+
this.view.dispose();
|
|
40
|
+
this.view = undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (renderType === CodeEditsRenderType.Default) {
|
|
44
|
+
this.view = new DefaultCodeEditsView(this.monacoEditor, this.injector);
|
|
45
|
+
} else {
|
|
46
|
+
this.view = new LegacyCodeEditsView(this.monacoEditor, this.injector);
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public render(completionModel: CodeEditsResultValue) {
|
|
53
|
+
this.view?.render(completionModel);
|
|
54
|
+
this.aiNativeContextKey.multiLineEditsIsVisible.set(true);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public hide() {
|
|
58
|
+
this.view?.hide();
|
|
59
|
+
this.aiNativeContextKey.multiLineEditsIsVisible.set(false);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public accept() {
|
|
63
|
+
this.view?.accept();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public discard() {
|
|
67
|
+
this.view?.discard();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InlineCompletionContext,
|
|
3
|
+
InlineCompletionTriggerKind,
|
|
4
|
+
InlineCompletionsProvider,
|
|
5
|
+
Position,
|
|
6
|
+
Range,
|
|
7
|
+
} from '@opensumi/ide-monaco';
|
|
8
|
+
import {
|
|
9
|
+
IObservable,
|
|
10
|
+
asyncTransaction,
|
|
11
|
+
constObservable,
|
|
12
|
+
derived,
|
|
13
|
+
transaction,
|
|
14
|
+
} from '@opensumi/ide-monaco/lib/common/observable';
|
|
15
|
+
import { equalsIfDefined, itemEquals } from '@opensumi/monaco-editor-core/esm/vs/base/common/equals';
|
|
16
|
+
import { LanguageFeatureRegistry } from '@opensumi/monaco-editor-core/esm/vs/editor/common/languageFeatureRegistry';
|
|
17
|
+
import { InlineCompletionsController } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController';
|
|
18
|
+
import { InlineCompletionsModel } from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel';
|
|
19
|
+
import {
|
|
20
|
+
InlineCompletionsSource,
|
|
21
|
+
UpToDateInlineCompletions,
|
|
22
|
+
} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource';
|
|
23
|
+
import {
|
|
24
|
+
InlineCompletionProviderResult,
|
|
25
|
+
provideInlineCompletions,
|
|
26
|
+
} from '@opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions';
|
|
27
|
+
|
|
28
|
+
import { CodeEditsResultValue, ICodeEdit, ICodeEditsResult } from '../index';
|
|
29
|
+
|
|
30
|
+
import { BaseCodeEditsView } from './base';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* copy from @opensumi/monaco-editor-core/esm/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions
|
|
34
|
+
*/
|
|
35
|
+
class UpdateRequest {
|
|
36
|
+
constructor(
|
|
37
|
+
public readonly position: Position,
|
|
38
|
+
public readonly context: InlineCompletionContext,
|
|
39
|
+
public readonly versionId: number,
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
public satisfies(other: UpdateRequest): boolean {
|
|
43
|
+
return (
|
|
44
|
+
this.position.equals(other.position) &&
|
|
45
|
+
equalsIfDefined(this.context.selectedSuggestionInfo, other.context.selectedSuggestionInfo, itemEquals()) &&
|
|
46
|
+
(other.context.triggerKind === InlineCompletionTriggerKind.Automatic ||
|
|
47
|
+
this.context.triggerKind === InlineCompletionTriggerKind.Explicit) &&
|
|
48
|
+
this.versionId === other.versionId
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public get isExplicitRequest() {
|
|
53
|
+
return this.context.triggerKind === InlineCompletionTriggerKind.Explicit;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class DefaultCodeEditsView extends BaseCodeEditsView {
|
|
58
|
+
private monacoCompletionsModel: IObservable<InlineCompletionsModel | undefined> = derived(this, (reader) => {
|
|
59
|
+
const inlineCompletionsController = InlineCompletionsController.get(this.monacoEditor);
|
|
60
|
+
if (!inlineCompletionsController) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return inlineCompletionsController.model.read(reader);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
private monacoCompletionsSource: IObservable<InlineCompletionsSource | undefined> = derived(this, (reader) => {
|
|
68
|
+
const model = this.monacoCompletionsModel.read(reader);
|
|
69
|
+
if (!model) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const source = model['_source'] as InlineCompletionsSource;
|
|
74
|
+
return source;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
public render(completionModel: CodeEditsResultValue): void {
|
|
78
|
+
const source = this.monacoCompletionsSource.get();
|
|
79
|
+
if (!source) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
asyncTransaction(async (tx) => {
|
|
84
|
+
const position = this.editorObs.positions.get()?.[0];
|
|
85
|
+
if (!position) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const model = this.editorObs.model.get();
|
|
90
|
+
if (!model) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const versionId = this.editorObs.versionId.get();
|
|
95
|
+
const context: InlineCompletionContext = {
|
|
96
|
+
triggerKind: InlineCompletionTriggerKind.Automatic,
|
|
97
|
+
selectedSuggestionInfo: undefined,
|
|
98
|
+
includeInlineCompletions: false,
|
|
99
|
+
includeInlineEdits: true,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const request = new UpdateRequest(position, context, versionId!);
|
|
103
|
+
const inlineEdits: InlineCompletionProviderResult = await provideInlineCompletions(
|
|
104
|
+
{
|
|
105
|
+
all: () => [
|
|
106
|
+
{
|
|
107
|
+
provideInlineCompletions: () => {},
|
|
108
|
+
provideInlineEditsForRange(model, range, context, token) {
|
|
109
|
+
return completionModel;
|
|
110
|
+
},
|
|
111
|
+
freeInlineCompletions: () => [],
|
|
112
|
+
} as InlineCompletionsProvider<ICodeEditsResult<ICodeEdit>>,
|
|
113
|
+
],
|
|
114
|
+
} as unknown as LanguageFeatureRegistry<InlineCompletionsProvider>,
|
|
115
|
+
Range.lift(completionModel.range),
|
|
116
|
+
model,
|
|
117
|
+
context,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const completions = new UpToDateInlineCompletions(
|
|
121
|
+
inlineEdits,
|
|
122
|
+
request,
|
|
123
|
+
model,
|
|
124
|
+
this.editorObs.versionId,
|
|
125
|
+
constObservable(1000),
|
|
126
|
+
);
|
|
127
|
+
source.inlineCompletions.set(completions, tx);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public accept(): void {
|
|
132
|
+
const model = this.monacoCompletionsModel.get();
|
|
133
|
+
model?.accept();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public discard(): void {
|
|
137
|
+
const model = this.monacoCompletionsModel.get();
|
|
138
|
+
transaction((tx) => {
|
|
139
|
+
model?.stop('explicitCancel', tx);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public hide(): void {
|
|
144
|
+
this.monacoCompletionsSource.get()?.inlineCompletions?.set(undefined, undefined);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { IRange, ITextModel, Range } from '@opensumi/ide-monaco';
|
|
2
|
+
import { Event } from '@opensumi/ide-utils';
|
|
3
|
+
import { empty } from '@opensumi/ide-utils/lib/strings';
|
|
4
|
+
|
|
5
|
+
import { REWRITE_DECORATION_INLINE_ADD, RewriteWidget } from '../../../widget/rewrite/rewrite-widget';
|
|
6
|
+
import { AdditionsDeletionsDecorationModel } from '../decoration/additions-deletions.decoration';
|
|
7
|
+
import { MultiLineDecorationModel } from '../decoration/multi-line.decoration';
|
|
8
|
+
import {
|
|
9
|
+
IMultiLineDiffChangeResult,
|
|
10
|
+
computeMultiLineDiffChanges,
|
|
11
|
+
mergeMultiLineDiffChanges,
|
|
12
|
+
wordChangesToLineChangesMap,
|
|
13
|
+
} from '../diff-computer';
|
|
14
|
+
import { CodeEditsResultValue } from '../index';
|
|
15
|
+
|
|
16
|
+
import { BaseCodeEditsView } from './base';
|
|
17
|
+
|
|
18
|
+
export class LegacyCodeEditsView extends BaseCodeEditsView {
|
|
19
|
+
private get model() {
|
|
20
|
+
return this.monacoEditor.getModel()!;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private multiLineDecorationModel: MultiLineDecorationModel = new MultiLineDecorationModel(this.monacoEditor);
|
|
24
|
+
private additionsDeletionsDecorationModel: AdditionsDeletionsDecorationModel = new AdditionsDeletionsDecorationModel(
|
|
25
|
+
this.monacoEditor,
|
|
26
|
+
);
|
|
27
|
+
private rewriteWidget: RewriteWidget | null;
|
|
28
|
+
|
|
29
|
+
protected mount(): void {
|
|
30
|
+
this.addDispose(
|
|
31
|
+
Event.any<any>(
|
|
32
|
+
this.monacoEditor.onDidChangeCursorPosition,
|
|
33
|
+
this.monacoEditor.onDidChangeModelContent,
|
|
34
|
+
this.monacoEditor.onDidBlurEditorWidget,
|
|
35
|
+
)(() => {
|
|
36
|
+
this.additionsDeletionsDecorationModel.clearAdditionsDecorations();
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private destroyRewriteWidget() {
|
|
42
|
+
if (this.rewriteWidget) {
|
|
43
|
+
this.rewriteWidget.dispose();
|
|
44
|
+
this.rewriteWidget = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async renderRewriteWidget(
|
|
49
|
+
wordChanges: IMultiLineDiffChangeResult[],
|
|
50
|
+
model: ITextModel | null,
|
|
51
|
+
range: IRange,
|
|
52
|
+
insertTextString: string,
|
|
53
|
+
) {
|
|
54
|
+
this.destroyRewriteWidget();
|
|
55
|
+
|
|
56
|
+
const cursorPosition = this.monacoEditor.getPosition();
|
|
57
|
+
if (!cursorPosition) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.rewriteWidget = this.injector.get(RewriteWidget, [this.monacoEditor]);
|
|
62
|
+
|
|
63
|
+
const startOffset = this.model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn });
|
|
64
|
+
const endOffset = this.model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn });
|
|
65
|
+
const allText = this.model.getValue();
|
|
66
|
+
// 这里是为了能在 rewrite widget 的 editor 当中完整的复用代码高亮与语法检测的能力
|
|
67
|
+
const newVirtualContent = allText.substring(0, startOffset) + insertTextString + allText.substring(endOffset);
|
|
68
|
+
|
|
69
|
+
const lineChangesMap = wordChangesToLineChangesMap(wordChanges, range, model);
|
|
70
|
+
|
|
71
|
+
await this.rewriteWidget.defered.promise;
|
|
72
|
+
|
|
73
|
+
const allLineChanges = Object.values(lineChangesMap).map((lineChanges) => ({
|
|
74
|
+
changes: lineChanges
|
|
75
|
+
.map((change) => change.filter((item) => item.value.trim() !== empty))
|
|
76
|
+
.filter((change) => change.length > 0),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
this.rewriteWidget.setInsertText(insertTextString);
|
|
80
|
+
this.rewriteWidget.show({ position: cursorPosition });
|
|
81
|
+
this.rewriteWidget.setEditArea(range);
|
|
82
|
+
|
|
83
|
+
if (allLineChanges.every(({ changes }) => changes.every((change) => change.every(({ removed }) => removed)))) {
|
|
84
|
+
// 处理全是删除的情况
|
|
85
|
+
this.rewriteWidget.renderTextLineThrough(allLineChanges);
|
|
86
|
+
} else {
|
|
87
|
+
this.rewriteWidget.renderVirtualEditor(newVirtualContent, wordChanges);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public render(completionModel: CodeEditsResultValue): void {
|
|
92
|
+
const { items } = completionModel;
|
|
93
|
+
const { range, insertText } = items[0];
|
|
94
|
+
|
|
95
|
+
// code edits 必须提供 range
|
|
96
|
+
if (!range) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const position = this.monacoEditor.getPosition()!;
|
|
101
|
+
const model = this.monacoEditor.getModel();
|
|
102
|
+
const insertTextString = insertText.toString();
|
|
103
|
+
const originalContent = model?.getValueInRange(range);
|
|
104
|
+
const eol = this.model.getEOL();
|
|
105
|
+
|
|
106
|
+
const changes = computeMultiLineDiffChanges(
|
|
107
|
+
originalContent!,
|
|
108
|
+
insertTextString,
|
|
109
|
+
this.monacoEditor,
|
|
110
|
+
range.startLineNumber,
|
|
111
|
+
eol,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (!changes) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { singleLineCharChanges, charChanges, wordChanges, isOnlyAddingToEachWord } = changes;
|
|
119
|
+
|
|
120
|
+
// 限制 changes 数量,超过这个数量直接显示智能重写
|
|
121
|
+
const maxCharChanges = 20;
|
|
122
|
+
const maxWordChanges = 20;
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
range &&
|
|
126
|
+
isOnlyAddingToEachWord &&
|
|
127
|
+
charChanges.length <= maxCharChanges &&
|
|
128
|
+
wordChanges.length <= maxWordChanges
|
|
129
|
+
) {
|
|
130
|
+
const modificationsResult = this.multiLineDecorationModel.applyInlineDecorations(
|
|
131
|
+
this.monacoEditor,
|
|
132
|
+
mergeMultiLineDiffChanges(singleLineCharChanges, eol),
|
|
133
|
+
range.startLineNumber,
|
|
134
|
+
position,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
this.multiLineDecorationModel.clearDecorations();
|
|
138
|
+
|
|
139
|
+
if (!modificationsResult) {
|
|
140
|
+
this.renderRewriteWidget(wordChanges, model, range, insertTextString);
|
|
141
|
+
} else if (modificationsResult && modificationsResult.inlineMods) {
|
|
142
|
+
this.multiLineDecorationModel.updateLineModificationDecorations(modificationsResult.inlineMods);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
this.additionsDeletionsDecorationModel.updateDeletionsDecoration(wordChanges, range, eol);
|
|
146
|
+
this.renderRewriteWidget(wordChanges, model, range, insertTextString);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public accept(): void {
|
|
151
|
+
this.multiLineDecorationModel.accept();
|
|
152
|
+
|
|
153
|
+
if (this.rewriteWidget) {
|
|
154
|
+
this.rewriteWidget.accept();
|
|
155
|
+
|
|
156
|
+
const virtualEditor = this.rewriteWidget.getVirtualEditor();
|
|
157
|
+
// 采纳完之后将 virtualEditor 的 decorations 重新映射在 editor 上
|
|
158
|
+
if (virtualEditor) {
|
|
159
|
+
const editArea = this.rewriteWidget.getEditArea();
|
|
160
|
+
const decorations = virtualEditor.getDecorationsInRange(Range.lift(editArea));
|
|
161
|
+
const preAddedDecorations = decorations?.filter(
|
|
162
|
+
(decoration) => decoration.options.description === REWRITE_DECORATION_INLINE_ADD,
|
|
163
|
+
);
|
|
164
|
+
if (preAddedDecorations) {
|
|
165
|
+
this.additionsDeletionsDecorationModel.updateAdditionsDecoration(
|
|
166
|
+
preAddedDecorations.map((decoration) => decoration.range),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public discard(): void {
|
|
174
|
+
this.hide();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public hide(): void {
|
|
178
|
+
this.multiLineDecorationModel.clearDecorations();
|
|
179
|
+
this.additionsDeletionsDecorationModel.clearDeletionsDecorations();
|
|
180
|
+
this.destroyRewriteWidget();
|
|
181
|
+
}
|
|
182
|
+
}
|