Skip to content

Commit 4058c2b

Browse files
committed
Add option for parallel diff edits
1 parent 7845c8a commit 4058c2b

File tree

15 files changed

+1068
-68
lines changed

15 files changed

+1068
-68
lines changed

.changeset/tame-buses-unite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Add option for parallel diff edits

.clinerules

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,72 @@
1-
- Before attempting completion, always make sure that any code changes have test coverage and that the tests pass.
1+
# Code Quality Rules
2+
3+
1. Test Coverage:
4+
- Before attempting completion, always make sure that any code changes have test coverage
5+
- Ensure all tests pass before submitting changes
6+
7+
2. Git Commits:
8+
- When finishing a task, always output a git commit command
9+
- Include a descriptive commit message that follows conventional commit format
10+
11+
# Adding a New Settings Checkbox
12+
13+
To add a new settings checkbox that persists its state, follow these steps:
14+
15+
1. Add the message type to WebviewMessage.ts:
16+
- Add the setting name to the WebviewMessage type's type union
17+
- Example: `| "multisearchDiffEnabled"`
18+
19+
2. Add the setting to ExtensionStateContext.tsx:
20+
- Add the setting to the ExtensionStateContextType interface
21+
- Add the setter function to the interface
22+
- Add the setting to the initial state in useState
23+
- Add the setting to the contextValue object
24+
- Example:
25+
```typescript
26+
interface ExtensionStateContextType {
27+
multisearchDiffEnabled: boolean;
28+
setMultisearchDiffEnabled: (value: boolean) => void;
29+
}
30+
```
31+
32+
3. Add the setting to ClineProvider.ts:
33+
- Add the setting name to the GlobalStateKey type union
34+
- Add the setting to the Promise.all array in getState
35+
- Add the setting to the return value in getState with a default value
36+
- Add the setting to the destructured variables in getStateToPostToWebview
37+
- Add the setting to the return value in getStateToPostToWebview
38+
- Add a case in setWebviewMessageListener to handle the setting's message type
39+
- Example:
40+
```typescript
41+
case "multisearchDiffEnabled":
42+
await this.updateGlobalState("multisearchDiffEnabled", message.bool)
43+
await this.postStateToWebview()
44+
break
45+
```
46+
47+
4. Add the checkbox UI to SettingsView.tsx:
48+
- Import the setting and its setter from ExtensionStateContext
49+
- Add the VSCodeCheckbox component with the setting's state and onChange handler
50+
- Add appropriate labels and description text
51+
- Example:
52+
```typescript
53+
<VSCodeCheckbox
54+
checked={multisearchDiffEnabled}
55+
onChange={(e: any) => setMultisearchDiffEnabled(e.target.checked)}
56+
>
57+
<span style={{ fontWeight: "500" }}>Enable multi-search diff matching</span>
58+
</VSCodeCheckbox>
59+
```
60+
61+
5. Add the setting to handleSubmit in SettingsView.tsx:
62+
- Add a vscode.postMessage call to send the setting's value when clicking Done
63+
- Example:
64+
```typescript
65+
vscode.postMessage({ type: "multisearchDiffEnabled", bool: multisearchDiffEnabled })
66+
```
67+
68+
These steps ensure that:
69+
- The setting's state is properly typed throughout the application
70+
- The setting persists between sessions
71+
- The setting's value is properly synchronized between the webview and extension
72+
- The setting has a proper UI representation in the settings view

src/core/Cline.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export class Cline {
103103
task?: string | undefined,
104104
images?: string[] | undefined,
105105
historyItem?: HistoryItem | undefined,
106+
multisearchDiffEnabled?: boolean,
106107
) {
107108
this.providerRef = new WeakRef(provider)
108109
this.api = buildApiHandler(apiConfiguration)
@@ -113,7 +114,7 @@ export class Cline {
113114
this.customInstructions = customInstructions
114115
this.diffEnabled = enableDiff ?? false
115116
if (this.diffEnabled && this.api.getModel().id) {
116-
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0)
117+
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0, multisearchDiffEnabled ?? false)
117118
}
118119
if (historyItem) {
119120
this.taskId = historyItem.id

src/core/__tests__/Cline.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,8 @@ describe('Cline', () => {
316316

317317
expect(cline.diffEnabled).toBe(true);
318318
expect(cline.diffStrategy).toBeDefined();
319-
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 0.9);
320-
319+
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 0.9, false);
320+
321321
getDiffStrategySpy.mockRestore();
322322
});
323323

@@ -335,8 +335,8 @@ describe('Cline', () => {
335335

336336
expect(cline.diffEnabled).toBe(true);
337337
expect(cline.diffStrategy).toBeDefined();
338-
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 1.0);
339-
338+
expect(getDiffStrategySpy).toHaveBeenCalledWith('claude-3-5-sonnet-20241022', 1.0, false);
339+
340340
getDiffStrategySpy.mockRestore();
341341
});
342342

src/core/diff/DiffStrategy.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import type { DiffStrategy } from './types'
22
import { UnifiedDiffStrategy } from './strategies/unified'
33
import { SearchReplaceDiffStrategy } from './strategies/search-replace'
4+
import { SearchReplaceMultisearchDiffStrategy } from './strategies/search-replace-multisearch'
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, multisearchDiffEnabled?: boolean): DiffStrategy {
11+
// Use SearchReplaceMultisearchDiffStrategy when multisearch diff is enabled
12+
// Otherwise fall back to regular SearchReplaceDiffStrategy
13+
return multisearchDiffEnabled
14+
? new SearchReplaceMultisearchDiffStrategy(fuzzyMatchThreshold ?? 1.0)
15+
: new SearchReplaceDiffStrategy(fuzzyMatchThreshold ?? 1.0)
1316
}
1417

1518
export type { DiffStrategy }
16-
export { UnifiedDiffStrategy, SearchReplaceDiffStrategy }
19+
export { UnifiedDiffStrategy, SearchReplaceDiffStrategy, SearchReplaceMultisearchDiffStrategy }
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { levenshteinDistance, getSimilarity } from "../utils"
2+
3+
describe("levenshteinDistance", () => {
4+
it("should return 0 for identical strings", () => {
5+
expect(levenshteinDistance("hello", "hello")).toBe(0)
6+
})
7+
8+
it("should handle single character differences", () => {
9+
expect(levenshteinDistance("hello", "hallo")).toBe(1)
10+
})
11+
12+
it("should handle insertions", () => {
13+
expect(levenshteinDistance("hello", "hello!")).toBe(1)
14+
})
15+
16+
it("should handle deletions", () => {
17+
expect(levenshteinDistance("hello!", "hello")).toBe(1)
18+
})
19+
20+
it("should handle completely different strings", () => {
21+
expect(levenshteinDistance("hello", "world")).toBe(4)
22+
})
23+
24+
it("should handle empty strings", () => {
25+
expect(levenshteinDistance("", "")).toBe(0)
26+
expect(levenshteinDistance("hello", "")).toBe(5)
27+
expect(levenshteinDistance("", "hello")).toBe(5)
28+
})
29+
})
30+
31+
describe("getSimilarity", () => {
32+
it("should return 1 for identical strings", () => {
33+
expect(getSimilarity("hello world", "hello world")).toBe(1)
34+
})
35+
36+
it("should handle empty search string", () => {
37+
expect(getSimilarity("hello world", "")).toBe(1)
38+
})
39+
40+
it("should normalize whitespace", () => {
41+
expect(getSimilarity("hello world", "hello world")).toBe(1)
42+
expect(getSimilarity("hello\tworld", "hello world")).toBe(1)
43+
expect(getSimilarity("hello\nworld", "hello world")).toBe(1)
44+
})
45+
46+
it("should preserve case sensitivity", () => {
47+
expect(getSimilarity("Hello World", "hello world")).toBeLessThan(1)
48+
})
49+
50+
it("should handle partial matches", () => {
51+
const similarity = getSimilarity("hello world", "hello there")
52+
expect(similarity).toBeGreaterThan(0)
53+
expect(similarity).toBeLessThan(1)
54+
})
55+
56+
it("should handle completely different strings", () => {
57+
const similarity = getSimilarity("hello world", "goodbye universe")
58+
expect(similarity).toBeGreaterThan(0)
59+
expect(similarity).toBeLessThan(0.5)
60+
})
61+
})

0 commit comments

Comments
 (0)