Skip to content

Commit 130c5ff

Browse files
authored
Merge pull request #115 from RooVetGit/search_replace_line_numbers
Improvements to search/replace diff
2 parents a3d10de + 2292372 commit 130c5ff

File tree

6 files changed

+384
-55
lines changed

6 files changed

+384
-55
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Roo Cline Changelog
22

3+
## [2.2.7]
4+
5+
- More fixes to search/replace diffs
6+
37
## [2.2.6]
48

59
- Add a fuzzy match tolerance when applying diffs

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Roo Cline",
44
"description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.",
55
"publisher": "RooVeterinaryInc",
6-
"version": "2.2.6",
6+
"version": "2.2.7",
77
"icon": "assets/icons/rocket.png",
88
"galleryBanner": {
99
"color": "#617A91",

src/core/diff/strategies/__tests__/search-replace.test.ts

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,329 @@ function sum(a, b) {
291291
})
292292
})
293293

294+
describe('line-constrained search', () => {
295+
let strategy: SearchReplaceDiffStrategy
296+
297+
beforeEach(() => {
298+
strategy = new SearchReplaceDiffStrategy()
299+
})
300+
301+
it('should find and replace within specified line range', () => {
302+
const originalContent = `
303+
function one() {
304+
return 1;
305+
}
306+
307+
function two() {
308+
return 2;
309+
}
310+
311+
function three() {
312+
return 3;
313+
}
314+
`.trim()
315+
const diffContent = `test.ts
316+
<<<<<<< SEARCH
317+
function two() {
318+
return 2;
319+
}
320+
=======
321+
function two() {
322+
return "two";
323+
}
324+
>>>>>>> REPLACE`
325+
326+
const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
327+
expect(result).toBe(`function one() {
328+
return 1;
329+
}
330+
331+
function two() {
332+
return "two";
333+
}
334+
335+
function three() {
336+
return 3;
337+
}`)
338+
})
339+
340+
it('should find and replace within buffer zone (5 lines before/after)', () => {
341+
const originalContent = `
342+
function one() {
343+
return 1;
344+
}
345+
346+
function two() {
347+
return 2;
348+
}
349+
350+
function three() {
351+
return 3;
352+
}
353+
`.trim()
354+
const diffContent = `test.ts
355+
<<<<<<< SEARCH
356+
function three() {
357+
return 3;
358+
}
359+
=======
360+
function three() {
361+
return "three";
362+
}
363+
>>>>>>> REPLACE`
364+
365+
// Even though we specify lines 5-7, it should still find the match at lines 9-11
366+
// because it's within the 5-line buffer zone
367+
const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
368+
expect(result).toBe(`function one() {
369+
return 1;
370+
}
371+
372+
function two() {
373+
return 2;
374+
}
375+
376+
function three() {
377+
return "three";
378+
}`)
379+
})
380+
381+
it('should not find matches outside search range and buffer zone', () => {
382+
const originalContent = `
383+
function one() {
384+
return 1;
385+
}
386+
387+
function two() {
388+
return 2;
389+
}
390+
391+
function three() {
392+
return 3;
393+
}
394+
395+
function four() {
396+
return 4;
397+
}
398+
399+
function five() {
400+
return 5;
401+
}
402+
`.trim()
403+
const diffContent = `test.ts
404+
<<<<<<< SEARCH
405+
function five() {
406+
return 5;
407+
}
408+
=======
409+
function five() {
410+
return "five";
411+
}
412+
>>>>>>> REPLACE`
413+
414+
// Searching around function two() (lines 5-7)
415+
// function five() is more than 5 lines away, so it shouldn't match
416+
const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
417+
expect(result).toBe(false)
418+
})
419+
420+
it('should handle search range at start of file', () => {
421+
const originalContent = `
422+
function one() {
423+
return 1;
424+
}
425+
426+
function two() {
427+
return 2;
428+
}
429+
`.trim()
430+
const diffContent = `test.ts
431+
<<<<<<< SEARCH
432+
function one() {
433+
return 1;
434+
}
435+
=======
436+
function one() {
437+
return "one";
438+
}
439+
>>>>>>> REPLACE`
440+
441+
const result = strategy.applyDiff(originalContent, diffContent, 1, 3)
442+
expect(result).toBe(`function one() {
443+
return "one";
444+
}
445+
446+
function two() {
447+
return 2;
448+
}`)
449+
})
450+
451+
it('should handle search range at end of file', () => {
452+
const originalContent = `
453+
function one() {
454+
return 1;
455+
}
456+
457+
function two() {
458+
return 2;
459+
}
460+
`.trim()
461+
const diffContent = `test.ts
462+
<<<<<<< SEARCH
463+
function two() {
464+
return 2;
465+
}
466+
=======
467+
function two() {
468+
return "two";
469+
}
470+
>>>>>>> REPLACE`
471+
472+
const result = strategy.applyDiff(originalContent, diffContent, 5, 7)
473+
expect(result).toBe(`function one() {
474+
return 1;
475+
}
476+
477+
function two() {
478+
return "two";
479+
}`)
480+
})
481+
482+
it('should match specific instance of duplicate code using line numbers', () => {
483+
const originalContent = `
484+
function processData(data) {
485+
return data.map(x => x * 2);
486+
}
487+
488+
function unrelatedStuff() {
489+
console.log("hello");
490+
}
491+
492+
// Another data processor
493+
function processData(data) {
494+
return data.map(x => x * 2);
495+
}
496+
497+
function moreStuff() {
498+
console.log("world");
499+
}
500+
`.trim()
501+
const diffContent = `test.ts
502+
<<<<<<< SEARCH
503+
function processData(data) {
504+
return data.map(x => x * 2);
505+
}
506+
=======
507+
function processData(data) {
508+
// Add logging
509+
console.log("Processing data...");
510+
return data.map(x => x * 2);
511+
}
512+
>>>>>>> REPLACE`
513+
514+
// Target the second instance of processData
515+
const result = strategy.applyDiff(originalContent, diffContent, 10, 12)
516+
expect(result).toBe(`function processData(data) {
517+
return data.map(x => x * 2);
518+
}
519+
520+
function unrelatedStuff() {
521+
console.log("hello");
522+
}
523+
524+
// Another data processor
525+
function processData(data) {
526+
// Add logging
527+
console.log("Processing data...");
528+
return data.map(x => x * 2);
529+
}
530+
531+
function moreStuff() {
532+
console.log("world");
533+
}`)
534+
})
535+
536+
it('should search from start line to end of file when only start_line is provided', () => {
537+
const originalContent = `
538+
function one() {
539+
return 1;
540+
}
541+
542+
function two() {
543+
return 2;
544+
}
545+
546+
function three() {
547+
return 3;
548+
}
549+
`.trim()
550+
const diffContent = `test.ts
551+
<<<<<<< SEARCH
552+
function three() {
553+
return 3;
554+
}
555+
=======
556+
function three() {
557+
return "three";
558+
}
559+
>>>>>>> REPLACE`
560+
561+
// Only provide start_line, should search from there to end of file
562+
const result = strategy.applyDiff(originalContent, diffContent, 8)
563+
expect(result).toBe(`function one() {
564+
return 1;
565+
}
566+
567+
function two() {
568+
return 2;
569+
}
570+
571+
function three() {
572+
return "three";
573+
}`)
574+
})
575+
576+
it('should search from start of file to end line when only end_line is provided', () => {
577+
const originalContent = `
578+
function one() {
579+
return 1;
580+
}
581+
582+
function two() {
583+
return 2;
584+
}
585+
586+
function three() {
587+
return 3;
588+
}
589+
`.trim()
590+
const diffContent = `test.ts
591+
<<<<<<< SEARCH
592+
function one() {
593+
return 1;
594+
}
595+
=======
596+
function one() {
597+
return "one";
598+
}
599+
>>>>>>> REPLACE`
600+
601+
// Only provide end_line, should search from start of file to there
602+
const result = strategy.applyDiff(originalContent, diffContent, undefined, 4)
603+
expect(result).toBe(`function one() {
604+
return "one";
605+
}
606+
607+
function two() {
608+
return 2;
609+
}
610+
611+
function three() {
612+
return 3;
613+
}`)
614+
})
615+
})
616+
294617
describe('getToolDescription', () => {
295618
let strategy: SearchReplaceDiffStrategy
296619

@@ -312,5 +635,11 @@ function sum(a, b) {
312635
expect(description).toContain('<apply_diff>')
313636
expect(description).toContain('</apply_diff>')
314637
})
638+
639+
it('should document start_line and end_line parameters', () => {
640+
const description = strategy.getToolDescription('/test')
641+
expect(description).toContain('start_line: (required) The line number where the search block starts.')
642+
expect(description).toContain('end_line: (required) The line number where the search block ends.')
643+
})
315644
})
316645
})

0 commit comments

Comments
 (0)