mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
fix: add diagnostics settings to global state (#2379)
- Add includeDiagnostics, maxDiagnosticsCount, and diagnosticsFilter to global settings - Create DiagnosticsSettings component for UI configuration - Update ClineProvider to handle diagnostics settings messages - Modify diagnosticsToProblemsString to accept options parameter - Update ExtensionState type and related components - Add proper defaults: includeDiagnostics=false, maxDiagnosticsCount=5, diagnosticsFilter=['error','warning'] This allows users to configure diagnostics settings that persist across sessions instead of being reset to defaults on each restart.
This commit is contained in:
parent
54edab571a
commit
6ab504a36f
13 changed files with 236 additions and 1 deletions
|
|
@ -70,6 +70,10 @@ export const globalSettingsSchema = z.object({
|
|||
showRooIgnoredFiles: z.boolean().optional(),
|
||||
maxReadFileLine: z.number().optional(),
|
||||
|
||||
includeDiagnostics: z.boolean().optional(),
|
||||
maxDiagnosticsCount: z.number().optional(),
|
||||
diagnosticsFilter: z.array(z.string()).optional(),
|
||||
|
||||
terminalOutputLineLimit: z.number().optional(),
|
||||
terminalShellIntegrationTimeout: z.number().optional(),
|
||||
terminalShellIntegrationDisabled: z.boolean().optional(),
|
||||
|
|
|
|||
|
|
@ -246,6 +246,14 @@ async function getFileOrFolderContent(
|
|||
}
|
||||
|
||||
async function getWorkspaceProblems(cwd: string): Promise<string> {
|
||||
// Check if diagnostics are enabled
|
||||
const config = vscode.workspace.getConfiguration("roo-cline")
|
||||
const includeDiagnostics = config.get<boolean>("includeDiagnostics", true)
|
||||
|
||||
if (!includeDiagnostics) {
|
||||
return "Diagnostics are disabled in settings."
|
||||
}
|
||||
|
||||
const diagnostics = vscode.languages.getDiagnostics()
|
||||
const result = await diagnosticsToProblemsString(
|
||||
diagnostics,
|
||||
|
|
|
|||
|
|
@ -1329,6 +1329,9 @@ export class ClineProvider
|
|||
showRooIgnoredFiles,
|
||||
language,
|
||||
maxReadFileLine,
|
||||
includeDiagnostics,
|
||||
maxDiagnosticsCount,
|
||||
diagnosticsFilter,
|
||||
terminalCompressProgressBar,
|
||||
historyPreviewCollapsed,
|
||||
cloudUserInfo,
|
||||
|
|
@ -1439,6 +1442,9 @@ export class ClineProvider
|
|||
renderContext: this.renderContext,
|
||||
maxReadFileLine: maxReadFileLine ?? -1,
|
||||
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
|
||||
includeDiagnostics: includeDiagnostics ?? false,
|
||||
maxDiagnosticsCount: maxDiagnosticsCount ?? 5,
|
||||
diagnosticsFilter: diagnosticsFilter ?? ["error", "warning"],
|
||||
settingsImportedAt: this.settingsImportedAt,
|
||||
terminalCompressProgressBar: terminalCompressProgressBar ?? true,
|
||||
hasSystemPromptOverride,
|
||||
|
|
@ -1590,6 +1596,9 @@ export class ClineProvider
|
|||
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
|
||||
maxReadFileLine: stateValues.maxReadFileLine ?? -1,
|
||||
maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5,
|
||||
includeDiagnostics: stateValues.includeDiagnostics ?? false,
|
||||
maxDiagnosticsCount: stateValues.maxDiagnosticsCount ?? 5,
|
||||
diagnosticsFilter: stateValues.diagnosticsFilter ?? ["error", "warning"],
|
||||
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
|
||||
cloudUserInfo,
|
||||
cloudIsAuthenticated,
|
||||
|
|
|
|||
|
|
@ -432,6 +432,9 @@ describe("ClineProvider", () => {
|
|||
autoCondenseContextPercent: 100,
|
||||
cloudIsAuthenticated: false,
|
||||
sharingEnabled: false,
|
||||
includeDiagnostics: false,
|
||||
maxDiagnosticsCount: 5,
|
||||
diagnosticsFilter: ["error", "warning"],
|
||||
}
|
||||
|
||||
const message: ExtensionMessage = {
|
||||
|
|
|
|||
|
|
@ -995,6 +995,18 @@ export const webviewMessageHandler = async (
|
|||
await updateGlobalState("maxConcurrentFileReads", valueToSave)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "includeDiagnostics":
|
||||
await updateGlobalState("includeDiagnostics", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxDiagnosticsCount":
|
||||
await updateGlobalState("maxDiagnosticsCount", message.value ?? 5)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "diagnosticsFilter":
|
||||
await updateGlobalState("diagnosticsFilter", message.values ?? ["error", "warning"])
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "setHistoryPreviewCollapsed": // Add the new case handler
|
||||
await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
|
||||
// No need to call postStateToWebview here as the UI already updated optimistically
|
||||
|
|
|
|||
|
|
@ -74,17 +74,62 @@ export async function diagnosticsToProblemsString(
|
|||
diagnostics: [vscode.Uri, vscode.Diagnostic[]][],
|
||||
severities: vscode.DiagnosticSeverity[],
|
||||
cwd: string,
|
||||
options?: {
|
||||
includeDiagnostics?: boolean
|
||||
maxDiagnosticsCount?: number
|
||||
diagnosticsFilter?: string[]
|
||||
},
|
||||
): Promise<string> {
|
||||
// Use provided options or fall back to VSCode configuration
|
||||
const includeDiagnostics =
|
||||
options?.includeDiagnostics ??
|
||||
vscode.workspace.getConfiguration("roo-cline").get<boolean>("includeDiagnostics", false)
|
||||
|
||||
if (!includeDiagnostics) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const maxDiagnosticsCount =
|
||||
options?.maxDiagnosticsCount ??
|
||||
vscode.workspace.getConfiguration("roo-cline").get<number>("maxDiagnosticsCount", 5)
|
||||
const diagnosticsFilter =
|
||||
options?.diagnosticsFilter ??
|
||||
vscode.workspace.getConfiguration("roo-cline").get<string[]>("diagnosticsFilter", ["error", "warning"])
|
||||
|
||||
const documents = new Map<vscode.Uri, vscode.TextDocument>()
|
||||
const fileStats = new Map<vscode.Uri, vscode.FileStat>()
|
||||
let result = ""
|
||||
let totalDiagnosticsCount = 0
|
||||
|
||||
for (const [uri, fileDiagnostics] of diagnostics) {
|
||||
const problems = fileDiagnostics
|
||||
.filter((d) => severities.includes(d.severity))
|
||||
.filter((d) => {
|
||||
// Apply diagnostics filter
|
||||
if (diagnosticsFilter.length === 0) return true
|
||||
|
||||
const source = d.source || ""
|
||||
const code = typeof d.code === "object" ? d.code.value : d.code
|
||||
const filterKey = source ? `${source} ${code || ""}`.trim() : `${code || ""}`.trim()
|
||||
|
||||
// Check if this diagnostic should be filtered out
|
||||
return !diagnosticsFilter.some((filter) => {
|
||||
// Support partial matching
|
||||
return filterKey.includes(filter) || d.message.includes(filter)
|
||||
})
|
||||
})
|
||||
.sort((a, b) => a.range.start.line - b.range.start.line)
|
||||
|
||||
if (problems.length > 0) {
|
||||
result += `\n\n${path.relative(cwd, uri.fsPath).toPosix()}`
|
||||
|
||||
for (const diagnostic of problems) {
|
||||
// Check if we've reached the max count
|
||||
if (maxDiagnosticsCount > 0 && totalDiagnosticsCount >= maxDiagnosticsCount) {
|
||||
result += `\n... (${diagnostics.reduce((sum, [, diags]) => sum + diags.filter((d) => severities.includes(d.severity)).length, 0) - totalDiagnosticsCount} more diagnostics omitted)`
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
let label: string
|
||||
switch (diagnostic.severity) {
|
||||
case vscode.DiagnosticSeverity.Error:
|
||||
|
|
@ -121,6 +166,8 @@ export async function diagnosticsToProblemsString(
|
|||
} catch {
|
||||
result += `\n- [${source}${label}] ${line} | (unavailable) : ${diagnostic.message}`
|
||||
}
|
||||
|
||||
totalDiagnosticsCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,6 +344,25 @@
|
|||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%settings.rooCodeCloudEnabled.description%"
|
||||
},
|
||||
"roo-cline.includeDiagnostics": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "%settings.includeDiagnostics.description%"
|
||||
},
|
||||
"roo-cline.maxDiagnosticsCount": {
|
||||
"type": "number",
|
||||
"default": 50,
|
||||
"minimum": 0,
|
||||
"description": "%settings.maxDiagnosticsCount.description%"
|
||||
},
|
||||
"roo-cline.diagnosticsFilter": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"description": "%settings.diagnosticsFilter.description%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,5 +30,8 @@
|
|||
"settings.vsCodeLmModelSelector.vendor.description": "The vendor of the language model (e.g. copilot)",
|
||||
"settings.vsCodeLmModelSelector.family.description": "The family of the language model (e.g. gpt-4)",
|
||||
"settings.customStoragePath.description": "Custom storage path. Leave empty to use the default location. Supports absolute paths (e.g. 'D:\\RooCodeStorage')",
|
||||
"settings.rooCodeCloudEnabled.description": "Enable Roo Code Cloud."
|
||||
"settings.rooCodeCloudEnabled.description": "Enable Roo Code Cloud.",
|
||||
"settings.includeDiagnostics.description": "Include diagnostics (errors/warnings) in API requests. Disable this when creating multi-file features to avoid temporary errors distracting the AI.",
|
||||
"settings.maxDiagnosticsCount.description": "Maximum number of diagnostics to include in API requests (0 for unlimited). Helps control token usage.",
|
||||
"settings.diagnosticsFilter.description": "Filter diagnostics by code or source. Add diagnostic codes (e.g., 'dart Error', 'eslint/no-unused-vars') to exclude specific types."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,9 @@ export type ExtensionState = Pick<
|
|||
// | "showRooIgnoredFiles" // Optional in GlobalSettings, required here.
|
||||
// | "maxReadFileLine" // Optional in GlobalSettings, required here.
|
||||
| "maxConcurrentFileReads" // Optional in GlobalSettings, required here.
|
||||
| "includeDiagnostics"
|
||||
| "maxDiagnosticsCount"
|
||||
| "diagnosticsFilter"
|
||||
| "terminalOutputLineLimit"
|
||||
| "terminalShellIntegrationTimeout"
|
||||
| "terminalShellIntegrationDisabled"
|
||||
|
|
@ -223,6 +226,10 @@ export type ExtensionState = Pick<
|
|||
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
|
||||
maxReadFileLine: number // Maximum number of lines to read from a file before truncating
|
||||
|
||||
includeDiagnostics: boolean // Whether to include diagnostics in context
|
||||
maxDiagnosticsCount: number // Maximum number of diagnostics to include
|
||||
diagnosticsFilter: string[] // Filter for diagnostic severities
|
||||
|
||||
experiments: Experiments // Map of experiment IDs to their enabled state
|
||||
|
||||
mcpEnabled: boolean
|
||||
|
|
|
|||
|
|
@ -144,6 +144,9 @@ export interface WebviewMessage {
|
|||
| "language"
|
||||
| "maxReadFileLine"
|
||||
| "maxConcurrentFileReads"
|
||||
| "includeDiagnostics"
|
||||
| "maxDiagnosticsCount"
|
||||
| "diagnosticsFilter"
|
||||
| "searchFiles"
|
||||
| "toggleApiConfigPin"
|
||||
| "setHistoryPreviewCollapsed"
|
||||
|
|
|
|||
105
webview-ui/src/components/settings/DiagnosticsSettings.tsx
Normal file
105
webview-ui/src/components/settings/DiagnosticsSettings.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import { HTMLAttributes } from "react"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { AlertCircle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Input, Slider } from "@/components/ui"
|
||||
|
||||
import { SetCachedStateField } from "./types"
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
import { Section } from "./Section"
|
||||
|
||||
type DiagnosticsSettingsProps = HTMLAttributes<HTMLDivElement> & {
|
||||
includeDiagnostics?: boolean
|
||||
maxDiagnosticsCount?: number
|
||||
diagnosticsFilter?: string[]
|
||||
setCachedStateField: SetCachedStateField<"includeDiagnostics" | "maxDiagnosticsCount" | "diagnosticsFilter">
|
||||
}
|
||||
|
||||
export const DiagnosticsSettings = ({
|
||||
includeDiagnostics,
|
||||
maxDiagnosticsCount,
|
||||
diagnosticsFilter,
|
||||
setCachedStateField,
|
||||
className,
|
||||
...props
|
||||
}: DiagnosticsSettingsProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleFilterChange = (value: string) => {
|
||||
const filters = value
|
||||
.split(",")
|
||||
.map((f) => f.trim())
|
||||
.filter((f) => f.length > 0)
|
||||
setCachedStateField("diagnosticsFilter", filters)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)} {...props}>
|
||||
<SectionHeader description={t("settings:diagnostics.description")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertCircle className="w-4" />
|
||||
<div>{t("settings:sections.diagnostics")}</div>
|
||||
</div>
|
||||
</SectionHeader>
|
||||
|
||||
<Section>
|
||||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={includeDiagnostics}
|
||||
onChange={(e: any) => setCachedStateField("includeDiagnostics", e.target.checked)}
|
||||
data-testid="include-diagnostics-checkbox">
|
||||
<label className="block font-medium mb-1">
|
||||
{t("settings:diagnostics.includeDiagnostics.label")}
|
||||
</label>
|
||||
</VSCodeCheckbox>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1 mb-3">
|
||||
{t("settings:diagnostics.includeDiagnostics.description")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{includeDiagnostics && (
|
||||
<div className="flex flex-col gap-4 pl-3 border-l-2 border-vscode-button-background">
|
||||
<div>
|
||||
<span className="block font-medium mb-1">
|
||||
{t("settings:diagnostics.maxDiagnosticsCount.label")}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
min={0}
|
||||
max={200}
|
||||
step={1}
|
||||
value={[maxDiagnosticsCount ?? 50]}
|
||||
onValueChange={([value]) => setCachedStateField("maxDiagnosticsCount", value)}
|
||||
data-testid="max-diagnostics-count-slider"
|
||||
/>
|
||||
<span className="w-10">{maxDiagnosticsCount ?? 50}</span>
|
||||
</div>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:diagnostics.maxDiagnosticsCount.description")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="block font-medium mb-1">
|
||||
{t("settings:diagnostics.diagnosticsFilter.label")}
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="w-full bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border px-2 py-1 rounded"
|
||||
value={diagnosticsFilter?.join(", ") || ""}
|
||||
onChange={(e) => handleFilterChange(e.target.value)}
|
||||
placeholder="e.g., dart Error, eslint/no-unused-vars"
|
||||
data-testid="diagnostics-filter-input"
|
||||
/>
|
||||
<div className="text-vscode-descriptionForeground text-sm mt-1">
|
||||
{t("settings:diagnostics.diagnosticsFilter.description")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -118,6 +118,12 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
autoCondenseContextPercent: number
|
||||
setAutoCondenseContextPercent: (value: number) => void
|
||||
routerModels?: RouterModels
|
||||
includeDiagnostics: boolean
|
||||
setIncludeDiagnostics: (value: boolean) => void
|
||||
maxDiagnosticsCount: number
|
||||
setMaxDiagnosticsCount: (value: number) => void
|
||||
diagnosticsFilter: string[]
|
||||
setDiagnosticsFilter: (value: string[]) => void
|
||||
}
|
||||
|
||||
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
|
|
@ -206,6 +212,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
codebaseIndexEmbedderModelId: "",
|
||||
},
|
||||
codebaseIndexModels: { ollama: {}, openai: {} },
|
||||
includeDiagnostics: false,
|
||||
maxDiagnosticsCount: 5,
|
||||
diagnosticsFilter: ["error", "warning"],
|
||||
})
|
||||
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
|
|
@ -403,6 +412,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
setCondensingApiConfigId: (value) => setState((prevState) => ({ ...prevState, condensingApiConfigId: value })),
|
||||
setCustomCondensingPrompt: (value) =>
|
||||
setState((prevState) => ({ ...prevState, customCondensingPrompt: value })),
|
||||
setIncludeDiagnostics: (value) => setState((prevState) => ({ ...prevState, includeDiagnostics: value })),
|
||||
setMaxDiagnosticsCount: (value) => setState((prevState) => ({ ...prevState, maxDiagnosticsCount: value })),
|
||||
setDiagnosticsFilter: (value) => setState((prevState) => ({ ...prevState, diagnosticsFilter: value })),
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
|
|
|||
|
|
@ -209,6 +209,9 @@ describe("mergeExtensionState", () => {
|
|||
autoCondenseContextPercent: 100,
|
||||
cloudIsAuthenticated: false,
|
||||
sharingEnabled: false,
|
||||
includeDiagnostics: false,
|
||||
maxDiagnosticsCount: 5,
|
||||
diagnosticsFilter: ["error", "warning"],
|
||||
}
|
||||
|
||||
const prevState: ExtensionState = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue