@@ -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} ) ;
0 commit comments