Skip to content

Commit 8d4c145

Browse files
committed
feat: add .rooindexignore support for selective code indexing
- Created RooIndexIgnoreController to handle .rooindexignore files - Updated DirectoryScanner to use RooIndexIgnoreController for filtering - Updated FileWatcher to check both .rooignore and .rooindexignore - Added comprehensive tests for RooIndexIgnoreController - Allows excluding files/folders from indexing while keeping them accessible Fixes #8570
1 parent eeaafef commit 8d4c145

File tree

5 files changed

+575
-10
lines changed

5 files changed

+575
-10
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import path from "path"
2+
import { fileExistsAtPath } from "../../utils/fs"
3+
import fs from "fs/promises"
4+
import fsSync from "fs"
5+
import ignore, { Ignore } from "ignore"
6+
import * as vscode from "vscode"
7+
8+
/**
9+
* Controls code indexing exclusions by enforcing ignore patterns from .rooindexignore.
10+
* This allows users to exclude files/folders from code indexing while still allowing
11+
* Roo to access them for other operations (unlike .rooignore which blocks all access).
12+
*
13+
* Designed to be used by the code indexing services (DirectoryScanner and FileWatcher).
14+
* Uses the 'ignore' library to support standard .gitignore syntax in .rooindexignore files.
15+
*/
16+
export class RooIndexIgnoreController {
17+
private cwd: string
18+
private ignoreInstance: Ignore
19+
private disposables: vscode.Disposable[] = []
20+
rooIndexIgnoreContent: string | undefined
21+
22+
constructor(cwd: string) {
23+
this.cwd = cwd
24+
this.ignoreInstance = ignore()
25+
this.rooIndexIgnoreContent = undefined
26+
// Set up file watcher for .rooindexignore
27+
this.setupFileWatcher()
28+
}
29+
30+
/**
31+
* Initialize the controller by loading custom patterns
32+
* Must be called after construction and before using the controller
33+
*/
34+
async initialize(): Promise<void> {
35+
await this.loadRooIndexIgnore()
36+
}
37+
38+
/**
39+
* Set up the file watcher for .rooindexignore changes
40+
*/
41+
private setupFileWatcher(): void {
42+
const rooindexignorePattern = new vscode.RelativePattern(this.cwd, ".rooindexignore")
43+
const fileWatcher = vscode.workspace.createFileSystemWatcher(rooindexignorePattern)
44+
45+
// Watch for changes and updates
46+
this.disposables.push(
47+
fileWatcher.onDidChange(() => {
48+
this.loadRooIndexIgnore()
49+
}),
50+
fileWatcher.onDidCreate(() => {
51+
this.loadRooIndexIgnore()
52+
}),
53+
fileWatcher.onDidDelete(() => {
54+
this.loadRooIndexIgnore()
55+
}),
56+
)
57+
58+
// Add fileWatcher itself to disposables
59+
this.disposables.push(fileWatcher)
60+
}
61+
62+
/**
63+
* Load custom patterns from .rooindexignore if it exists
64+
*/
65+
private async loadRooIndexIgnore(): Promise<void> {
66+
try {
67+
// Reset ignore instance to prevent duplicate patterns
68+
this.ignoreInstance = ignore()
69+
const ignorePath = path.join(this.cwd, ".rooindexignore")
70+
if (await fileExistsAtPath(ignorePath)) {
71+
const content = await fs.readFile(ignorePath, "utf8")
72+
this.rooIndexIgnoreContent = content
73+
this.ignoreInstance.add(content)
74+
// Note: We don't add .rooindexignore itself to the ignore list
75+
// as it's not typically something that would be indexed anyway
76+
} else {
77+
this.rooIndexIgnoreContent = undefined
78+
}
79+
} catch (error) {
80+
// Should never happen: reading file failed even though it exists
81+
console.error("Unexpected error loading .rooindexignore:", error)
82+
}
83+
}
84+
85+
/**
86+
* Check if a file should be included in code indexing
87+
* Automatically resolves symlinks
88+
* @param filePath - Path to check (relative to cwd)
89+
* @returns true if file should be indexed, false if ignored
90+
*/
91+
shouldIndex(filePath: string): boolean {
92+
// Always allow indexing if .rooindexignore does not exist
93+
if (!this.rooIndexIgnoreContent) {
94+
return true
95+
}
96+
try {
97+
const absolutePath = path.resolve(this.cwd, filePath)
98+
99+
// Follow symlinks to get the real path
100+
let realPath: string
101+
try {
102+
realPath = fsSync.realpathSync(absolutePath)
103+
} catch {
104+
// If realpath fails (file doesn't exist, broken symlink, etc.),
105+
// use the original path
106+
realPath = absolutePath
107+
}
108+
109+
// Convert real path to relative for .rooindexignore checking
110+
const relativePath = path.relative(this.cwd, realPath).toPosix()
111+
112+
// Check if the real path is ignored for indexing
113+
return !this.ignoreInstance.ignores(relativePath)
114+
} catch (error) {
115+
// Allow indexing on errors (backward compatibility)
116+
return true
117+
}
118+
}
119+
120+
/**
121+
* Filter an array of paths, removing those that should not be indexed
122+
* @param paths - Array of paths to filter (relative to cwd)
123+
* @returns Array of paths that should be indexed
124+
*/
125+
filterPaths(paths: string[]): string[] {
126+
try {
127+
return paths
128+
.map((p) => ({
129+
path: p,
130+
shouldIndex: this.shouldIndex(p),
131+
}))
132+
.filter((x) => x.shouldIndex)
133+
.map((x) => x.path)
134+
} catch (error) {
135+
console.error("Error filtering paths for indexing:", error)
136+
return paths // Return all paths on error (fail open for indexing)
137+
}
138+
}
139+
140+
/**
141+
* Clean up resources when the controller is no longer needed
142+
*/
143+
dispose(): void {
144+
this.disposables.forEach((d) => d.dispose())
145+
this.disposables = []
146+
}
147+
148+
/**
149+
* Check if .rooindexignore file exists
150+
* @returns true if .rooindexignore exists, false otherwise
151+
*/
152+
hasIndexIgnoreFile(): boolean {
153+
return this.rooIndexIgnoreContent !== undefined
154+
}
155+
}

0 commit comments

Comments
 (0)