Skip to content
Closed
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
20 changes: 13 additions & 7 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%"
Expand Down
1 change: 1 addition & 0 deletions src/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 156 additions & 0 deletions src/services/code-index/__tests__/file-watching-pause.spec.ts
Original file line number Diff line number Diff line change
@@ -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")
})
})
})
17 changes: 17 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,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
}
}
}
2 changes: 1 addition & 1 deletion src/services/code-index/interfaces/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 12 additions & 4 deletions src/services/code-index/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 34 additions & 17 deletions src/services/code-index/orchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}.`,
)
Expand Down Expand Up @@ -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.",
)
Comment on lines +206 to +209
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This message uses a hardcoded string while the existing code pattern (e.g., line 202) uses the t() function for i18n translations. For consistency and to support non-English locales, consider adding translation keys for these new messages.

Fix it with Roo Code or mention @roomote and request a fix.

}
} else {
// No existing data or collection was just created - do a full scan
this.stateManager.setSystemState("Indexing", "Services ready. Starting workspace scan...")
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 6 additions & 4 deletions src/services/code-index/state-manager.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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())
Expand Down
Loading