Skip to content

Commit ed59fcd

Browse files
ulugbeknaCopilot
andcommitted
account for CRLF
Co-authored-by: Copilot <copilot@github.com>
1 parent af4687c commit ed59fcd

File tree

2 files changed

+57
-5
lines changed

2 files changed

+57
-5
lines changed

extensions/copilot/src/extension/inlineEdits/test/vscode-node/isInlineSuggestion.spec.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ suite('toInlineSuggestion', () => {
103103
assert.deepStrictEqual(result!.range, new Range(1, 15, 1, 15));
104104
// Text is prepended with the newline between cursor and original range,
105105
// and the trailing newline is dropped so we don't introduce a blank line.
106-
assert.strictEqual(result!.newText, '\n' + replaceText.replace(/\n$/, ''));
106+
assert.strictEqual(result!.newText, '\n' + replaceText.replace(/\r?\n$/, ''));
107107
});
108108

109109
test('should not use ghost text when inserting on next line when none empty', () => {
@@ -151,7 +151,7 @@ suite('toInlineSuggestion', () => {
151151
assert.isDefined(result);
152152
assert.deepStrictEqual(result!.range, new Range(0, 13, 0, 13));
153153
// Trailing '\n' is dropped to avoid a spurious blank line.
154-
assert.strictEqual(result!.newText, '\n' + replaceText.replace(/\n$/, ''));
154+
assert.strictEqual(result!.newText, '\n' + replaceText.replace(/\r?\n$/, ''));
155155
});
156156

157157
test('multi-line insertion without trailing newline rejected when target line has content', () => {
@@ -593,4 +593,55 @@ const fieldLabels: Record<keyof FormData, string> = {
593593
// the cursor is preserved.
594594
assert.strictEqual(result!.newText, '\n password: "Password",');
595595
});
596+
597+
suite('CRLF', () => {
598+
599+
function createCRLFDocument(lines: string[], languageId: string = 'typescript') {
600+
return createTextDocumentData(
601+
Uri.from({ scheme: 'test', path: '/test/file.ts' }),
602+
lines.join('\r\n'),
603+
languageId,
604+
'\r\n',
605+
).document;
606+
}
607+
608+
test('next-line insertion: trailing CRLF is dropped (no dangling \\r)', () => {
609+
const document = createCRLFDocument(['function foo(', '', 'other']);
610+
const cursorPosition = new Position(0, 13); // end of "function foo("
611+
const replaceRange = new Range(1, 0, 1, 0); // empty line
612+
const replaceText = ' a: string,\r\n b: number\r\n';
613+
614+
const result = toInlineSuggestion(cursorPosition, document, replaceRange, replaceText);
615+
assert.isDefined(result);
616+
assert.deepStrictEqual(result!.range, new Range(0, 13, 0, 13));
617+
// The trailing CRLF must be stripped entirely; no dangling '\r'
618+
// should leak into the suggestion text.
619+
assert.strictEqual(result!.newText, '\r\n a: string,\r\n b: number');
620+
});
621+
622+
test('next-line insertion: trailing CRLF on non-empty target line', () => {
623+
const document = createCRLFDocument(['function foo(', ')', 'other']);
624+
const cursorPosition = new Position(0, 13);
625+
const replaceRange = new Range(1, 0, 1, 0);
626+
const replaceText = ' a: string,\r\n b: number\r\n';
627+
628+
const result = toInlineSuggestion(cursorPosition, document, replaceRange, replaceText);
629+
assert.isDefined(result);
630+
assert.deepStrictEqual(result!.range, new Range(0, 13, 0, 13));
631+
assert.strictEqual(result!.newText, '\r\n a: string,\r\n b: number');
632+
});
633+
634+
test('next-line insertion: CRLF-only newText is fully stripped', () => {
635+
const document = createCRLFDocument(['line 0', '', 'line 2']);
636+
const cursorPosition = new Position(0, 6);
637+
const replaceRange = new Range(1, 0, 1, 0);
638+
const replaceText = '\r\n';
639+
640+
const result = toInlineSuggestion(cursorPosition, document, replaceRange, replaceText);
641+
assert.isDefined(result);
642+
assert.deepStrictEqual(result!.range, new Range(0, 6, 0, 6));
643+
// Only the prepended CRLF between cursor and original range remains.
644+
assert.strictEqual(result!.newText, '\r\n');
645+
});
646+
});
596647
});

extensions/copilot/src/extension/inlineEdits/vscode-node/isInlineSuggestion.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ export function toInlineSuggestion(cursorPos: Position, doc: TextDocument, range
2828
const textBetweenCursorAndRange = doc.getText(new Range(cursorPos, range.start));
2929
// The original range is on the next line, so the line terminator that
3030
// already separates the cursor's line from range.start is preserved.
31-
// Drop a single trailing '\n' from newText (if present) to avoid
32-
// inserting an extra blank line after the suggestion.
33-
const adjustedNewText = newText.endsWith('\n') ? newText.slice(0, -1) : newText;
31+
// Drop a single trailing line ending from newText (if present) to avoid
32+
// inserting an extra blank line after the suggestion. Handle CRLF as
33+
// well as LF so we don't leave a dangling '\r'.
34+
const adjustedNewText = newText.replace(/\r?\n$/, '');
3435
return { range: adjustedRange, newText: textBetweenCursorAndRange + adjustedNewText };
3536
}
3637

0 commit comments

Comments
 (0)