Skip to content

Commit b35206b

Browse files
authored
Merge pull request #364 from daniel-lxs/new_unified
New unified edit strategy
2 parents 8a47d28 + 82a0ffe commit b35206b

23 files changed

+2718
-159
lines changed

package-lock.json

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

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,11 @@
202202
"@changesets/cli": "^2.27.10",
203203
"@changesets/types": "^6.0.0",
204204
"@types/diff": "^5.2.1",
205+
"@types/diff-match-patch": "^1.0.36",
205206
"@types/jest": "^29.5.14",
206207
"@types/mocha": "^10.0.7",
207208
"@types/node": "20.x",
209+
"@types/string-similarity": "^4.0.2",
208210
"@typescript-eslint/eslint-plugin": "^7.14.1",
209211
"@typescript-eslint/parser": "^7.11.0",
210212
"@vscode/test-cli": "^0.0.9",
@@ -230,6 +232,7 @@
230232
"@modelcontextprotocol/sdk": "^1.0.1",
231233
"@types/clone-deep": "^4.0.4",
232234
"@types/pdf-parse": "^1.1.4",
235+
"@types/tmp": "^0.2.6",
233236
"@types/turndown": "^5.0.5",
234237
"@types/vscode": "^1.95.0",
235238
"@vscode/codicons": "^0.0.36",
@@ -240,7 +243,9 @@
240243
"default-shell": "^2.2.0",
241244
"delay": "^6.0.0",
242245
"diff": "^5.2.0",
246+
"diff-match-patch": "^1.0.5",
243247
"fast-deep-equal": "^3.1.3",
248+
"fastest-levenshtein": "^1.0.16",
244249
"globby": "^14.0.2",
245250
"isbinaryfile": "^5.0.2",
246251
"mammoth": "^1.8.0",
@@ -252,8 +257,11 @@
252257
"puppeteer-chromium-resolver": "^23.0.0",
253258
"puppeteer-core": "^23.4.0",
254259
"serialize-error": "^11.0.3",
260+
"simple-git": "^3.27.0",
255261
"sound-play": "^1.1.0",
262+
"string-similarity": "^4.0.4",
256263
"strip-ansi": "^7.1.0",
264+
"tmp": "^0.2.3",
257265
"tree-sitter-wasms": "^0.1.11",
258266
"turndown": "^7.2.0",
259267
"web-tree-sitter": "^0.22.6",

src/core/Cline.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { detectCodeOmission } from "../integrations/editor/detect-omission"
5252
import { BrowserSession } from "../services/browser/BrowserSession"
5353
import { OpenRouterHandler } from "../api/providers/openrouter"
5454
import { McpHub } from "../services/mcp/McpHub"
55+
import crypto from "crypto"
5556

5657
const cwd =
5758
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@@ -71,6 +72,7 @@ export class Cline {
7172
customInstructions?: string
7273
diffStrategy?: DiffStrategy
7374
diffEnabled: boolean = false
75+
fuzzyMatchThreshold: number = 1.0
7476

7577
apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
7678
clineMessages: ClineMessage[] = []
@@ -105,28 +107,46 @@ export class Cline {
105107
fuzzyMatchThreshold?: number,
106108
task?: string | undefined,
107109
images?: string[] | undefined,
108-
historyItem?: HistoryItem | undefined
110+
historyItem?: HistoryItem | undefined,
111+
experimentalDiffStrategy: boolean = false,
109112
) {
110-
this.providerRef = new WeakRef(provider)
113+
if (!task && !images && !historyItem) {
114+
throw new Error('Either historyItem or task/images must be provided');
115+
}
116+
117+
this.taskId = crypto.randomUUID()
111118
this.api = buildApiHandler(apiConfiguration)
112119
this.terminalManager = new TerminalManager()
113120
this.urlContentFetcher = new UrlContentFetcher(provider.context)
114121
this.browserSession = new BrowserSession(provider.context)
115-
this.diffViewProvider = new DiffViewProvider(cwd)
116122
this.customInstructions = customInstructions
117123
this.diffEnabled = enableDiff ?? false
118-
if (this.diffEnabled && this.api.getModel().id) {
119-
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0)
120-
}
124+
this.fuzzyMatchThreshold = fuzzyMatchThreshold ?? 1.0
125+
this.providerRef = new WeakRef(provider)
126+
this.diffViewProvider = new DiffViewProvider(cwd)
127+
121128
if (historyItem) {
122129
this.taskId = historyItem.id
123-
this.resumeTaskFromHistory()
124-
} else if (task || images) {
125-
this.taskId = Date.now().toString()
130+
}
131+
132+
// Initialize diffStrategy based on current state
133+
this.updateDiffStrategy(experimentalDiffStrategy)
134+
135+
if (task || images) {
126136
this.startTask(task, images)
127-
} else {
128-
throw new Error("Either historyItem or task/images must be provided")
137+
} else if (historyItem) {
138+
this.resumeTaskFromHistory()
139+
}
140+
}
141+
142+
// Add method to update diffStrategy
143+
async updateDiffStrategy(experimentalDiffStrategy?: boolean) {
144+
// If not provided, get from current state
145+
if (experimentalDiffStrategy === undefined) {
146+
const { experimentalDiffStrategy: stateExperimentalDiffStrategy } = await this.providerRef.deref()?.getState() ?? {}
147+
experimentalDiffStrategy = stateExperimentalDiffStrategy ?? false
129148
}
149+
this.diffStrategy = getDiffStrategy(this.api.getModel().id, this.fuzzyMatchThreshold, experimentalDiffStrategy)
130150
}
131151

132152
// Storing task to disk for history
@@ -1326,7 +1346,7 @@ export class Cline {
13261346
const originalContent = await fs.readFile(absolutePath, "utf-8")
13271347

13281348
// Apply the diff to the original content
1329-
const diffResult = this.diffStrategy?.applyDiff(
1349+
const diffResult = await this.diffStrategy?.applyDiff(
13301350
originalContent,
13311351
diffContent,
13321352
parseInt(block.params.start_line ?? ''),

src/core/__tests__/Cline.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ describe('Cline', () => {
322322

323323
expect(cline.diffEnabled).toBe(true);
324324
expect(cline.diffStrategy).toBeDefined();
325-
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 0.9);
325+
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 0.9, false);
326326

327327
getDiffStrategySpy.mockRestore();
328328
});
@@ -341,7 +341,7 @@ describe('Cline', () => {
341341

342342
expect(cline.diffEnabled).toBe(true);
343343
expect(cline.diffStrategy).toBeDefined();
344-
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 1.0);
344+
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 1.0, false);
345345

346346
getDiffStrategySpy.mockRestore();
347347
});

src/core/diff/DiffStrategy.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import type { DiffStrategy } from './types'
22
import { UnifiedDiffStrategy } from './strategies/unified'
33
import { SearchReplaceDiffStrategy } from './strategies/search-replace'
4+
import { NewUnifiedDiffStrategy } from './strategies/new-unified'
45
/**
56
* Get the appropriate diff strategy for the given model
67
* @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
78
* @returns The appropriate diff strategy for the model
89
*/
9-
export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number): DiffStrategy {
10-
// For now, return SearchReplaceDiffStrategy for all models
11-
// This architecture allows for future optimizations based on model capabilities
12-
return new SearchReplaceDiffStrategy(fuzzyMatchThreshold ?? 1.0)
10+
export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number, experimentalDiffStrategy: boolean = false): DiffStrategy {
11+
if (experimentalDiffStrategy) {
12+
return new NewUnifiedDiffStrategy(fuzzyMatchThreshold)
13+
}
14+
return new SearchReplaceDiffStrategy(fuzzyMatchThreshold)
1315
}
1416

1517
export type { DiffStrategy }

0 commit comments

Comments
 (0)