Skip to content
Open
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
21 changes: 21 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { selectImages } from "../../integrations/misc/process-images"
import { getTheme } from "../../integrations/theme/getTheme"
import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery"
import { searchWorkspaceFiles } from "../../services/search/file-search"
import { loadRoogitincludePatterns } from "../../services/glob/list-files"
import { fileExistsAtPath } from "../../utils/fs"
import { playTts, setTtsEnabled, setTtsSpeed, stopTts } from "../../utils/tts"
import { searchCommits } from "../../utils/git"
Expand Down Expand Up @@ -1701,11 +1702,31 @@ export const webviewMessageHandler = async (
break
}
try {
// Get mentions.respectGitignore setting
const respectGitignore = vscode.workspace
.getConfiguration(Package.name)
.get<boolean>("mentions.respectGitignore", false)

// Load .roogitinclude patterns
const roogitincludePatterns = await loadRoogitincludePatterns(workspacePath)

// Get codeIndex.includePatterns setting
const settingsIncludePatterns = vscode.workspace
.getConfiguration(Package.name)
.get<string[]>("codeIndex.includePatterns", [])

// Combine both sources: .roogitinclude + settings includePatterns
const allIncludePatterns = [...roogitincludePatterns, ...settingsIncludePatterns]

// Call file search service with query from message
const results = await searchWorkspaceFiles(
message.query || "",
workspacePath,
20, // Use default limit, as filtering is now done in the backend
{
respectGitignore,
includePatterns: allIncludePatterns,
},
)

// Send results back to webview
Expand Down
18 changes: 18 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,24 @@
"maximum": 200,
"description": "%settings.codeIndex.embeddingBatchSize.description%"
},
"roo-cline.codeIndex.respectGitignore": {
"type": "boolean",
"default": true,
"markdownDescription": "Whether code indexing should respect `.gitignore` files. When disabled, all files except those in `.rooignore` will be indexed."
},
"roo-cline.codeIndex.includePatterns": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"markdownDescription": "Glob patterns (e.g., `generated/**`, `dist/types/**`) to include in code indexing even if they match `.gitignore`. These patterns override `.gitignore` exclusions."
},
"roo-cline.mentions.respectGitignore": {
"type": "boolean",
"default": false,
"markdownDescription": "Whether `@filename` mentions should respect `.gitignore` files. When enabled, gitignored files won't appear in mention suggestions (unless overridden by `.roogitinclude` or `includePatterns`)."
},
"roo-cline.debug": {
"type": "boolean",
"default": false,
Expand Down
12 changes: 12 additions & 0 deletions src/services/code-index/__tests__/manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
qdrantApiKey: "test-key",
searchMinScore: 0.4,
}),
getCodeIndexRespectGitignore: vi.fn().mockReturnValue(true),
getCodeIndexIncludePatterns: vi.fn().mockReturnValue([]),
getMentionsRespectGitignore: vi.fn().mockReturnValue(false),
}
;(manager as any)._configManager = mockConfigManager

Expand Down Expand Up @@ -211,6 +214,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
qdrantApiKey: "test-key",
searchMinScore: 0.4,
}),
getCodeIndexRespectGitignore: vi.fn().mockReturnValue(true),
getCodeIndexIncludePatterns: vi.fn().mockReturnValue([]),
getMentionsRespectGitignore: vi.fn().mockReturnValue(false),
}
;(manager as any)._configManager = mockConfigManager

Expand Down Expand Up @@ -337,6 +343,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
qdrantApiKey: "test-key",
searchMinScore: 0.4,
}),
getCodeIndexRespectGitignore: vitest.fn().mockReturnValue(true),
getCodeIndexIncludePatterns: vitest.fn().mockReturnValue([]),
getMentionsRespectGitignore: vitest.fn().mockReturnValue(false),
}
;(manager as any)._configManager = mockConfigManager
})
Expand Down Expand Up @@ -434,6 +443,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
qdrantApiKey: "test-key",
searchMinScore: 0.4,
}),
getCodeIndexRespectGitignore: vi.fn().mockReturnValue(true),
getCodeIndexIncludePatterns: vi.fn().mockReturnValue([]),
getMentionsRespectGitignore: vi.fn().mockReturnValue(false),
}
;(manager as any)._configManager = mockConfigManager

Expand Down
35 changes: 35 additions & 0 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as vscode from "vscode"
import { ApiHandlerOptions } from "../../shared/api"
import { ContextProxy } from "../../core/config/ContextProxy"
import { EmbedderProvider } from "./interfaces/manager"
import { CodeIndexConfig, PreviousConfigSnapshot } from "./interfaces/config"
import { DEFAULT_SEARCH_MIN_SCORE, DEFAULT_MAX_SEARCH_RESULTS } from "./constants"
import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "../../shared/embeddingModels"
import { Package } from "../../shared/package"

/**
* Manages configuration state and validation for the code indexing feature.
Expand Down Expand Up @@ -541,4 +543,37 @@ export class CodeIndexConfigManager {
public get currentSearchMaxResults(): number {
return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS
}

/**
* Gets whether code indexing should respect .gitignore files
*/
public getCodeIndexRespectGitignore(): boolean {
try {
return vscode.workspace.getConfiguration(Package.name).get<boolean>("codeIndex.respectGitignore", true)
} catch {
return true // Default to respecting gitignore
}
}

/**
* Gets glob patterns to include in code indexing even if gitignored
*/
public getCodeIndexIncludePatterns(): string[] {
try {
return vscode.workspace.getConfiguration(Package.name).get<string[]>("codeIndex.includePatterns", [])
} catch {
return [] // Default to no include patterns
}
}

/**
* Gets whether mentions should respect .gitignore files
*/
public getMentionsRespectGitignore(): boolean {
try {
return vscode.workspace.getConfiguration(Package.name).get<boolean>("mentions.respectGitignore", false)
} catch {
return false // Default to not respecting gitignore for mentions
}
}
}
43 changes: 28 additions & 15 deletions src/services/code-index/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import path from "path"
import { t } from "../../i18n"
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"
import { loadRoogitincludePatterns } from "../glob/list-files"

export class CodeIndexManager {
// --- Singleton Implementation ---
Expand Down Expand Up @@ -312,20 +313,31 @@ export class CodeIndexManager {
return
}

// Create .gitignore instance
const ignorePath = path.join(workspacePath, ".gitignore")
try {
const content = await fs.readFile(ignorePath, "utf8")
ignoreInstance.add(content)
ignoreInstance.add(".gitignore")
} catch (error) {
// Should never happen: reading file failed even though it exists
console.error("Unexpected error loading .gitignore:", error)
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
location: "_recreateServices",
})
// Load .roogitinclude patterns
const roogitincludePatterns = await loadRoogitincludePatterns(workspacePath)

// Load settings-based include patterns
const settingsIncludePatterns = this._configManager!.getCodeIndexIncludePatterns()

// Combine both sources
const allIncludePatterns = [...roogitincludePatterns, ...settingsIncludePatterns]

// Check respectGitignore setting
const respectGitignore = this._configManager!.getCodeIndexRespectGitignore()

// Create .gitignore instance (if respecting gitignore)
let gitignoreInstance: ReturnType<typeof ignore> | undefined
if (respectGitignore) {
const ignorePath = path.join(workspacePath, ".gitignore")
try {
const content = await fs.readFile(ignorePath, "utf8")
ignoreInstance.add(content)
ignoreInstance.add(".gitignore")
gitignoreInstance = ignoreInstance
} catch (error) {
// .gitignore doesn't exist or can't be read - continue without it
console.warn("Could not load .gitignore:", error)
}
}

// Create RooIgnoreController instance
Expand All @@ -336,8 +348,9 @@ export class CodeIndexManager {
const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices(
this.context,
this._cacheManager!,
ignoreInstance,
gitignoreInstance ?? ignore(), // Pass empty ignore instance if not respecting gitignore
rooIgnoreController,
allIncludePatterns,
)

// Validate embedder configuration before proceeding
Expand Down
25 changes: 20 additions & 5 deletions src/services/code-index/processors/file-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"
import { sanitizeErrorMessage } from "../shared/validation-helpers"
import { Package } from "../../../shared/package"
import { matchesIncludePatterns } from "../../glob/list-files"

/**
* Implementation of the file watcher interface
Expand All @@ -40,6 +41,7 @@ export class FileWatcher implements IFileWatcher {
private readonly BATCH_DEBOUNCE_DELAY_MS = 500
private readonly FILE_PROCESSING_CONCURRENCY_LIMIT = 10
private readonly batchSegmentThreshold: number
private readonly includePatterns: string[]

private readonly _onDidStartBatchProcessing = new vscode.EventEmitter<string[]>()
private readonly _onBatchProgressUpdate = new vscode.EventEmitter<{
Expand Down Expand Up @@ -81,6 +83,7 @@ export class FileWatcher implements IFileWatcher {
ignoreInstance?: Ignore,
ignoreController?: RooIgnoreController,
batchSegmentThreshold?: number,
includePatterns?: string[],
) {
this.ignoreController = ignoreController || new RooIgnoreController(workspacePath)
if (ignoreInstance) {
Expand All @@ -100,6 +103,7 @@ export class FileWatcher implements IFileWatcher {
this.batchSegmentThreshold = BATCH_SEGMENT_THRESHOLD
}
}
this.includePatterns = includePatterns || []
}

/**
Expand Down Expand Up @@ -519,14 +523,25 @@ export class FileWatcher implements IFileWatcher {

// Check if file should be ignored
const relativeFilePath = generateRelativeFilePath(filePath, this.workspacePath)
if (
!this.ignoreController.validateAccess(filePath) ||
(this.ignoreInstance && this.ignoreInstance.ignores(relativeFilePath))
) {

// Priority 1: .rooignore - always excluded (cannot be overridden)
if (!this.ignoreController.validateAccess(filePath)) {
return {
path: filePath,
status: "skipped" as const,
reason: "File is ignored by .rooignore",
}
}

// Priority 2: .roogitinclude + includePatterns - force include (overrides gitignore)
const shouldInclude = matchesIncludePatterns(relativeFilePath, this.includePatterns)

// Priority 3: .gitignore - exclude if not included by patterns
if (!shouldInclude && this.ignoreInstance && this.ignoreInstance.ignores(relativeFilePath)) {
return {
path: filePath,
status: "skipped" as const,
reason: "File is ignored by .rooignore or .gitignore",
reason: "File is ignored by .gitignore",
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/services/code-index/processors/scanner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { listFiles } from "../../glob/list-files"
import { listFiles, matchesIncludePatterns } from "../../glob/list-files"
import { Ignore } from "ignore"
import { RooIgnoreController } from "../../../core/ignore/RooIgnoreController"
import { stat } from "fs/promises"
Expand Down Expand Up @@ -33,6 +33,7 @@ import { Package } from "../../../shared/package"

export class DirectoryScanner implements IDirectoryScanner {
private readonly batchSegmentThreshold: number
private readonly includePatterns: string[]

constructor(
private readonly embedder: IEmbedder,
Expand All @@ -41,6 +42,7 @@ export class DirectoryScanner implements IDirectoryScanner {
private readonly cacheManager: CacheManager,
private readonly ignoreInstance: Ignore,
batchSegmentThreshold?: number,
includePatterns?: string[],
) {
// Get the configurable batch size from VSCode settings, fallback to default
// If not provided in constructor, try to get from VSCode settings
Expand All @@ -56,6 +58,7 @@ export class DirectoryScanner implements IDirectoryScanner {
this.batchSegmentThreshold = BATCH_SEGMENT_THRESHOLD
}
}
this.includePatterns = includePatterns || []
}

/**
Expand Down Expand Up @@ -100,7 +103,12 @@ export class DirectoryScanner implements IDirectoryScanner {
return false
}

return scannerExtensions.includes(ext) && !this.ignoreInstance.ignores(relativeFilePath)
// Check if file matches include patterns (overrides gitignore)
const shouldInclude = matchesIncludePatterns(relativeFilePath, this.includePatterns)
const isGitIgnored = this.ignoreInstance.ignores(relativeFilePath)

// Include if: matches include patterns OR (not gitignored AND supported extension)
return scannerExtensions.includes(ext) && (shouldInclude || !isGitIgnored)
})

// Initialize tracking variables
Expand Down
17 changes: 15 additions & 2 deletions src/services/code-index/service-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export class CodeIndexServiceFactory {
vectorStore: IVectorStore,
parser: ICodeParser,
ignoreInstance: Ignore,
includePatterns?: string[],
): DirectoryScanner {
// Get the configurable batch size from VSCode settings
let batchSize: number
Expand All @@ -186,7 +187,15 @@ export class CodeIndexServiceFactory {
// In test environment, vscode.workspace might not be available
batchSize = BATCH_SEGMENT_THRESHOLD
}
return new DirectoryScanner(embedder, vectorStore, parser, this.cacheManager, ignoreInstance, batchSize)
return new DirectoryScanner(
embedder,
vectorStore,
parser,
this.cacheManager,
ignoreInstance,
batchSize,
includePatterns,
)
}

/**
Expand All @@ -199,6 +208,7 @@ export class CodeIndexServiceFactory {
cacheManager: CacheManager,
ignoreInstance: Ignore,
rooIgnoreController?: RooIgnoreController,
includePatterns?: string[],
): IFileWatcher {
// Get the configurable batch size from VSCode settings
let batchSize: number
Expand All @@ -219,6 +229,7 @@ export class CodeIndexServiceFactory {
ignoreInstance,
rooIgnoreController,
batchSize,
includePatterns,
)
}

Expand All @@ -231,6 +242,7 @@ export class CodeIndexServiceFactory {
cacheManager: CacheManager,
ignoreInstance: Ignore,
rooIgnoreController?: RooIgnoreController,
includePatterns?: string[],
): {
embedder: IEmbedder
vectorStore: IVectorStore
Expand All @@ -245,14 +257,15 @@ export class CodeIndexServiceFactory {
const embedder = this.createEmbedder()
const vectorStore = this.createVectorStore()
const parser = codeParser
const scanner = this.createDirectoryScanner(embedder, vectorStore, parser, ignoreInstance)
const scanner = this.createDirectoryScanner(embedder, vectorStore, parser, ignoreInstance, includePatterns)
const fileWatcher = this.createFileWatcher(
context,
embedder,
vectorStore,
cacheManager,
ignoreInstance,
rooIgnoreController,
includePatterns,
)

return {
Expand Down
Loading
Loading