Skip to content

Commit 7affa39

Browse files
committed
Improve list files, search files, add some diff normalization
Remove a todo Revert unrelated changes
1 parent 780b9e0 commit 7affa39

File tree

11 files changed

+956
-681
lines changed

11 files changed

+956
-681
lines changed

src/core/Cline.ts

Lines changed: 377 additions & 422 deletions
Large diffs are not rendered by default.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Code normalization functions for improving diff accuracy in JS/TS code.
3+
*/
4+
5+
import crypto from 'crypto';
6+
7+
/**
8+
* Normalizes whitespace in JS/TS code to improve diff matching.
9+
* @param code The JS/TS code string.
10+
* @returns The code with normalized whitespace.
11+
*/
12+
export function normalizeWhitespace(code: string): string {
13+
return code.replace(/[\s\t\n]*/g, ' ');
14+
}
15+
16+
/**
17+
* Hashes template literals in JS/TS code to simplify diff matching.
18+
* @param code The JS/TS code string.
19+
* @returns The code with template literals replaced by hashes and a mapping of hashes to original literals.
20+
*/
21+
export function hashTemplateLiterals(code: string): { code: string; literalMap: { [hash: string]: string } } {
22+
const literalMap: { [hash: string]: string } = {};
23+
let hashCounter = 0;
24+
25+
const hashedCode = code.replace(/`([^`]*)`/g, (match, literalContent) => {
26+
const hash = `__TEMPLATE_LITERAL_HASH_${hashCounter++}__`;
27+
literalMap[hash] = match;
28+
return hash;
29+
});
30+
31+
return { code: hashedCode, literalMap };
32+
}

src/core/diff/strategies/new-unified/index.ts

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { Diff, Hunk, Change } from "./types"
22
import { findBestMatch, prepareSearchString } from "./search-strategies"
33
import { applyEdit } from "./edit-strategies"
44
import { DiffResult, DiffStrategy } from "../../types"
5+
import { normalizeWhitespace, hashTemplateLiterals } from "../../code-normalization" // Import normalization functions
6+
import { loadRequiredLanguageParsers } from "../../../../services/tree-sitter/languageParser"
7+
8+
const JS_TS_EXTENSIONS = /\.(js|jsx|ts|tsx)$/i; // Regex for JS/TS file extensions
59

610
export class NewUnifiedDiffStrategy implements DiffStrategy {
711
private readonly confidenceThreshold: number
@@ -173,7 +177,7 @@ Usage:
173177
<diff>
174178
Your diff here
175179
</diff>
176-
</apply_diff>`
180+
</apply_diff>`
177181
}
178182

179183
// Helper function to split a hunk into smaller hunks based on contiguous changes
@@ -235,8 +239,28 @@ Your diff here
235239
startLine?: number,
236240
endLine?: number,
237241
): Promise<DiffResult> {
238-
const parsedDiff = this.parseUnifiedDiff(diffContent)
239-
const originalLines = originalContent.split("\n")
242+
// Check if the file is a JS/TS file
243+
const isJSTSFile = JS_TS_EXTENSIONS.test(arguments[0]); // Assuming path is the first argument
244+
245+
let normalizedOriginalContent = originalContent;
246+
let normalizedDiffContent = diffContent;
247+
let literalMap: { [hash: string]: string } = {};
248+
249+
if (isJSTSFile) {
250+
// Apply normalization for JS/TS files
251+
normalizedOriginalContent = normalizeWhitespace(originalContent);
252+
normalizedDiffContent = normalizeWhitespace(diffContent);
253+
const templateLiteralResult = hashTemplateLiterals(normalizedOriginalContent);
254+
normalizedOriginalContent = templateLiteralResult.code;
255+
literalMap = templateLiteralResult.literalMap;
256+
const diffTemplateLiteralResult = hashTemplateLiterals(normalizedDiffContent);
257+
normalizedDiffContent = diffTemplateLiteralResult.code;
258+
// TODO: Consider merging literalMaps if needed, or handle diffContent literals separately
259+
}
260+
261+
262+
const parsedDiff = this.parseUnifiedDiff(normalizedDiffContent)
263+
const originalLines = normalizedOriginalContent.split("\n")
240264
let result = [...originalLines]
241265

242266
if (!parsedDiff.hunks.length) {
@@ -345,6 +369,35 @@ Your diff here
345369
}
346370
}
347371

348-
return { success: true, content: result.join("\n") }
372+
let finalContent = result.join("\n");
373+
374+
if (isJSTSFile) {
375+
// Reverse template literal hashing
376+
Object.keys(literalMap).forEach(hash => {
377+
finalContent = finalContent.replace(hash, literalMap[hash]);
378+
});
379+
}
380+
381+
382+
if (isJSTSFile) {
383+
// Perform syntax check for JS/TS files
384+
try {
385+
const parsers = await loadRequiredLanguageParsers(["temp.ts"]); // Dummy file for parser loading
386+
const parser = parsers["ts"]?.parser;
387+
if (parser) {
388+
parser.parse(finalContent); // Attempt to parse
389+
} else {
390+
console.warn("TypeScript parser not loaded, skipping syntax check.");
391+
}
392+
} catch (e) {
393+
console.error("JS/TS parsing error after applying diff:", e);
394+
return {
395+
success: false,
396+
error: "JS/TS syntax error introduced by diff. Rollback recommended.",
397+
};
398+
}
399+
}
400+
401+
return { success: true, content: finalContent }
349402
}
350403
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ Your search/replace content here
136136
endLine?: number,
137137
): Promise<DiffResult> {
138138
// Extract the search and replace blocks
139-
const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n?=======\n([\s\S]*?)\n?>>>>>>> REPLACE/)
139+
const match = diffContent.match(
140+
/<<<<<<< SEARCH(?:\r?\n)([\s\S]*?)(?:\r?\n)?=======(?:\r?\n)([\s\S]*?)(?:\r?\n)?>>>>>>> REPLACE/,
141+
)
140142
if (!match) {
141143
return {
142144
success: false,
@@ -266,7 +268,6 @@ Your search/replace content here
266268
const bestMatchSection = bestMatchContent
267269
? `\n\nBest Match Found:\n${addLineNumbers(bestMatchContent, matchIndex + 1)}`
268270
: `\n\nBest Match Found:\n(no match)`
269-
270271
const lineRange =
271272
startLine || endLine
272273
? ` at ${startLine ? `start: ${startLine}` : "start"} to ${endLine ? `end: ${endLine}` : "end"}`

src/core/prompts/responses.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ ${toolUseInstructionsReminder}
2020
2121
# Next Steps
2222
23-
If you have completed the user's task, use the attempt_completion tool.
24-
If you require additional information from the user, use the ask_followup_question tool.
25-
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
23+
If you have completed the user's task, use the attempt_completion tool.
24+
If you require additional information from the user, use the ask_followup_question tool.
25+
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
2626
(This is an automated message, so do not respond to it conversationally.)`,
2727

2828
tooManyMistakes: (feedback?: string) =>
Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,95 @@
11
import { ToolArgs } from "./types"
22

33
export function getListFilesDescription(args: ToolArgs): string {
4-
return `## list_files
5-
Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
6-
Parameters:
7-
- path: (required) The path of the directory to list contents for (relative to the current working directory ${args.cwd})
8-
- recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.
9-
Usage:
4+
return `<tool_definition>
5+
<name>list_files</name>
6+
<description>Lists files and directories in the specified path, returning a JSON structure with file metadata.</description>
7+
8+
<output_format>
9+
<json_structure>
10+
<field>
11+
<name>name</name>
12+
<description>File or directory name</description>
13+
</field>
14+
<field>
15+
<name>type</name>
16+
<description>Either "file" or "directory"</description>
17+
</field>
18+
<field>
19+
<name>extension</name>
20+
<description>File extension (files only)</description>
21+
</field>
22+
<field>
23+
<name>children</name>
24+
<description>Nested files/directories (directories only)</description>
25+
</field>
26+
<field>
27+
<name>hasMore</name>
28+
<description>Indicates if listing was truncated</description>
29+
</field>
30+
</json_structure>
31+
</output_format>
32+
33+
<parameters>
34+
<parameter>
35+
<name>path</name>
36+
<required>true</required>
37+
<description>Directory path relative to ${args.cwd}</description>
38+
</parameter>
39+
<parameter>
40+
<name>recursive</name>
41+
<required>false</required>
42+
<description>When true, lists contents recursively</description>
43+
</parameter>
44+
<parameter>
45+
<name>format</name>
46+
<required>false</required>
47+
<description>Format of the output. Can be "flat" or "tree". Default is "flat"</description>
48+
</parameter>
49+
</parameters>
50+
51+
<syntax_template>
1052
<list_files>
1153
<path>Directory path here</path>
1254
<recursive>true or false (optional)</recursive>
55+
<format>"flat" or "tree" (optional)</format>
1356
</list_files>
57+
</syntax_template>
1458
15-
Example: Requesting to list all files in the current directory
59+
<example>
1660
<list_files>
1761
<path>.</path>
1862
<recursive>false</recursive>
19-
</list_files>`
63+
<format>tree</format>
64+
</list_files>
65+
</example>
66+
67+
<example_output>
68+
{
69+
"root": {
70+
"name": "project",
71+
"type": "directory",
72+
"children": {
73+
"src": {
74+
"name": "src",
75+
"type": "directory",
76+
"children": {
77+
"index.ts": {
78+
"name": "index",
79+
"type": "file",
80+
"extension": ".ts",
81+
}
82+
}
83+
},
84+
"package.json": {
85+
"name": "package",
86+
"type": "file",
87+
"extension": ".json",
88+
}
89+
}
90+
},
91+
"hasMore": false
92+
}
93+
</example_output>
94+
</tool_definition>`
2095
}

src/integrations/workspace/WorkspaceTracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class WorkspaceTracker {
2323
if (!cwd) {
2424
return
2525
}
26-
const [files, _] = await listFiles(cwd, true, MAX_INITIAL_FILES)
26+
const [files, _] = await listFiles(cwd, { path: cwd, recursive: true, limit: MAX_INITIAL_FILES, format: "flat" })
2727
files.slice(0, MAX_INITIAL_FILES).forEach((file) => this.filePaths.add(this.normalizeFilePath(file)))
2828
this.workspaceDidUpdate()
2929
}

0 commit comments

Comments
 (0)