@@ -275,6 +275,20 @@ function createDocumentSymbol(
275275 assert . isUndefined ( toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ) ;
276276 } ) ;
277277
278+ test ( 'next-line: non-empty replace range covering only whitespace on next line' , ( ) => {
279+ const document = createMockDocument ( [
280+ ' for item in items:' ,
281+ ' ' ,
282+ 'other_code' ,
283+ ] , 'python' ) ;
284+ const cursorPosition = new Position ( 1 , 4 ) ;
285+ const replaceRange = new Range ( 0 , 22 , 1 , 8 ) ;
286+ const replaceText = '\n process(item)\n return result' ;
287+
288+ const result = toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ;
289+ assert . isDefined ( result ) ;
290+ } ) ;
291+
278292 test ( 'next-line: range 2 lines ahead is rejected' , ( ) => {
279293 const document = createMockDocument ( [ 'line 0' , 'line 1' , '' , 'line 3' ] ) ;
280294 const cursorPosition = new Position ( 0 , 6 ) ;
@@ -419,4 +433,118 @@ function createDocumentSymbol(
419433 assert . isDefined ( result ) ;
420434 assert . strictEqual ( result ! . newText , 'console.log' ) ;
421435 } ) ;
436+
437+ // --- Prefix-stripping: multi-line range reduction ---
438+
439+ test ( 'prefix-strip: multi-line range reduced to single-line edit on cursor line' , ( ) => {
440+ // Range spans lines 0-1, replaced text = "abc\ndef", newText = "abc\ndefghi"
441+ // Common prefix up to newline = "abc\n", strip it → range becomes (1,0)-(1,3), newText = "defghi"
442+ const document = createMockDocument ( [ 'abc' , 'def' , 'other' ] ) ;
443+ const cursorPosition = new Position ( 1 , 0 ) ;
444+ const replaceRange = new Range ( 0 , 0 , 1 , 3 ) ;
445+ const replaceText = 'abc\ndefghi' ;
446+
447+ const result = toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ;
448+ assert . isDefined ( result ) ;
449+ assert . deepStrictEqual ( result ! . range , new Range ( 1 , 0 , 1 , 3 ) ) ;
450+ assert . strictEqual ( result ! . newText , 'defghi' ) ;
451+ } ) ;
452+
453+ test ( 'prefix-strip: no newline in common prefix, multi-line range still rejected' , ( ) => {
454+ // Range spans lines 0-1, replaced = "ab\ncd", newText = "abXY"
455+ // Common prefix = "ab" but no newline → no stripping → multi-line range rejected
456+ const document = createMockDocument ( [ 'ab' , 'cd' , 'other' ] ) ;
457+ const cursorPosition = new Position ( 0 , 0 ) ;
458+ const replaceRange = new Range ( 0 , 0 , 1 , 2 ) ;
459+ const replaceText = 'abXY' ;
460+
461+ assert . isUndefined ( toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ) ;
462+ } ) ;
463+
464+ test ( 'prefix-strip: strips multiple newlines to last boundary' , ( ) => {
465+ // Range spans 3 lines: "line0\nline1\nxy", newText = "line0\nline1\nxyz"
466+ // Common prefix includes two newlines, stripping to last → range becomes (2,0)-(2,2)
467+ const document = createMockDocument ( [ 'line0' , 'line1' , 'xy' , 'other' ] ) ;
468+ const cursorPosition = new Position ( 2 , 0 ) ;
469+ const replaceRange = new Range ( 0 , 0 , 2 , 2 ) ;
470+ const replaceText = 'line0\nline1\nxyz' ;
471+
472+ const result = toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ;
473+ assert . isDefined ( result ) ;
474+ assert . deepStrictEqual ( result ! . range , new Range ( 2 , 0 , 2 , 2 ) ) ;
475+ assert . strictEqual ( result ! . newText , 'xyz' ) ;
476+ } ) ;
477+
478+ test ( 'prefix-strip: after stripping still multi-line, rejected' , ( ) => {
479+ // Range spans 3 lines: "a\nb\nc", newText = "a\nB\nC"
480+ // Common prefix up to newline = "a\n", strip → range becomes (1,0)-(2,1) which is still multi-line
481+ const document = createMockDocument ( [ 'a' , 'b' , 'c' , 'other' ] ) ;
482+ const cursorPosition = new Position ( 1 , 0 ) ;
483+ const replaceRange = new Range ( 0 , 0 , 2 , 1 ) ;
484+ const replaceText = 'a\nB\nC' ;
485+
486+ assert . isUndefined ( toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ) ;
487+ } ) ;
488+
489+ test ( 'prefix-strip: reduced to single line but cursor on different line, rejected' , ( ) => {
490+ // Strip reduces range to line 1, but cursor is on line 0
491+ const document = createMockDocument ( [ 'abc' , 'def' , 'other' ] ) ;
492+ const cursorPosition = new Position ( 0 , 2 ) ;
493+ const replaceRange = new Range ( 0 , 0 , 1 , 3 ) ;
494+ const replaceText = 'abc\ndefghi' ;
495+
496+ assert . isUndefined ( toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ) ;
497+ } ) ;
498+
499+ test ( 'prefix-strip: reduced to single line, subword check fails' , ( ) => {
500+ // After stripping "abc\n", range = (1,0)-(1,3) with replaced "def", newText = "xy"
501+ // "def" is not a subword of "xy"
502+ const document = createMockDocument ( [ 'abc' , 'def' , 'other' ] ) ;
503+ const cursorPosition = new Position ( 1 , 0 ) ;
504+ const replaceRange = new Range ( 0 , 0 , 1 , 3 ) ;
505+ const replaceText = 'abc\nxy' ;
506+
507+ assert . isUndefined ( toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ) ;
508+ } ) ;
509+
510+ test ( 'prefix-strip: diverges before first newline, no stripping' , ( ) => {
511+ // replaced = "ax\nyz", newText = "ab\nyz" → common prefix "a" has no newline → no strip
512+ const document = createMockDocument ( [ 'ax' , 'yz' ] ) ;
513+ const cursorPosition = new Position ( 0 , 0 ) ;
514+ const replaceRange = new Range ( 0 , 0 , 1 , 2 ) ;
515+ const replaceText = 'ab\nyz' ;
516+
517+ assert . isUndefined ( toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ) ;
518+ } ) ;
519+
520+ test ( 'prefix-strip: range starts mid-line, strips prefix through newline' , ( ) => {
521+ // Document: "hello world", " ns", "other"
522+ // Range (0,6)-(1,4) → replaced text = "world\n ns", newText = "world\n new_stuff"
523+ // Common prefix = "world\n " → last newline at index 5, strip "world\n"
524+ // Reduced range: (1,0)-(1,4), newText = " new_stuff"
525+ // isSubword(" ns", " new_stuff") → true
526+ const document = createMockDocument ( [ 'hello world' , ' ns' , 'other' ] ) ;
527+ const cursorPosition = new Position ( 1 , 0 ) ;
528+ const replaceRange = new Range ( 0 , 6 , 1 , 4 ) ;
529+ const replaceText = 'world\n new_stuff' ;
530+
531+ const result = toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ;
532+ assert . isDefined ( result ) ;
533+ assert . deepStrictEqual ( result ! . range , new Range ( 1 , 0 , 1 , 4 ) ) ;
534+ assert . strictEqual ( result ! . newText , ' new_stuff' ) ;
535+ } ) ;
536+
537+ test ( 'prefix-strip: empty newText after stripping prefix' , ( ) => {
538+ // replaced = "abc\n", newText = "abc\n" → after stripping "abc\n", replaced="" and newText=""
539+ // This is a no-op on the second line, succeeds as empty edit
540+ const document = createMockDocument ( [ 'abc' , '' , 'other' ] ) ;
541+ const cursorPosition = new Position ( 1 , 0 ) ;
542+ const replaceRange = new Range ( 0 , 0 , 1 , 0 ) ;
543+ const replaceText = 'abc\n' ;
544+
545+ const result = toInlineSuggestion ( cursorPosition , document , replaceRange , replaceText ) ;
546+ assert . isDefined ( result ) ;
547+ assert . deepStrictEqual ( result ! . range , new Range ( 1 , 0 , 1 , 0 ) ) ;
548+ assert . strictEqual ( result ! . newText , '' ) ;
549+ } ) ;
422550} ) ;
0 commit comments