Skip to content
Merged
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
11 changes: 10 additions & 1 deletion src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2223,11 +2223,13 @@ export class Cline {
this.consecutiveMistakeCount = 0
const absolutePath = path.resolve(cwd, relDirPath)
const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200)
const { showRooIgnoredFiles } = (await this.providerRef.deref()?.getState()) ?? {}
const result = formatResponse.formatFilesList(
absolutePath,
files,
didHitLimit,
this.rooIgnoreController,
showRooIgnoredFiles ?? true,
)
const completeMessage = JSON.stringify({
...sharedMessageProps,
Expand Down Expand Up @@ -3626,7 +3628,14 @@ export class Cline {
details += "(Desktop files not shown automatically. Use list_files to explore if needed.)"
} else {
const [files, didHitLimit] = await listFiles(cwd, true, 200)
const result = formatResponse.formatFilesList(cwd, files, didHitLimit, this.rooIgnoreController)
const { showRooIgnoredFiles } = (await this.providerRef.deref()?.getState()) ?? {}
const result = formatResponse.formatFilesList(
cwd,
files,
didHitLimit,
this.rooIgnoreController,
showRooIgnoredFiles,
)
details += result
}
}
Expand Down
54 changes: 52 additions & 2 deletions src/core/prompts/__tests__/responses-rooignore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe("RooIgnore Response Formatting", () => {
]

// Format with controller
const result = formatResponse.formatFilesList(TEST_CWD, files, false, controller as any)
const result = formatResponse.formatFilesList(TEST_CWD, files, false, controller as any, true)

// Should contain each file
expect(result).toContain("src/app.ts")
Expand All @@ -112,6 +112,55 @@ describe("RooIgnore Response Formatting", () => {
expect(result).not.toContain(`${LOCK_TEXT_SYMBOL} README.md`)
})

/**
* Tests formatFilesList when showRooIgnoredFiles is set to false
*/
it("should hide ignored files when showRooIgnoredFiles is false", async () => {
// Create controller
const controller = new RooIgnoreController(TEST_CWD)
await controller.initialize()

// Mock validateAccess to control which files are ignored
controller.validateAccess = jest.fn().mockImplementation((filePath: string) => {
// Only allow files not matching these patterns
return (
!filePath.includes("node_modules") && !filePath.includes(".git") && !filePath.includes("secrets/")
)
})

// Files list with mixed allowed/ignored files
const files = [
"src/app.ts", // allowed
"node_modules/package.json", // ignored
"README.md", // allowed
".git/HEAD", // ignored
"secrets/keys.json", // ignored
]

// Format with controller and showRooIgnoredFiles = false
const result = formatResponse.formatFilesList(
TEST_CWD,
files,
false,
controller as any,
false, // showRooIgnoredFiles = false
)

// Should contain allowed files
expect(result).toContain("src/app.ts")
expect(result).toContain("README.md")

// Should NOT contain ignored files (even with lock symbols)
expect(result).not.toContain("node_modules/package.json")
expect(result).not.toContain(".git/HEAD")
expect(result).not.toContain("secrets/keys.json")

// Double-check with regex to ensure no form of these filenames appears
expect(result).not.toMatch(/node_modules\/package\.json/i)
expect(result).not.toMatch(/\.git\/HEAD/i)
expect(result).not.toMatch(/secrets\/keys\.json/i)
})

/**
* Tests formatFilesList handles truncation correctly with RooIgnoreController
*/
Expand All @@ -126,6 +175,7 @@ describe("RooIgnore Response Formatting", () => {
["file1.txt", "file2.txt"],
true, // didHitLimit = true
controller as any,
true,
)

// Should contain truncation message (case-insensitive check)
Expand All @@ -142,7 +192,7 @@ describe("RooIgnore Response Formatting", () => {
await controller.initialize()

// Format with empty files array
const result = formatResponse.formatFilesList(TEST_CWD, [], false, controller as any)
const result = formatResponse.formatFilesList(TEST_CWD, [], false, controller as any, true)

// Should show "No files found"
expect(result).toBe("No files found.")
Expand Down
38 changes: 24 additions & 14 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ Otherwise, if you have not completed the task and do not need additional informa
absolutePath: string,
files: string[],
didHitLimit: boolean,
rooIgnoreController?: RooIgnoreController,
rooIgnoreController: RooIgnoreController | undefined,
showRooIgnoredFiles: boolean,
): string => {
const sorted = files
.map((file) => {
Expand Down Expand Up @@ -90,20 +91,29 @@ Otherwise, if you have not completed the task and do not need additional informa
return aParts.length - bParts.length
})

const rooIgnoreParsed = rooIgnoreController
? sorted.map((filePath) => {
// path is relative to absolute path, not cwd
// validateAccess expects either path relative to cwd or absolute path
// otherwise, for validating against ignore patterns like "assets/icons", we would end up with just "icons", which would result in the path not being ignored.
const absoluteFilePath = path.resolve(absolutePath, filePath)
const isIgnored = !rooIgnoreController.validateAccess(absoluteFilePath)
if (isIgnored) {
return LOCK_TEXT_SYMBOL + " " + filePath
let rooIgnoreParsed: string[] = sorted

if (rooIgnoreController) {
rooIgnoreParsed = []
for (const filePath of sorted) {
// path is relative to absolute path, not cwd
// validateAccess expects either path relative to cwd or absolute path
// otherwise, for validating against ignore patterns like "assets/icons", we would end up with just "icons", which would result in the path not being ignored.
const absoluteFilePath = path.resolve(absolutePath, filePath)
const isIgnored = !rooIgnoreController.validateAccess(absoluteFilePath)

if (isIgnored) {
// If file is ignored and we're not showing ignored files, skip it
if (!showRooIgnoredFiles) {
continue
}

return filePath
})
: sorted
// Otherwise, mark it with a lock symbol
rooIgnoreParsed.push(LOCK_TEXT_SYMBOL + " " + filePath)
} else {
rooIgnoreParsed.push(filePath)
}
}
}
if (didHitLimit) {
return `${rooIgnoreParsed.join(
"\n",
Expand Down
7 changes: 7 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
await this.updateGlobalState("browserToolEnabled", message.bool ?? true)
await this.postStateToWebview()
break
case "showRooIgnoredFiles":
await this.updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
await this.postStateToWebview()
break
case "enhancementApiConfigId":
await this.updateGlobalState("enhancementApiConfigId", message.text)
await this.postStateToWebview()
Expand Down Expand Up @@ -2201,6 +2205,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
maxOpenTabsContext,
browserToolEnabled,
telemetrySetting,
showRooIgnoredFiles,
} = await this.getState()
const telemetryKey = process.env.POSTHOG_API_KEY
const machineId = vscode.env.machineId
Expand Down Expand Up @@ -2262,6 +2267,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
telemetrySetting,
telemetryKey,
machineId,
showRooIgnoredFiles: showRooIgnoredFiles ?? true,
}
}

Expand Down Expand Up @@ -2441,6 +2447,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true,
browserToolEnabled: stateValues.browserToolEnabled ?? true,
telemetrySetting: stateValues.telemetrySetting || "unset",
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/core/webview/__tests__/ClineProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ describe("ClineProvider", () => {
maxOpenTabsContext: 20,
browserToolEnabled: true,
telemetrySetting: "unset",
showRooIgnoredFiles: true,
}

const message: ExtensionMessage = {
Expand Down Expand Up @@ -703,6 +704,27 @@ describe("ClineProvider", () => {
expect(state.browserToolEnabled).toBe(true) // Default value should be true
})

test("handles showRooIgnoredFiles setting", async () => {
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]

// Test showRooIgnoredFiles with true
await messageHandler({ type: "showRooIgnoredFiles", bool: true })
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", true)
expect(mockPostMessage).toHaveBeenCalled()

// Test showRooIgnoredFiles with false
jest.clearAllMocks() // Clear all mocks including mockContext.globalState.update
await messageHandler({ type: "showRooIgnoredFiles", bool: false })
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", false)
expect(mockPostMessage).toHaveBeenCalled()

// Verify state includes showRooIgnoredFiles
const state = await provider.getState()
expect(state).toHaveProperty("showRooIgnoredFiles")
expect(state.showRooIgnoredFiles).toBe(true) // Default value should be true
})

test("handles request delay settings messages", async () => {
await provider.resolveWebviewView(mockWebviewView)
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export interface ExtensionState {
telemetrySetting: TelemetrySetting
telemetryKey?: string
machineId?: string
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
}

export interface ClineMessage {
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export interface WebviewMessage {
| "humanRelayCancel"
| "browserToolEnabled"
| "telemetrySetting"
| "showRooIgnoredFiles"
text?: string
disabled?: boolean
askResponse?: ClineAskResponse
Expand Down
1 change: 1 addition & 0 deletions src/shared/globalState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const GLOBAL_STATE_KEYS = [
"lmStudioSpeculativeDecodingEnabled",
"lmStudioDraftModelId",
"telemetrySetting",
"showRooIgnoredFiles",
] as const

// Derive the type from the array - creates a union of string literals
Expand Down
24 changes: 22 additions & 2 deletions webview-ui/src/components/settings/AdvancedSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,25 @@ type AdvancedSettingsProps = HTMLAttributes<HTMLDivElement> & {
maxOpenTabsContext: number
diffEnabled?: boolean
fuzzyMatchThreshold?: number
showRooIgnoredFiles?: boolean
setCachedStateField: SetCachedStateField<
"rateLimitSeconds" | "terminalOutputLimit" | "maxOpenTabsContext" | "diffEnabled" | "fuzzyMatchThreshold"
| "rateLimitSeconds"
| "terminalOutputLimit"
| "maxOpenTabsContext"
| "diffEnabled"
| "fuzzyMatchThreshold"
| "showRooIgnoredFiles"
>
experiments: Record<ExperimentId, boolean>
setExperimentEnabled: SetExperimentEnabled
}

export const AdvancedSettings = ({
rateLimitSeconds,
terminalOutputLimit = TERMINAL_OUTPUT_LIMIT,
maxOpenTabsContext,
diffEnabled,
fuzzyMatchThreshold,
showRooIgnoredFiles,
setCachedStateField,
experiments,
setExperimentEnabled,
Expand Down Expand Up @@ -197,6 +203,20 @@ export const AdvancedSettings = ({
</div>
)}
</div>

<div>
<VSCodeCheckbox
checked={showRooIgnoredFiles}
onChange={(e: any) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider replacing the use of any for the event parameter with a proper type (e.g. React.ChangeEvent<HTMLInputElement>) for better type safety.

Suggested change
onChange={(e: any) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {

setCachedStateField("showRooIgnoredFiles", e.target.checked)
}}>
<span className="font-medium">Show .rooignore'd files in lists and searches</span>
</VSCodeCheckbox>
<p className="text-vscode-descriptionForeground text-sm mt-0">
When enabled, files matching patterns in .rooignore will be shown in lists with a lock symbol.
When disabled, these files will be completely hidden from file lists and searches.
</p>
</div>
</Section>
</div>
)
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
telemetrySetting,
terminalOutputLimit,
writeDelayMs,
showRooIgnoredFiles,
} = cachedState

// Make sure apiConfiguration is initialized and managed by SettingsView.
Expand Down Expand Up @@ -179,6 +180,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
vscode.postMessage({ type: "rateLimitSeconds", value: rateLimitSeconds })
vscode.postMessage({ type: "maxOpenTabsContext", value: maxOpenTabsContext })
vscode.postMessage({ type: "showRooIgnoredFiles", bool: showRooIgnoredFiles })
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
vscode.postMessage({ type: "updateExperimental", values: experiments })
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
Expand Down Expand Up @@ -400,6 +402,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone },
maxOpenTabsContext={maxOpenTabsContext}
diffEnabled={diffEnabled}
fuzzyMatchThreshold={fuzzyMatchThreshold}
showRooIgnoredFiles={showRooIgnoredFiles}
setCachedStateField={setCachedStateField}
setExperimentEnabled={setExperimentEnabled}
experiments={experiments}
Expand Down
3 changes: 3 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ExtensionStateContextType extends ExtensionState {
setAlwaysAllowMcp: (value: boolean) => void
setAlwaysAllowModeSwitch: (value: boolean) => void
setBrowserToolEnabled: (value: boolean) => void
setShowRooIgnoredFiles: (value: boolean) => void
setShowAnnouncement: (value: boolean) => void
setAllowedCommands: (value: string[]) => void
setSoundEnabled: (value: boolean) => void
Expand Down Expand Up @@ -138,6 +139,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
cwd: "",
browserToolEnabled: true,
telemetrySetting: "unset",
showRooIgnoredFiles: true, // Default to showing .rooignore'd files with lock symbol (current behavior)
})

const [didHydrateState, setDidHydrateState] = useState(false)
Expand Down Expand Up @@ -276,6 +278,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })),
setBrowserToolEnabled: (value) => setState((prevState) => ({ ...prevState, browserToolEnabled: value })),
setTelemetrySetting: (value) => setState((prevState) => ({ ...prevState, telemetrySetting: value })),
setShowRooIgnoredFiles: (value) => setState((prevState) => ({ ...prevState, showRooIgnoredFiles: value })),
}

return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
Expand Down
Loading