mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
feat: add option to pause file-watching after initial indexing
This addresses Issue #8131 by adding a workspace-scoped setting roo-cline.codeIndex.fileWatchingEnabled that allows users to: - Enable initial indexing for codebase_search functionality - Pause file-watching to reduce CPU usage (especially useful with git worktrees) - Each workspace/worktree maintains independent settings Changes: - Add fileWatchingEnabled setting in package.json (scope: resource) - Add IndexedPaused state to the indexing state machine - Update ConfigManager with isFileWatchingEnabled getter - Modify orchestrator to conditionally start/stop file watcher - Add comprehensive tests for the new functionality
This commit is contained in:
parent
1459f0113b
commit
29b437a775
8 changed files with 240 additions and 33 deletions
|
|
@ -401,13 +401,19 @@
|
|||
"description": "%settings.newTaskRequireTodos.description%"
|
||||
},
|
||||
"roo-cline.codeIndex.embeddingBatchSize": {
|
||||
"type": "number",
|
||||
"default": 60,
|
||||
"minimum": 1,
|
||||
"maximum": 200,
|
||||
"description": "%settings.codeIndex.embeddingBatchSize.description%"
|
||||
},
|
||||
"roo-cline.debug": {
|
||||
"type": "number",
|
||||
"default": 60,
|
||||
"minimum": 1,
|
||||
"maximum": 200,
|
||||
"description": "%settings.codeIndex.embeddingBatchSize.description%"
|
||||
},
|
||||
"roo-cline.codeIndex.fileWatchingEnabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"scope": "resource",
|
||||
"description": "%settings.codeIndex.fileWatchingEnabled.description%"
|
||||
},
|
||||
"roo-cline.debug": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%settings.debug.description%"
|
||||
|
|
|
|||
|
|
@ -42,5 +42,6 @@
|
|||
"settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.",
|
||||
"settings.newTaskRequireTodos.description": "Require todos parameter when creating new tasks with the new_task tool",
|
||||
"settings.codeIndex.embeddingBatchSize.description": "The batch size for embedding operations during code indexing. Adjust this based on your API provider's limits. Default is 60.",
|
||||
"settings.codeIndex.fileWatchingEnabled.description": "Enable file watching to automatically update the index when files change. Disable to reduce CPU usage after initial indexing. When disabled, use 'Re-Index' to manually update. (Workspace setting)",
|
||||
"settings.debug.description": "Enable debug mode to show additional buttons for viewing API conversation history and UI messages as prettified JSON in temporary files."
|
||||
}
|
||||
|
|
|
|||
156
src/services/code-index/__tests__/file-watching-pause.spec.ts
Normal file
156
src/services/code-index/__tests__/file-watching-pause.spec.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// npx vitest services/code-index/__tests__/file-watching-pause.spec.ts
|
||||
|
||||
import { CodeIndexConfigManager } from "../config-manager"
|
||||
import { CodeIndexStateManager } from "../state-manager"
|
||||
|
||||
// Mock vscode
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
getConfiguration: vi.fn().mockImplementation(() => ({
|
||||
get: vi.fn().mockReturnValue(true),
|
||||
})),
|
||||
workspaceFolders: [{ uri: { fsPath: "/test/workspace" } }],
|
||||
},
|
||||
EventEmitter: vi.fn().mockImplementation(() => ({
|
||||
event: vi.fn(),
|
||||
fire: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
// Mock ContextProxy
|
||||
vi.mock("../../../core/config/ContextProxy")
|
||||
|
||||
// Mock embeddingModels module
|
||||
vi.mock("../../../shared/embeddingModels")
|
||||
|
||||
// Mock Package
|
||||
vi.mock("../../../shared/package", () => ({
|
||||
Package: {
|
||||
name: "roo-cline",
|
||||
},
|
||||
}))
|
||||
|
||||
import * as vscode from "vscode"
|
||||
|
||||
describe("File Watching Pause Feature", () => {
|
||||
let mockContextProxy: any
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
mockContextProxy = {
|
||||
getGlobalState: vi.fn(),
|
||||
getSecret: vi.fn().mockReturnValue(undefined),
|
||||
refreshSecrets: vi.fn().mockResolvedValue(undefined),
|
||||
updateGlobalState: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe("CodeIndexConfigManager.isFileWatchingEnabled", () => {
|
||||
it("should return true when file watching is enabled in workspace settings", () => {
|
||||
// Mock vscode.workspace.getConfiguration to return true
|
||||
const mockGetConfiguration = vi.mocked(vscode.workspace.getConfiguration)
|
||||
mockGetConfiguration.mockReturnValue({
|
||||
get: vi.fn().mockReturnValue(true),
|
||||
} as any)
|
||||
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
})
|
||||
|
||||
const configManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
expect(configManager.isFileWatchingEnabled).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false when file watching is disabled in workspace settings", () => {
|
||||
// Mock vscode.workspace.getConfiguration to return false
|
||||
const mockGetConfiguration = vi.mocked(vscode.workspace.getConfiguration)
|
||||
mockGetConfiguration.mockReturnValue({
|
||||
get: vi.fn().mockReturnValue(false),
|
||||
} as any)
|
||||
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
})
|
||||
|
||||
const configManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
expect(configManager.isFileWatchingEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it("should default to true when file watching setting is not set", () => {
|
||||
// Mock vscode.workspace.getConfiguration to return default value
|
||||
const mockGetConfiguration = vi.mocked(vscode.workspace.getConfiguration)
|
||||
mockGetConfiguration.mockReturnValue({
|
||||
get: vi.fn().mockImplementation((key: string, defaultValue: any) => defaultValue),
|
||||
} as any)
|
||||
|
||||
mockContextProxy.getGlobalState.mockReturnValue({
|
||||
codebaseIndexEnabled: true,
|
||||
})
|
||||
|
||||
const configManager = new CodeIndexConfigManager(mockContextProxy)
|
||||
expect(configManager.isFileWatchingEnabled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("CodeIndexStateManager IndexedPaused state", () => {
|
||||
it("should set state to IndexedPaused with default message", () => {
|
||||
const stateManager = new CodeIndexStateManager()
|
||||
|
||||
stateManager.setSystemState("IndexedPaused")
|
||||
|
||||
const status = stateManager.getCurrentStatus()
|
||||
expect(status.systemStatus).toBe("IndexedPaused")
|
||||
expect(status.message).toBe("Index ready. File watching paused.")
|
||||
})
|
||||
|
||||
it("should set state to IndexedPaused with custom message", () => {
|
||||
const stateManager = new CodeIndexStateManager()
|
||||
|
||||
stateManager.setSystemState("IndexedPaused", "Custom paused message")
|
||||
|
||||
const status = stateManager.getCurrentStatus()
|
||||
expect(status.systemStatus).toBe("IndexedPaused")
|
||||
expect(status.message).toBe("Custom paused message")
|
||||
})
|
||||
|
||||
it("should reset progress counters when transitioning to IndexedPaused", () => {
|
||||
const stateManager = new CodeIndexStateManager()
|
||||
|
||||
// First set to indexing with progress
|
||||
stateManager.reportBlockIndexingProgress(50, 100)
|
||||
|
||||
// Then transition to IndexedPaused
|
||||
stateManager.setSystemState("IndexedPaused")
|
||||
|
||||
const status = stateManager.getCurrentStatus()
|
||||
expect(status.processedItems).toBe(0)
|
||||
expect(status.totalItems).toBe(0)
|
||||
})
|
||||
|
||||
it("should allow transition from IndexedPaused to Indexing", () => {
|
||||
const stateManager = new CodeIndexStateManager()
|
||||
|
||||
// Start in IndexedPaused state
|
||||
stateManager.setSystemState("IndexedPaused")
|
||||
|
||||
// Then transition to Indexing (simulating re-index)
|
||||
stateManager.setSystemState("Indexing", "Re-indexing...")
|
||||
|
||||
const status = stateManager.getCurrentStatus()
|
||||
expect(status.systemStatus).toBe("Indexing")
|
||||
expect(status.message).toBe("Re-indexing...")
|
||||
})
|
||||
})
|
||||
|
||||
describe("IndexingState type includes IndexedPaused", () => {
|
||||
it("should accept IndexedPaused as valid state", () => {
|
||||
const stateManager = new CodeIndexStateManager()
|
||||
|
||||
// Should not throw
|
||||
stateManager.setSystemState("IndexedPaused")
|
||||
expect(stateManager.state).toBe("IndexedPaused")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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.
|
||||
|
|
@ -541,4 +543,19 @@ export class CodeIndexConfigManager {
|
|||
public get currentSearchMaxResults(): number {
|
||||
return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether file watching is enabled for the current workspace.
|
||||
* This is a workspace-scoped setting that allows users to disable file watching
|
||||
* to reduce CPU usage while still using the codebase search with an existing index.
|
||||
* Defaults to true (file watching enabled).
|
||||
*/
|
||||
public get isFileWatchingEnabled(): boolean {
|
||||
try {
|
||||
return vscode.workspace.getConfiguration(Package.name).get<boolean>("codeIndex.fileWatchingEnabled", true)
|
||||
} catch {
|
||||
// In test environment, vscode.workspace might not be available
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export interface ICodeIndexManager {
|
|||
dispose(): void
|
||||
}
|
||||
|
||||
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
|
||||
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "IndexedPaused" | "Error"
|
||||
export type EmbedderProvider =
|
||||
| "openai"
|
||||
| "ollama"
|
||||
|
|
|
|||
|
|
@ -98,10 +98,18 @@ export class CodeIndexManager {
|
|||
}
|
||||
|
||||
public get isFeatureConfigured(): boolean {
|
||||
return this._configManager?.isFeatureConfigured ?? false
|
||||
}
|
||||
|
||||
public get isInitialized(): boolean {
|
||||
return this._configManager?.isFeatureConfigured ?? false
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether file watching is enabled for the current workspace.
|
||||
* When disabled, the index can still be used for searches but won't auto-update.
|
||||
*/
|
||||
public get isFileWatchingEnabled(): boolean {
|
||||
return this._configManager?.isFileWatchingEnabled ?? true
|
||||
}
|
||||
|
||||
public get isInitialized(): boolean {
|
||||
try {
|
||||
this.assertInitialized()
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -109,11 +109,12 @@ export class CodeIndexOrchestrator {
|
|||
}
|
||||
|
||||
if (
|
||||
this._isProcessing ||
|
||||
(this.stateManager.state !== "Standby" &&
|
||||
this.stateManager.state !== "Error" &&
|
||||
this.stateManager.state !== "Indexed")
|
||||
) {
|
||||
this._isProcessing ||
|
||||
(this.stateManager.state !== "Standby" &&
|
||||
this.stateManager.state !== "Error" &&
|
||||
this.stateManager.state !== "Indexed" &&
|
||||
this.stateManager.state !== "IndexedPaused")
|
||||
) {
|
||||
console.warn(
|
||||
`[CodeIndexOrchestrator] Start rejected: Already processing or in state ${this.stateManager.state}.`,
|
||||
)
|
||||
|
|
@ -193,12 +194,20 @@ export class CodeIndexOrchestrator {
|
|||
console.log("[CodeIndexOrchestrator] No new or changed files found")
|
||||
}
|
||||
|
||||
await this._startWatcher()
|
||||
|
||||
// Mark indexing as complete after successful incremental scan
|
||||
await this.vectorStore.markIndexingComplete()
|
||||
|
||||
this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
|
||||
// Check if file watching is enabled before starting watcher
|
||||
if (this.configManager.isFileWatchingEnabled) {
|
||||
await this._startWatcher()
|
||||
// Mark indexing as complete after successful incremental scan
|
||||
await this.vectorStore.markIndexingComplete()
|
||||
this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
|
||||
} else {
|
||||
// Mark indexing as complete but skip file watching
|
||||
await this.vectorStore.markIndexingComplete()
|
||||
this.stateManager.setSystemState(
|
||||
"IndexedPaused",
|
||||
"Index ready. File watching disabled. Use 'Re-Index' to update.",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// No existing data or collection was just created - do a full scan
|
||||
this.stateManager.setSystemState("Indexing", "Services ready. Starting workspace scan...")
|
||||
|
|
@ -274,12 +283,20 @@ export class CodeIndexOrchestrator {
|
|||
throw new Error(t("embeddings:orchestrator.indexingFailedCritical"))
|
||||
}
|
||||
|
||||
await this._startWatcher()
|
||||
|
||||
// Mark indexing as complete after successful full scan
|
||||
await this.vectorStore.markIndexingComplete()
|
||||
|
||||
this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
|
||||
// Check if file watching is enabled before starting watcher
|
||||
if (this.configManager.isFileWatchingEnabled) {
|
||||
await this._startWatcher()
|
||||
// Mark indexing as complete after successful full scan
|
||||
await this.vectorStore.markIndexingComplete()
|
||||
this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted"))
|
||||
} else {
|
||||
// Mark indexing as complete but skip file watching
|
||||
await this.vectorStore.markIndexingComplete()
|
||||
this.stateManager.setSystemState(
|
||||
"IndexedPaused",
|
||||
"Index ready. File watching disabled. Use 'Re-Index' to update.",
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("[CodeIndexOrchestrator] Error during indexing:", error)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as vscode from "vscode"
|
||||
|
||||
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error"
|
||||
export type IndexingState = "Standby" | "Indexing" | "Indexed" | "IndexedPaused" | "Error"
|
||||
|
||||
export class CodeIndexStateManager {
|
||||
private _systemStatus: IndexingState = "Standby"
|
||||
|
|
@ -46,9 +46,11 @@ export class CodeIndexStateManager {
|
|||
this._totalItems = 0
|
||||
this._currentItemUnit = "blocks" // Reset to default unit
|
||||
// Optionally clear the message or set a default for non-indexing states
|
||||
if (newState === "Standby" && message === undefined) this._statusMessage = "Ready."
|
||||
if (newState === "Indexed" && message === undefined) this._statusMessage = "Index up-to-date."
|
||||
if (newState === "Error" && message === undefined) this._statusMessage = "An error occurred."
|
||||
if (newState === "Standby" && message === undefined) this._statusMessage = "Ready."
|
||||
if (newState === "Indexed" && message === undefined) this._statusMessage = "Index up-to-date."
|
||||
if (newState === "IndexedPaused" && message === undefined)
|
||||
this._statusMessage = "Index ready. File watching paused."
|
||||
if (newState === "Error" && message === undefined) this._statusMessage = "An error occurred."
|
||||
}
|
||||
|
||||
this._progressEmitter.fire(this.getCurrentStatus())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue