Path: blob/main/extensions/copilot/src/extension/inlineEdits/node/rebaseResult.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { MarkdownLoggable } from '../../../platform/inlineEdits/common/inlineEditLogContext';6import { StringEdit, StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit';7import { OffsetRange } from '../../../util/vs/editor/common/core/ranges/offsetRange';8import { NesRebaseConfigs } from '../common/editRebase';9import { CachedOrRebasedEdit } from './nextEditCache';1011export interface RebaseResult {12readonly edit: CachedOrRebasedEdit | undefined;13readonly failureInfo?: RebaseFailureInfo;14}1516export class RebaseFailureInfo implements MarkdownLoggable {17constructor(18readonly originalDocument: string,19readonly editWindow: OffsetRange | undefined,20readonly originalEdits: readonly StringReplacement[],21readonly userEditSince: StringEdit,22readonly currentDocument: string,23readonly currentSelection: readonly OffsetRange[],24readonly nesRebaseConfigs: NesRebaseConfigs,25) { }2627toMarkdown(): string {28const lines: string[] = [];2930lines.push('### Original Document');31lines.push('```');32lines.push(this.originalDocument);33lines.push('```');3435lines.push('');36lines.push('### Suggested Edits');37for (let i = 0; i < this.originalEdits.length; i++) {38const edit = this.originalEdits[i];39lines.push(`- **Edit ${i}**: \`${edit.toString()}\``);40lines.push(` - replaces: \`${JSON.stringify(edit.replaceRange.substring(this.originalDocument))}\``);41lines.push(` - with: \`${JSON.stringify(edit.newText)}\``);42}4344if (this.editWindow) {45lines.push('');46lines.push(`### Edit Window: ${this.editWindow.toString()}`);47lines.push(`Content: \`${JSON.stringify(this.editWindow.substring(this.originalDocument))}\``);48}4950lines.push('');51lines.push('### User Edit Since');52for (const replacement of this.userEditSince.replacements) {53lines.push(`- \`${replacement.toString()}\``);54lines.push(` - replaces: \`${JSON.stringify(replacement.replaceRange.substring(this.originalDocument))}\``);55lines.push(` - with: \`${JSON.stringify(replacement.newText)}\``);56}5758lines.push('');59lines.push('### Current Document (after user edits)');60lines.push('```');61lines.push(this.currentDocument);62lines.push('```');6364if (this.currentSelection.length > 0) {65lines.push('');66lines.push(`### Cursor: ${this.currentSelection.map(s => s.toString()).join(', ')}`);67}6869lines.push('');70lines.push('### Document Intended After Suggested Edits');71lines.push('```');72try {73const intended = new StringEdit(this.originalEdits.slice()).apply(this.originalDocument);74lines.push(intended);75} catch {76lines.push('<could not compute>');77}78lines.push('```');7980lines.push('');81lines.push('### Copy-Pasteable Test');82lines.push('```typescript');83lines.push(this._generateTest());84lines.push('```');8586return lines.join('\n');87}8889private _generateTest(): string {90const lines: string[] = [];91lines.push(`test('rebase failure (auto-generated)', () => {`);92lines.push(`\tconst originalDocument = ${toBacktickLiteral(this.originalDocument)};`);9394lines.push('\tconst originalEdits = [');95for (const edit of this.originalEdits) {96lines.push(`\t\tStringReplacement.replace(new OffsetRange(${edit.replaceRange.start}, ${edit.replaceRange.endExclusive}), ${toBacktickLiteral(edit.newText)}),`);97}98lines.push('\t];');99100lines.push('\tconst userEditSince = StringEdit.create([');101for (const replacement of this.userEditSince.replacements) {102lines.push(`\t\tStringReplacement.replace(new OffsetRange(${replacement.replaceRange.start}, ${replacement.replaceRange.endExclusive}), ${toBacktickLiteral(replacement.newText)}),`);103}104lines.push('\t]);');105106lines.push(`\tconst currentDocumentContent = ${toBacktickLiteral(this.currentDocument)};`);107108if (this.editWindow) {109lines.push(`\tconst editWindow = new OffsetRange(${this.editWindow.start}, ${this.editWindow.endExclusive});`);110} else {111lines.push('\tconst editWindow = undefined;');112}113114lines.push(`\tconst currentSelection = [${this.currentSelection.map(s => `new OffsetRange(${s.start}, ${s.endExclusive})`).join(', ')}];`);115116const configEntries: string[] = [];117if (this.nesRebaseConfigs.absorbSubsequenceTyping) {118configEntries.push(`absorbSubsequenceTyping: ${this.nesRebaseConfigs.absorbSubsequenceTyping}`);119}120if (this.nesRebaseConfigs.reverseAgreement) {121configEntries.push(`reverseAgreement: ${this.nesRebaseConfigs.reverseAgreement}`);122}123configEntries.push(`maxImperfectAgreementLength: ${this.nesRebaseConfigs.maxImperfectAgreementLength}`);124lines.push(`\tconst nesConfigs = { ${configEntries.join(', ')} };`);125126lines.push('');127lines.push('\tconst logger = new TestLogService();');128lines.push('\texpect(userEditSince.apply(originalDocument)).toBe(currentDocumentContent);');129130const configsArg = ', nesConfigs';131lines.push(`\texpect(tryRebase(originalDocument, editWindow, originalEdits, [], userEditSince, currentDocumentContent, currentSelection, 'strict', logger${configsArg})).toMatchInlineSnapshot();`);132133lines.push('});');134135return lines.join('\n');136}137}138139function toBacktickLiteral(value: string): string {140const escaped = value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');141return '`' + escaped + '`';142}143144145