diff --git a/.changeset/disable-diff-visualization.md b/.changeset/disable-diff-visualization.md new file mode 100644 index 0000000000..5f95e013bd --- /dev/null +++ b/.changeset/disable-diff-visualization.md @@ -0,0 +1,7 @@ +--- +"roo-cline": patch +--- + +Add setting to disable diff visualization to prevent LSP crashes + +Added a new setting `disableDiffVisualization` that allows users to disable the diff view when editing files. When enabled, files will open directly in the editor instead of showing a side-by-side diff view. This helps prevent Language Server Protocol (LSP) crashes that can occur with very large files, particularly affecting C# developers. The changes made by Roo are still visible in the chat window. diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 5b729a125f..4ca50a1200 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -83,6 +83,7 @@ export const globalSettingsSchema = z.object({ rateLimitSeconds: z.number().optional(), diffEnabled: z.boolean().optional(), + disableDiffVisualization: z.boolean().optional(), fuzzyMatchThreshold: z.number().optional(), experiments: experimentsSchema.optional(), @@ -211,6 +212,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { terminalShellIntegrationDisabled: true, diffEnabled: true, + disableDiffVisualization: false, fuzzyMatchThreshold: 1, enableCheckpoints: false, diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index a6a9d89986..c43a1ca12b 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -104,6 +104,7 @@ export type TaskOptions = { provider: ClineProvider apiConfiguration: ProviderSettings enableDiff?: boolean + disableDiffVisualization?: boolean enableCheckpoints?: boolean fuzzyMatchThreshold?: number consecutiveMistakeLimit?: number @@ -198,6 +199,7 @@ export class Task extends EventEmitter { provider, apiConfiguration, enableDiff = false, + disableDiffVisualization = false, enableCheckpoints = true, fuzzyMatchThreshold = 1.0, consecutiveMistakeLimit = 3, @@ -242,7 +244,7 @@ export class Task extends EventEmitter { this.consecutiveMistakeLimit = consecutiveMistakeLimit this.providerRef = new WeakRef(provider) this.globalStoragePath = provider.context.globalStorageUri.fsPath - this.diffViewProvider = new DiffViewProvider(this.cwd) + this.diffViewProvider = new DiffViewProvider(this.cwd, disableDiffVisualization) this.enableCheckpoints = enableCheckpoints this.rootTask = rootTask diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 57fa16a848..f73bf1eef0 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -529,6 +529,7 @@ export class ClineProvider apiConfiguration, organizationAllowList, diffEnabled: enableDiff, + disableDiffVisualization, enableCheckpoints, fuzzyMatchThreshold, experiments, @@ -542,6 +543,7 @@ export class ClineProvider provider: this, apiConfiguration, enableDiff, + disableDiffVisualization, enableCheckpoints, fuzzyMatchThreshold, task, @@ -569,6 +571,7 @@ export class ClineProvider const { apiConfiguration, diffEnabled: enableDiff, + disableDiffVisualization, enableCheckpoints, fuzzyMatchThreshold, experiments, @@ -578,6 +581,7 @@ export class ClineProvider provider: this, apiConfiguration, enableDiff, + disableDiffVisualization, enableCheckpoints, fuzzyMatchThreshold, historyItem, @@ -1546,6 +1550,7 @@ export class ClineProvider ttsEnabled: stateValues.ttsEnabled ?? false, ttsSpeed: stateValues.ttsSpeed ?? 1.0, diffEnabled: stateValues.diffEnabled ?? true, + disableDiffVisualization: stateValues.disableDiffVisualization ?? false, enableCheckpoints: stateValues.enableCheckpoints ?? true, soundVolume: stateValues.soundVolume, browserViewportSize: stateValues.browserViewportSize ?? "900x600", diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index a4d9dafecf..d5a0d9a116 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -645,6 +645,11 @@ export const webviewMessageHandler = async ( await updateGlobalState("diffEnabled", diffEnabled) await provider.postStateToWebview() break + case "disableDiffVisualization": + const disableDiffVisualization = message.bool ?? false + await updateGlobalState("disableDiffVisualization", disableDiffVisualization) + await provider.postStateToWebview() + break case "enableCheckpoints": const enableCheckpoints = message.bool ?? true await updateGlobalState("enableCheckpoints", enableCheckpoints) diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index b97886d32d..e485ee5ee5 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -34,7 +34,10 @@ export class DiffViewProvider { private streamedLines: string[] = [] private preDiagnostics: [vscode.Uri, vscode.Diagnostic[]][] = [] - constructor(private cwd: string) {} + constructor( + private cwd: string, + private disableDiffVisualization: boolean = false, + ) {} async open(relPath: string): Promise { this.relPath = relPath @@ -92,7 +95,13 @@ export class DiffViewProvider { this.documentWasOpen = true } - this.activeDiffEditor = await this.openDiffEditor() + // If diff visualization is disabled, open the file directly + if (this.disableDiffVisualization) { + const document = await vscode.workspace.openTextDocument(absolutePath) + this.activeDiffEditor = await vscode.window.showTextDocument(document, { preview: false }) + } else { + this.activeDiffEditor = await this.openDiffEditor() + } this.fadedOverlayController = new DecorationController("fadedOverlay", this.activeDiffEditor) this.activeLineController = new DecorationController("activeLine", this.activeDiffEditor) // Apply faded overlay to all lines initially. diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index ac19ba0ef2..2fad358bcc 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -188,6 +188,7 @@ export type ExtensionState = Pick< | "terminalZdotdir" | "terminalCompressProgressBar" | "diffEnabled" + | "disableDiffVisualization" | "fuzzyMatchThreshold" // | "experiments" // Optional in GlobalSettings, required here. | "language" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 5186c716b9..ea7fcd84ef 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -82,6 +82,7 @@ export interface WebviewMessage { | "ttsSpeed" | "soundVolume" | "diffEnabled" + | "disableDiffVisualization" | "enableCheckpoints" | "browserViewportSize" | "screenshotQuality" diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 905f34a860..e64aad95e7 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -73,7 +73,7 @@ const ApiOptions = ({ setErrorMessage, }: ApiOptionsProps) => { const { t } = useAppTranslation() - const { organizationAllowList } = useExtensionState() + const { organizationAllowList, disableDiffVisualization, setDisableDiffVisualization } = useExtensionState() const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => { const headers = apiConfiguration?.openAiHeaders || {} @@ -476,8 +476,19 @@ const ApiOptions = ({ <> setApiConfigurationField(field, value)} + onChange={(field, value) => { + if (field === "disableDiffVisualization") { + setDisableDiffVisualization(value) + vscode.postMessage({ + type: "disableDiffVisualization", + bool: value, + }) + } else { + setApiConfigurationField(field, value) + } + }} /> void + onChange: (field: "diffEnabled" | "disableDiffVisualization" | "fuzzyMatchThreshold", value: any) => void } export const DiffSettingsControl: React.FC = ({ diffEnabled = true, + disableDiffVisualization = false, fuzzyMatchThreshold = 1.0, onChange, }) => { @@ -23,6 +25,13 @@ export const DiffSettingsControl: React.FC = ({ [onChange], ) + const handleDisableDiffVisualizationChange = useCallback( + (e: any) => { + onChange("disableDiffVisualization", e.target.checked) + }, + [onChange], + ) + const handleThresholdChange = useCallback( (newValue: number[]) => { onChange("fuzzyMatchThreshold", newValue[0]) @@ -61,6 +70,19 @@ export const DiffSettingsControl: React.FC = ({ {t("settings:advanced.diff.matchPrecision.description")} + +
+ + + {t("settings:advanced.diff.disableVisualization.label")} + + +
+ {t("settings:advanced.diff.disableVisualization.description")} +
+
)} diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 5a330c8996..71509f32a6 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -139,6 +139,7 @@ const SettingsView = forwardRef(({ onDone, t browserViewportSize, enableCheckpoints, diffEnabled, + disableDiffVisualization, experiments, fuzzyMatchThreshold, maxOpenTabsContext, @@ -280,6 +281,7 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "ttsSpeed", value: ttsSpeed }) vscode.postMessage({ type: "soundVolume", value: soundVolume }) vscode.postMessage({ type: "diffEnabled", bool: diffEnabled }) + vscode.postMessage({ type: "disableDiffVisualization", bool: disableDiffVisualization }) vscode.postMessage({ type: "enableCheckpoints", bool: enableCheckpoints }) vscode.postMessage({ type: "browserViewportSize", text: browserViewportSize }) vscode.postMessage({ type: "remoteBrowserHost", text: remoteBrowserHost }) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index ab79f63df8..f18ce909bd 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -68,6 +68,7 @@ export interface ExtensionStateContextType extends ExtensionState { setTtsEnabled: (value: boolean) => void setTtsSpeed: (value: number) => void setDiffEnabled: (value: boolean) => void + setDisableDiffVisualization: (value: boolean) => void setEnableCheckpoints: (value: boolean) => void setBrowserViewportSize: (value: string) => void setFuzzyMatchThreshold: (value: number) => void @@ -154,6 +155,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode ttsEnabled: false, ttsSpeed: 1.0, diffEnabled: false, + disableDiffVisualization: false, enableCheckpoints: true, fuzzyMatchThreshold: 1.0, language: "en", // Default language code @@ -342,6 +344,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setTtsEnabled: (value) => setState((prevState) => ({ ...prevState, ttsEnabled: value })), setTtsSpeed: (value) => setState((prevState) => ({ ...prevState, ttsSpeed: value })), setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })), + setDisableDiffVisualization: (value) => + setState((prevState) => ({ ...prevState, disableDiffVisualization: value })), setEnableCheckpoints: (value) => setState((prevState) => ({ ...prevState, enableCheckpoints: value })), setBrowserViewportSize: (value: string) => setState((prevState) => ({ ...prevState, browserViewportSize: value })), diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index b8e51afc50..a4a2ce5a5b 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -469,6 +469,10 @@ "matchPrecision": { "label": "Match precision", "description": "This slider controls how precisely code sections must match when applying diffs. Lower values allow more flexible matching but increase the risk of incorrect replacements. Use values below 100% with extreme caution." + }, + "disableVisualization": { + "label": "Disable diff visualization", + "description": "When enabled, files will open directly in the editor instead of showing a diff view. This can prevent LSP crashes with very large files. You can still see the changes in the chat window." } } },