import debounce from 'lodash/debounce'; import { Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di'; import { AINativeConfigService, ClientAppContribution, MERGE_CONFLICT_COMMANDS } from '@opensumi/ide-core-browser'; import { MergeConflictReportService } from '@opensumi/ide-core-browser/lib/ai-native/conflict-report.service'; import { CancelResponse, CancellationTokenSource, ChatResponse, CommandContribution, CommandRegistry, Disposable, Domain, Emitter, ErrorResponse, Event, ExtensionActivatedEvent, IConflictContentMetadata, IEventBus, IInternalResolveConflictRegistry, ILogger, IRange, MaybePromise, MergeConflictEditorMode, MergeConflictRT, ReplyResponse, ResolveConflictRegistryToken, Schemes, Uri, localize, } from '@opensumi/ide-core-common'; import { GitCommands } from '@opensumi/ide-core-common/lib/commands/git'; import { IEditor, WorkbenchEditorService } from '@opensumi/ide-editor/lib/browser'; import { CommitType, DocumentMergeConflict, MergeConflictParser, } from '@opensumi/ide-editor/lib/browser/merge-conflict'; import * as monaco from '@opensumi/ide-monaco'; import { ITextModel } from '@opensumi/ide-monaco'; import { LineRange } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/model/line-range'; import { ACCEPT_CURRENT_ACTIONS, AI_RESOLVE_REGENERATE_ACTIONS, IAcceptConflictActionsEvent, IConflictActionsEvent, IGNORE_ACTIONS, REVOKE_ACTIONS, } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/types'; import styles from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/view/merge-editor.module.less'; import { IWidgetFactory, WidgetFactory } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/widget/facotry'; import { StopWidget } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/widget/stop-widget'; import { IMergeEditorShape } from '@opensumi/ide-monaco/lib/browser/contrib/merge-editor/widget/types'; import { monacoApi } from '@opensumi/ide-monaco/lib/browser/monaco-api'; import { ICodeEditor, IModelDeltaDecoration } from '@opensumi/ide-monaco/lib/browser/monaco-api/editor'; import { languageFeaturesService } from '@opensumi/ide-monaco/lib/browser/monaco-api/languages'; import { DiffResolveResultWidget } from './override-resolve-result-widget'; const MERGE_CONFLICT_CODELENS_STYLE = 'merge-conflict-codelens-style'; // codelens 无法定义样式 此处通过 css hack const cssStyle = ` .monaco-editor .codelens-decoration { & > a[title*=AI] { background-image: radial-gradient(circle at -21% -22%, #00f6ff, #9c03ff); color: transparent; background-clip: text; } & > a[title*=AI]:hover { background-image: radial-gradient(circle at -21% -22%, #00f6ff, #9c03ff); color: transparent !important; background-clip: text; } .codicon-ai-magic::before { font-family: 'kaitian-icon'; content: "\\e60e"; } .codicon-ai-magic { font: normal normal normal 16px/1 'kaitian-icon'; display: inline-block; text-decoration: none; text-rendering: auto; text-align: center; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; } .codicon-modifier-spin { color: #00f6ff !important; } } `; function loadStyleString(load: boolean) { let mergeConflictStyleNode = document.getElementById(MERGE_CONFLICT_CODELENS_STYLE); if (!load) { mergeConflictStyleNode?.remove(); return; } if (!mergeConflictStyleNode && load) { mergeConflictStyleNode = document.createElement('style'); mergeConflictStyleNode.id = MERGE_CONFLICT_CODELENS_STYLE; mergeConflictStyleNode.appendChild(document.createTextNode(cssStyle)); document.getElementsByTagName('head')[0].appendChild(mergeConflictStyleNode); } } interface ICacheResolvedConflicts { conflict: DocumentMergeConflict; id: string; conflictText: string; /** * 解决冲突后的文本 */ text: string; metadata: IConflictContentMetadata; isAccept?: boolean; isClosed?: boolean; } interface IRequestCancel { id: string; type: 'cancel'; } interface IReportData extends Partial { relationId?: string; } @Domain(CommandContribution, ClientAppContribution) export class MergeConflictContribution extends Disposable implements CommandContribution, ClientAppContribution, IMergeEditorShape { @Autowired(INJECTOR_TOKEN) private readonly injector: Injector; @Autowired(ILogger) private readonly logger: ILogger; @Autowired(WorkbenchEditorService) private readonly editorService: WorkbenchEditorService; @Autowired(IEventBus) private readonly eventBus: IEventBus; @Autowired(AINativeConfigService) private readonly aiNativeConfigService: AINativeConfigService; @Autowired() private readonly conflictParser: MergeConflictParser; @Autowired(MergeConflictReportService) private readonly mergeConflictReportService: MergeConflictReportService; @Autowired(ResolveConflictRegistryToken) private readonly resolveConflictRegistry: IInternalResolveConflictRegistry; private resolveResultWidgetManager: IWidgetFactory; private stopWidgetManager: IWidgetFactory; private cancelIndicatorMap: Map = new Map(); private cacheResolvedConflicts: Map> = new Map(); private readonly _onRequestCancel = new Emitter(); public readonly onRequestCancel: Event = this._onRequestCancel.event; private readonly _onRequestsCancel = new Emitter(); public readonly onRequestsCancel: Event = this._onRequestsCancel.event; // for widget editor: ICodeEditor; // for codelens loading private loadingRange: Set = new Set(); // for report private currentReportMap = new Map(); // for decoration dispose private decorationId2Dispose = new Map void>(); private decorationId2Range = new Map(); dispose() { super.dispose(); this.cancelRequestToken(); this.mergeConflictReportService.dispose(); this.conflictParser.dispose(); } onDidStart(): MaybePromise { // 提供了 provider 才允许注册 const handler = this.resolveConflictRegistry.getTraditionalHandler(); if (this.aiNativeConfigService.capabilities.supportsConflictResolve && handler) { this.disposables.push( this.eventBus.on(ExtensionActivatedEvent, (e) => { // 当插件注册完毕 再初始化 codelens 使其位置正确 if ( e.payload.topic === 'onExtensionActivated' && (e.payload.data?.id as string).endsWith('.merge-conflict') ) { this.registerCodeLensProvider(); loadStyleString(true); } }), // TODO 优化使用 registerEditorFeature this.editorService.onActiveResourceChange(() => { this.contribute(this.editorService.currentEditor!); }), ); } else { loadStyleString(false); } } private initListenEvent(): void { if (this.aiNativeConfigService.capabilities.supportsConflictResolve) { this.disposables.push( this.editor.onDidChangeModelContent(({ changes, eol }) => { const deltaEdits: Array<{ startLineNumber: number; endLineNumber: number; offset: number }> = []; changes.forEach((change) => { const { text, range } = change; const textLineCount = (text.match(new RegExp(eol, 'ig')) ?? []).length; const { startLineNumber, endLineNumber } = range; /** * startLineNumber 与 endLineNumber 的差值表示选区选了多少行 * textLineCount 则表示文本出现的换行符数量 * 两者相加就得出此次文本变更最终新增或减少了多少行 */ const offset = startLineNumber - endLineNumber + textLineCount; if (offset === 0) { return; } deltaEdits.push({ startLineNumber, endLineNumber, offset, }); }); deltaEdits.forEach((edits) => { const map: Map = new Map(); const { startLineNumber, endLineNumber, offset } = edits; for (const [id, value] of this.getCacheResolvedConflicts().entries()) { const { range: newRange } = value.conflict; const { startLineNumber: newStartLineNumber, endLineNumber: newEndLineNumber } = newRange; // 在冲突位置下方改动 不处理 if (newEndLineNumber < startLineNumber) { map.set(id, value); continue; } // 在冲突位置上方改动 偏移 if (startLineNumber < newStartLineNumber && endLineNumber < newEndLineNumber) { value.conflict.range = newRange.delta(offset); value.conflict.current.content = value.conflict.current.content.delta(offset); value.conflict.incoming.content = value.conflict.incoming.content.delta(offset); value.conflict.splitter = value.conflict.splitter.delta(offset); map.set(id, value); continue; } // 包含冲突位置 删除 if (startLineNumber <= newStartLineNumber && endLineNumber >= newEndLineNumber) { const newRangeOffset = newEndLineNumber - newStartLineNumber; if (offset <= newRangeOffset) { continue; } } // 内部改动 if (startLineNumber >= newStartLineNumber && endLineNumber <= newEndLineNumber) { // 内部改动删除 // TODO // const newRange = new monaco.Range(newStartLineNumber, 1, newEndLineNumber + offset, 1); // map.set(id, { // ...value, // newRange, // }); } } this.resetCacheResolvedConflicts(map); for (const [id, value] of this.decorationId2Range.entries()) { const { startLineNumber: decorationStartLineNumber, endLineNumber: decorationEndLineNumber, endColumn: decorationEndColumn, } = value; // 在位置下方改动 不处理 if (decorationEndLineNumber < startLineNumber) { continue; } // 在位置上方改动 偏移 if (startLineNumber < decorationStartLineNumber && endLineNumber < decorationEndLineNumber) { const newRange = new monaco.Range( decorationStartLineNumber + offset, 1, decorationEndLineNumber + offset, decorationEndColumn, ); this.decorationId2Range.set(id, newRange); continue; } // 包含位置 删除 if (startLineNumber <= decorationStartLineNumber && endLineNumber >= decorationEndLineNumber) { const newRangeOffset = decorationEndLineNumber - decorationStartLineNumber; if (offset <= newRangeOffset) { this.cleanDecoration(id); continue; } } // 内部改动删除 if (startLineNumber >= decorationStartLineNumber && endLineNumber <= decorationEndLineNumber) { this.cleanDecoration(id); } } }); this.updateAllWidgets(); }), this.editor.onDidChangeModel(() => { this.updateAllWidgets(); }), ); } } private init() { this.initListenEvent(); } /* report */ get reportData() { const uri = this.getUri(); const reportData = this.currentReportMap.get(uri); if (reportData) { return reportData; } else { const reportInfo: Partial = { editorMode: MergeConflictEditorMode.traditional, conflictPointNum: 0, useAiConflictPointNum: 0, receiveNum: 0, clickNum: 0, clickAllNum: 0, aiOutputNum: 0, cancelNum: 0, }; this.currentReportMap.set(uri, reportInfo); this.mergeConflictReportService.report(uri, reportInfo); return reportInfo; } } set reportData(data: Partial) { const uri = this.getUri(); const reportData = this.reportData; this.currentReportMap.set(uri, { ...reportData, ...data, }); } private reportConflictData() { if (!this.editor) { return; } const uri = this.getUri(); const reportData = this.currentReportMap.get(uri); if (reportData) { this.mergeConflictReportService.report(uri, this.reportData); } } private updateReportData() { const allConflictCache = this.conflictParser.getAllConflictsByUri(this.getUri()); let conflictPointNum = 0; let useAIConflictPointNum = 0; let receiveNum = 0; conflictPointNum = allConflictCache?.length || 0; allConflictCache?.forEach((cacheConflict) => { if (cacheConflict.isResolved) { useAIConflictPointNum += 1; } }); // 内部修改 删除态无法统计 for (const [uri, cacheResolvedConflictsMap] of this.cacheResolvedConflicts.entries()) { for (const [, cacheResolvedConflicts] of cacheResolvedConflictsMap.entries()) { // 统计当前文件 if (uri === this.getModel().uri.toString()) { // 计算当前文件采纳数量 const aiResult = cacheResolvedConflicts.text; const currentResult = this.getModel()?.getValueInRange(cacheResolvedConflicts.conflict.range); if (aiResult.trim() === currentResult.trim()) { cacheResolvedConflicts.isAccept = true; receiveNum += 1; } else { cacheResolvedConflicts.isAccept = false; } } } } this.reportData = { conflictPointNum, useAiConflictPointNum: useAIConflictPointNum, receiveNum, }; } /* report */ getAllDiffRanges() { const rangeLines: IRange[] = []; for (const [, value] of this.getCacheResolvedConflicts().entries()) { rangeLines.push(value.conflict.range); } return rangeLines; } updateAllWidgets() { this.hideResolveResultWidget(); for (const [id, value] of this.getCacheResolvedConflicts().entries()) { if (!value.isClosed) { const lineRange = this.toLineRange(value.conflict.range, id); this.resolveResultWidgetManager.addWidget(lineRange, value.conflict.current.content, value.text); } } this.updateCodeLensDebounce(); } private getModel(): ITextModel { return this.editor.getModel()!; } private getUri(): string { return this.getModel()?.uri.toString(); } private getCacheResolvedConflicts(currentUri?: string) { currentUri = currentUri || this.getModel()?.uri.toString(); if (!currentUri) { return new Map(); } const cache = this.cacheResolvedConflicts.get(currentUri); if (cache) { return cache; } else { this.cacheResolvedConflicts.set(currentUri, new Map()); return this.cacheResolvedConflicts.get(currentUri)!; } } private deleteCacheResolvedConflicts(id: string, currentUri?: string) { if (!currentUri) { currentUri = this.getModel().uri.toString(); } const cache = this.cacheResolvedConflicts.get(currentUri); if (cache) { cache.delete(id); } } private setCacheResolvedConflict(id: string, cacheConflict: ICacheResolvedConflicts, currentUri?: string) { if (!currentUri) { currentUri = this.getModel().uri.toString(); } const cache = this.getCacheResolvedConflicts(currentUri); cache.set(id, cacheConflict); } private resetCacheResolvedConflicts(cacheConflicts: Map, currentUri?: string) { if (!currentUri) { currentUri = this.getModel().uri.toString(); } if (this.cacheResolvedConflicts.has(currentUri)) { this.cacheResolvedConflicts.set(currentUri, cacheConflicts); } } private toLineRange(range: IRange, id?: string) { const lineRange = new LineRange(range.startLineNumber, range.endLineNumber + 1); if (id) { lineRange.setId(id); } return lineRange; } public renderSkeletonDecoration(range: IRange, classNames: string[]) { const model = this.getModel(); const renderSkeletonDecoration = (className: string): IModelDeltaDecoration => ({ range, options: { isWholeLine: true, description: 'skeleton', className, }, }); const preDecorationsIds = model?.deltaDecorations( [], classNames.map((cls) => renderSkeletonDecoration(cls)), ) || []; return () => { model?.deltaDecorations(preDecorationsIds, []); }; } registerCommands(commands: CommandRegistry): void { this.disposables.push( commands.registerCommand(MERGE_CONFLICT_COMMANDS.AI_ACCEPT, { execute: async (type: CommitType, conflict: DocumentMergeConflict) => { this.conflictAIAccept(conflict); }, }), commands.registerCommand(MERGE_CONFLICT_COMMANDS.ALL_RESET, { execute: async (uri: Uri) => { this.cleanAllCache(); const content = this.conflictParser.getConflictText(uri.toString()); this.cancelRequestToken(); if (content) { if (this.editorService.currentEditor?.currentUri?.toString() === uri.toString()) { const editor = this.editorService.currentEditor?.monacoEditor; if (editor) { const model = editor.getModel(); model?.setValue(content); this.conflictParser.deleteConflictText(uri.toString()); } } } }, }), commands.registerCommand(MERGE_CONFLICT_COMMANDS.AI_ALL_ACCEPT, { execute: async () => { const document = this.getModel(); if (!document) { return Promise.resolve(); } const conflicts = this.conflictParser.scanDocument(document as monaco.editor.ITextModel); if (!conflicts?.length) { return Promise.resolve(); } this.reportData = { clickAllNum: this.reportData.clickAllNum! + 1, }; // 一个个解决 await this.acceptAllConflict(); }, }), commands.registerCommand(MERGE_CONFLICT_COMMANDS.AI_ALL_ACCEPT_STOP, { execute: async () => { this.cancelRequestToken(); }, }), commands.afterExecuteCommand(GitCommands.Stage, (args) => { this.reportConflictData(); return args; }), commands.afterExecuteCommand(GitCommands.StageAllMerge, (args) => { this.reportConflictData(); return args; }), ); } private contribute(editor: IEditor) { if (!editor) { return this; } const { monacoEditor, currentUri } = editor; if (currentUri && currentUri.codeUri.scheme !== Schemes.file) { return this; } const currentEditor = editor; if (currentEditor && monacoEditor && !this.editor) { this.editor = monacoEditor; this.resolveResultWidgetManager = new WidgetFactory(DiffResolveResultWidget, this, this.injector); this.stopWidgetManager = new WidgetFactory(StopWidget, this, this.injector); this.init(); } } async provideCodeLens( document: monaco.editor.ITextModel, _token: monaco.CancellationToken, ): Promise { const conflicts = this.conflictParser.scanDocument(document); if (!conflicts.length) { return null; } const items: monaco.languages.CodeLens[] = []; conflicts.forEach((conflict) => { const aiAcceptCommand = { id: MERGE_CONFLICT_COMMANDS.AI_ACCEPT.id, title: `$(ai-magic) ${localize('mergeEditor.conflict.ai.resolve.all')}`, arguments: ['know-conflict', conflict], tooltip: localize('mergeEditor.conflict.ai.resolve.all'), }; // loading 效果 this.loadingRange.forEach((range) => { if (conflict.range.equalsRange(range)) { aiAcceptCommand.title = `$(loading~spin) ${localize('mergeEditor.conflict.ai.resolve.all')}`; } }); items.push({ range: conflict.range, command: aiAcceptCommand, id: MERGE_CONFLICT_COMMANDS.AI_ACCEPT.id, }); }); return Promise.resolve(items); } private async conflictAIAccept(conflict: DocumentMergeConflict, previousId?: string) { if (!this.editorService.currentEditor?.monacoEditor) { return; } const model = this.getModel(); const lineRange = this.toLineRange(conflict.range, previousId); const conflictMetadata: IConflictContentMetadata = { base: '', current: model.getValueInRange(conflict.current.content), incoming: model.getValueInRange(conflict.incoming.content), currentName: conflict.current.name, incomingName: conflict.incoming.name, }; const removeSkeletonDecoration = this.renderSkeletonDecoration(conflict.range, [ styles.skeleton_decoration, styles.skeleton_decoration_background_black, ]); this.stopWidgetManager.addWidget(lineRange); const conflictText = model.getValueInRange(conflict.range) ?? ''; this.logger.log('start ai resolve conflict:', conflictMetadata); let resolveConflictResult: ChatResponse | undefined; try { this.loadingRange.add(conflict.range); this.reportData = { clickNum: this.reportData.clickNum! + 1, }; resolveConflictResult = await this.requestAIResolveConflict(conflictMetadata, lineRange, Boolean(previousId)); } catch (error) { throw new Error(`AI resolve conflict error: ${error.toString()}`); } finally { removeSkeletonDecoration(); this.stopWidgetManager.hideWidget(lineRange.id); this.loadingRange.delete(conflict.range); } if (ReplyResponse.is(resolveConflictResult)) { this.reportData = { aiOutputNum: this.reportData.aiOutputNum! + 1, }; const { text } = this.resolveEndLineEOL(resolveConflictResult!.message!); const decorationDispose = this.renderSkeletonDecoration(conflict.range, [styles.skeleton_decoration_complete]); this.decorationId2Dispose.set(lineRange.id, decorationDispose); this.decorationId2Range.set(lineRange.id, conflict.range); const widgetLineRange = this.toLineRange(conflict.range, lineRange.id); this.resolveResultWidgetManager.addWidget(widgetLineRange, conflict.current.content, text); this.setCacheResolvedConflict(lineRange.id, { conflict, id: lineRange.id, metadata: conflictMetadata, // 保留原始冲突文本 conflictText, text, }); if (!previousId) { // 记录处理数量 非重新生成 conflict 存在 const uri = this.getModel().uri.toString(); const cacheConflictRanges = this.conflictParser.getAllConflictsByUri(uri); if (cacheConflictRanges) { const cacheConflict = cacheConflictRanges.find((cacheConflict) => { if (cacheConflict.isResolved) { return false; } if (cacheConflict.range.equalsRange(conflict.range)) { return true; } if (cacheConflict.text === conflictText) { return true; } }); if (cacheConflict && !cacheConflict.isResolved) { this.conflictParser.setConflictResolved(uri, cacheConflict.id); } } } return Promise.resolve(resolveConflictResult); } else { if (CancelResponse.is(resolveConflictResult)) { this.reportData = { cancelNum: this.reportData.cancelNum! + 1, }; this.reportConflictData(); } if (ErrorResponse.is(resolveConflictResult)) { this.loadingRange.delete(conflict.range); return Promise.resolve(resolveConflictResult); } } } private resolveEndLineEOL(text: string): { text: string; lineNumber: number; lines: string[] } { const eol = this.getModel()?.getEOL() ?? '\n'; const lines = text.split(eol); const lastLine = lines[lines.length - 1]; // 最后一行去掉换行符 if (lastLine === '' && lines.length > 1) { lines.pop(); } return { text: lines.join(eol), lineNumber: lines.length, lines, }; } private async acceptAllConflict() { const document = this.getModel() as monaco.editor.ITextModel; if (!document) { return Promise.resolve(); } const conflicts = this.conflictParser.scanDocument(document); if (!conflicts.length) { return Promise.resolve(); } let isCancelAll = false; // TODO: 优化 this.disposables.push( this.onRequestsCancel(() => { this.logger.log('cancel all conflict'); isCancelAll = true; return; }), // 取消一个后取消所有 this.onRequestCancel(() => { this.logger.log('cancel all conflict'); isCancelAll = true; return; }), ); for (const conflict of conflicts) { if (isCancelAll) { break; } await this.conflictAIAccept(conflict); } } private registerCodeLensProvider() { return monacoApi.languages.registerCodeLensProvider([{ scheme: 'file' }], { provideCodeLenses: async (model, token) => { let lens = await this.provideCodeLens(model, token); return { lenses: lens ?? [], dispose: () => { lens = []; }, }; }, }); } private async requestAIResolveConflict( metadata: IConflictContentMetadata, range: LineRange, isRegenerate = false, ): Promise { const handler = this.resolveConflictRegistry.getTraditionalHandler(); if (!handler) { return; } if (isRegenerate) { const newContent = this.getModel()!.getValueInRange(range.toRange()); metadata.resultContent = newContent; } const response = await handler.providerRequest(metadata, { isRegenerate }, this.createRequestToken(range.id).token); return response; } // 生成 cancel token public createRequestToken(id: string): CancellationTokenSource { const token = new CancellationTokenSource(); this.cancelIndicatorMap.set(id, token); return token; } public hideResolveResultWidget(id?: string) { if (id) { this.resolveResultWidgetManager.hideWidget(id); } else { this.resolveResultWidgetManager.hideAll(); } } public hideStopWidget(id?: string) { if (id) { this.stopWidgetManager.hideWidget(id); } else { this.stopWidgetManager.hideAll(); } } public cancelRequestToken(id?: string) { if (id) { if (!this.cancelIndicatorMap.has(id)) { return; } const token = this.cancelIndicatorMap.get(id); token?.cancel(); this._onRequestCancel.fire({ id, type: 'cancel' }); return; } const requestsCancel: IRequestCancel[] = []; this.cancelIndicatorMap.forEach((token, id) => { token.cancel(); requestsCancel.push({ id, type: 'cancel' }); }); this._onRequestsCancel.fire(requestsCancel); this.cancelIndicatorMap.clear(); } public launchConflictActionsEvent(eventData: Omit): void { const { range, action } = eventData; switch (action) { case ACCEPT_CURRENT_ACTIONS: { const newValue = (eventData as IAcceptConflictActionsEvent).value; const cacheConflict = this.getCacheResolvedConflicts().get(range.id); if (cacheConflict) { const edit = { range: cacheConflict.conflict.range, text: newValue || cacheConflict.text, }; this.getModel()?.pushEditOperations(null, [edit], () => null); this.getCacheResolvedConflicts().set(range.id, { ...cacheConflict, isAccept: true, }); } this.cleanWidget(range.id); this.deleteCacheResolvedConflicts(range.id); this.cleanDecoration(range.id); this.updateCodeLens(); } break; case REVOKE_ACTIONS: { this.cleanWidget(range.id); this.deleteCacheResolvedConflicts(range.id); this.cleanDecoration(range.id); break; } case AI_RESOLVE_REGENERATE_ACTIONS: { this.cleanWidget(range.id); this.cleanDecoration(range.id); const cacheConflict = this.getCacheResolvedConflicts().get(range.id); if (cacheConflict) { this.conflictAIAccept(cacheConflict.conflict, range.id); } break; } case IGNORE_ACTIONS: { this.cleanWidget(range.id); this.cleanDecoration(range.id); const resolvedConflict = this.getCacheResolvedConflicts().get(range.id)!; this.setCacheResolvedConflict(range.id, { ...resolvedConflict, isClosed: true, }); break; } } this.updateReportData(); this.reportConflictData(); } private cleanAllCache() { this.getCacheResolvedConflicts().clear(); this.hideStopWidget(); this.hideResolveResultWidget(); this.decorationId2Dispose.clear(); this.decorationId2Range.clear(); this.updateCodeLens(); } private cleanWidget(id: string) { if (id) { this.hideStopWidget(id); this.hideResolveResultWidget(id); return; } this.hideStopWidget(); this.hideResolveResultWidget(); } private cleanDecoration(id: string) { if (this.decorationId2Dispose.has(id)) { this.decorationId2Dispose.get(id)?.call(this); this.decorationId2Dispose.delete(id); } if (this.decorationId2Range.has(id)) { this.decorationId2Range.delete(id); } } private updateCodeLensDebounce = debounce(() => { this.updateCodeLens(); }, 2000); /** * 刷新 codelens */ private updateCodeLens() { if (this.getModel()?.uri) { // @ts-ignore languageFeaturesService.codeLensProvider._onDidChange.fire(); } } }