Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tame-buses-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"roo-cline": patch
---

Add option for parallel diff edits
73 changes: 72 additions & 1 deletion .clinerules
Original file line number Diff line number Diff line change
@@ -1 +1,72 @@
- Before attempting completion, always make sure that any code changes have test coverage and that the tests pass.
# Code Quality Rules

1. Test Coverage:
- Before attempting completion, always make sure that any code changes have test coverage
- Ensure all tests pass before submitting changes

2. Git Commits:
- When finishing a task, always output a git commit command
- Include a descriptive commit message that follows conventional commit format

# Adding a New Settings Checkbox

To add a new settings checkbox that persists its state, follow these steps:

1. Add the message type to WebviewMessage.ts:
- Add the setting name to the WebviewMessage type's type union
- Example: `| "multisearchDiffEnabled"`

2. Add the setting to ExtensionStateContext.tsx:
- Add the setting to the ExtensionStateContextType interface
- Add the setter function to the interface
- Add the setting to the initial state in useState
- Add the setting to the contextValue object
- Example:
```typescript
interface ExtensionStateContextType {
multisearchDiffEnabled: boolean;
setMultisearchDiffEnabled: (value: boolean) => void;
}
```

3. Add the setting to ClineProvider.ts:
- Add the setting name to the GlobalStateKey type union
- Add the setting to the Promise.all array in getState
- Add the setting to the return value in getState with a default value
- Add the setting to the destructured variables in getStateToPostToWebview
- Add the setting to the return value in getStateToPostToWebview
- Add a case in setWebviewMessageListener to handle the setting's message type
- Example:
```typescript
case "multisearchDiffEnabled":
await this.updateGlobalState("multisearchDiffEnabled", message.bool)
await this.postStateToWebview()
break
```

4. Add the checkbox UI to SettingsView.tsx:
- Import the setting and its setter from ExtensionStateContext
- Add the VSCodeCheckbox component with the setting's state and onChange handler
- Add appropriate labels and description text
- Example:
```typescript
<VSCodeCheckbox
checked={multisearchDiffEnabled}
onChange={(e: any) => setMultisearchDiffEnabled(e.target.checked)}
>
<span style={{ fontWeight: "500" }}>Enable multi-search diff matching</span>
</VSCodeCheckbox>
```

5. Add the setting to handleSubmit in SettingsView.tsx:
- Add a vscode.postMessage call to send the setting's value when clicking Done
- Example:
```typescript
vscode.postMessage({ type: "multisearchDiffEnabled", bool: multisearchDiffEnabled })
```

These steps ensure that:
- The setting's state is properly typed throughout the application
- The setting persists between sessions
- The setting's value is properly synchronized between the webview and extension
- The setting has a proper UI representation in the settings view
3 changes: 2 additions & 1 deletion src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export class Cline {
task?: string | undefined,
images?: string[] | undefined,
historyItem?: HistoryItem | undefined,
multisearchDiffEnabled?: boolean,
) {
this.providerRef = new WeakRef(provider)
this.api = buildApiHandler(apiConfiguration)
Expand All @@ -113,7 +114,7 @@ export class Cline {
this.customInstructions = customInstructions
this.diffEnabled = enableDiff ?? false
if (this.diffEnabled && this.api.getModel().id) {
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0)
this.diffStrategy = getDiffStrategy(this.api.getModel().id, fuzzyMatchThreshold ?? 1.0, multisearchDiffEnabled ?? false)
}
if (historyItem) {
this.taskId = historyItem.id
Expand Down
8 changes: 4 additions & 4 deletions src/core/__tests__/Cline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ describe('Cline', () => {

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

getDiffStrategySpy.mockRestore();
});

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

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

getDiffStrategySpy.mockRestore();
});

Expand Down
13 changes: 8 additions & 5 deletions src/core/diff/DiffStrategy.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import type { DiffStrategy } from './types'
import { UnifiedDiffStrategy } from './strategies/unified'
import { SearchReplaceDiffStrategy } from './strategies/search-replace'
import { SearchReplaceMultisearchDiffStrategy } from './strategies/search-replace-multisearch'
/**
* Get the appropriate diff strategy for the given model
* @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
* @returns The appropriate diff strategy for the model
*/
export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number): DiffStrategy {
// For now, return SearchReplaceDiffStrategy for all models
// This architecture allows for future optimizations based on model capabilities
return new SearchReplaceDiffStrategy(fuzzyMatchThreshold ?? 1.0)
export function getDiffStrategy(model: string, fuzzyMatchThreshold?: number, multisearchDiffEnabled?: boolean): DiffStrategy {
// Use SearchReplaceMultisearchDiffStrategy when multisearch diff is enabled
// Otherwise fall back to regular SearchReplaceDiffStrategy
return multisearchDiffEnabled
? new SearchReplaceMultisearchDiffStrategy(fuzzyMatchThreshold ?? 1.0)
: new SearchReplaceDiffStrategy(fuzzyMatchThreshold ?? 1.0)
}

export type { DiffStrategy }
export { UnifiedDiffStrategy, SearchReplaceDiffStrategy }
export { UnifiedDiffStrategy, SearchReplaceDiffStrategy, SearchReplaceMultisearchDiffStrategy }
61 changes: 61 additions & 0 deletions src/core/diff/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { levenshteinDistance, getSimilarity } from "../utils"

describe("levenshteinDistance", () => {
it("should return 0 for identical strings", () => {
expect(levenshteinDistance("hello", "hello")).toBe(0)
})

it("should handle single character differences", () => {
expect(levenshteinDistance("hello", "hallo")).toBe(1)
})

it("should handle insertions", () => {
expect(levenshteinDistance("hello", "hello!")).toBe(1)
})

it("should handle deletions", () => {
expect(levenshteinDistance("hello!", "hello")).toBe(1)
})

it("should handle completely different strings", () => {
expect(levenshteinDistance("hello", "world")).toBe(4)
})

it("should handle empty strings", () => {
expect(levenshteinDistance("", "")).toBe(0)
expect(levenshteinDistance("hello", "")).toBe(5)
expect(levenshteinDistance("", "hello")).toBe(5)
})
})

describe("getSimilarity", () => {
it("should return 1 for identical strings", () => {
expect(getSimilarity("hello world", "hello world")).toBe(1)
})

it("should handle empty search string", () => {
expect(getSimilarity("hello world", "")).toBe(1)
})

it("should normalize whitespace", () => {
expect(getSimilarity("hello world", "hello world")).toBe(1)
expect(getSimilarity("hello\tworld", "hello world")).toBe(1)
expect(getSimilarity("hello\nworld", "hello world")).toBe(1)
})

it("should preserve case sensitivity", () => {
expect(getSimilarity("Hello World", "hello world")).toBeLessThan(1)
})

it("should handle partial matches", () => {
const similarity = getSimilarity("hello world", "hello there")
expect(similarity).toBeGreaterThan(0)
expect(similarity).toBeLessThan(1)
})

it("should handle completely different strings", () => {
const similarity = getSimilarity("hello world", "goodbye universe")
expect(similarity).toBeGreaterThan(0)
expect(similarity).toBeLessThan(0.5)
})
})
Loading
Loading