apply-multi-diff
Version:
A zero-dependency library to apply unified diffs and search-and-replace patches, with support for fuzzy matching.
43 lines (40 loc) • 4.68 kB
JavaScript
import {ERROR_CODES}from'../constants';import {createErrorResult}from'../utils/error';import {dedent,levenshtein,getCommonIndent}from'../utils/string';const $=s=>`apply_diff Tool: Search and Replace
Targeted code changes using search/replace blocks. Supports fuzzy matching.
Parameters:
:file_path: Path to file relative to ${s}
:diff_content: Search/replace blocks
:start_line: (optional) Line to start search (required for insertions)
:end_line: (optional) Line to end search
Format:
<<<<< SEARCH
content to find
=======
replacement content
>>>>> REPLACE
Special cases:
- INSERT Insertion (note the empty SEARCH block and \`start_line\`):
<apply_diff file_path="src/app.ts" start_line="5">
src/app.ts
<<<<<<< SEARCH
=======
// Add a new configuration setting
const newConfig = initializeNewDependency();
>>>>>>> REPLACE
</apply_diff>`,N=s=>{const r=s.split(`
`);return r.filter(a=>a.trim()!=="").every(a=>/^\s*\d+\s*\|/.test(a))?r.map(a=>a.replace(/^\s*\d+\s*\|\s?/,"")).join(`
`):s},B=s=>s.replace(/^\r?\n/,"").replace(/\r?\n$/,""),H=s=>{const r=[],f=/^\s*<<<<<<< SEARCH\s*$/m,a=/^\s*>>>>>>> REPLACE\s*$/m;let e=s;const n=e.indexOf(`
`);for(n!==-1&&!e.substring(0,n).includes("<<<<<<<")&&(e=e.substring(n+1));f.test(e);){const u=e.search(f),l=e.match(a);if(!l||typeof l.index>"u")break;const m=l.index+l[0].length,S=e.substring(u,m).split(/^\s*<<<<<<< SEARCH\s*$|^\s*=======*\s*$|^\s*>>>>>>> REPLACE\s*$/m);S.length>=4&&r.push({search:N(B(S[1]??"")),replace:N(B(S[2]??""))}),e=e.substring(m);}return r.length>0?r:null},v=(s,r,f,a)=>{if(r.length===0)return null;let e=-1,n=1/0;const u=r.join(`
`),l=dedent(u),m=f-1,E=a??s.length;for(let d=m;d<=E-r.length;d++){const h=s.slice(d,d+r.length).join(`
`),x=dedent(h),t=levenshtein(l,x);if(t<n&&(n=t,e=d),t===0)break}if(e===-1)return null;const S=s.slice(e,e+r.length).join(`
`),C=dedent(S),L=Math.max(20,Math.floor(Math.min(l.length,C.length)*.7));if(n>L)return null;if(n>0){const b=s.slice(e,e+r.length).join(`
`),h=dedent(b),x=/["'].*["']/.test(l),t=/["'].*["']/.test(h);if(x&&t){const i=l.match(/["'](.*?)["']/),c=h.match(/["'](.*?)["']/);if(i&&c&&typeof i[1]=="string"&&typeof c[1]=="string"){const o=i[1],g=c[1];if(levenshtein(o,g)>o.length*.5)return null}}}return {index:e,distance:n}},W=(s,r,f={})=>{const a=H(r);if(!a)return createErrorResult(ERROR_CODES.INVALID_DIFF_FORMAT,"Invalid diff format. Could not parse any '<<<<<<< SEARCH'...'>>>>>>> REPLACE' blocks.");let e=s;for(const n of a){if(n.search===""){if(typeof f.start_line!="number")return createErrorResult(ERROR_CODES.INSERTION_REQUIRES_LINE_NUMBER,"Insertion requires a start_line. A SEARCH block was empty, but no start_line was provided.");if(e===""){e=n.replace;continue}const t=e.split(`
`),i=Math.max(0,f.start_line-1);let c="";if(i<t.length){const p=t[i],R=p?.match(/^[ \t]*/)?.[0]||"";if(i>0){const w=t[i-1],I=w?.match(/^[ \t]*/)?.[0]||"",T=w?.trim()??"";I.length>R.length&&(p?.trim()?.length??0)>0?c=I:T.endsWith("{")||T.endsWith("[")||T.endsWith("(")?c=I+" ":c=R;}else c=R;}else t.length>0&&(c=t[t.length-1]?.match(/^[ \t]*/)?.[0]||"");const o=n.replace.split(`
`),g=getCommonIndent(n.replace),_=o.map(p=>{if(p.trim()==="")return p;const R=p.startsWith(g)?p.substring(g.length):p;return c+R});t.splice(i,0,..._),e=t.join(`
`);continue}const u=e.split(`
`),l=n.search===`
`?[""]:n.search.split(`
`),m=v(u,l,f.start_line??1,f.end_line??u.length);if(m===null)return createErrorResult(ERROR_CODES.SEARCH_BLOCK_NOT_FOUND,"Search block not found in the original content. The content to be replaced could not be located in the file, even with fuzzy matching.");const{index:E}=m,S=E+l.length,C=u.slice(E,S).join(`
`),L=getCommonIndent(C),d=n.replace?n.replace.split(`
`):[],b=getCommonIndent(n.replace);let h;if(l.length===1&&d.length===1&&m.distance>0){const t=u[E],i=l[0]??"",c=d[0]??"";if(t?.includes(i)){const o=t.replace(i,"").trim();o.length>0&&c.includes(o)?h=[c]:h=[t.replace(i,c)];}else if(m.distance>0){const g=(t?.trim()??"").match(/;\s*(\/\/.*|\/\*.*\*\/)$/);if(g){const _=g[1]??"";h=[(t?.match(/^[ \t]*/)?.[0]||"")+c.trim()+" "+_];}else h=d.map(_=>{if(_.trim()==="")return "";const p=_.startsWith(b)?_.substring(b.length):_;return L+p});}else h=d.map(o=>{if(o.trim()==="")return "";const g=o.startsWith(b)?o.substring(b.length):o;return L+g});}else h=d.map(t=>{if(t.trim()==="")return "";const i=t.startsWith(b)?t.substring(b.length):t;return L+i});e=[...u.slice(0,E),...h,...u.slice(S)].join(`
`);}return {success:true,content:e}};export{v as _findBestMatch_for_debug,H as _parseDiff_for_debug,W as applyDiff,$ as getToolDescription};//# sourceMappingURL=search-replace.js.map
//# sourceMappingURL=search-replace.js.map