Skip to content
Open
9 changes: 9 additions & 0 deletions .changeset/indexing-workspace-opt-in-and-stop-control.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"roo-cline": minor
---

Add per-workspace indexing opt-in and stop/cancel indexing controls

- **Per-workspace indexing opt-in**: Indexing no longer auto-starts on every workspace. A new `codeIndexWorkspaceEnabled` flag (stored in `workspaceState`, default: false) requires users to explicitly enable indexing per workspace via a toggle in the CodeIndex popover. The choice is remembered across sessions.
- **Stop/cancel indexing**: Users can stop an in-progress indexing operation via a "Stop Indexing" button. Uses `AbortController`/`AbortSignal` threaded through the orchestrator → scanner pipeline with graceful abort at file and batch boundaries.
- **Disable toggle bug fix**: Unchecking "Enable Codebase Indexing" during active indexing now properly stops the scan via `stopIndexing()` instead of only calling `stopWatcher()`, which left the scanner running asynchronously.
7 changes: 6 additions & 1 deletion packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,12 @@ export interface WebviewMessage {
| "condenseTaskContextRequest"
| "requestIndexingStatus"
| "startIndexing"
| "stopIndexing"
| "clearIndexData"
| "indexingStatusUpdate"
| "indexCleared"
| "toggleWorkspaceIndexing"
| "setAutoEnableDefault"
| "focusPanelRequest"
| "openExternal"
| "filterMarketplaceItems"
Expand Down Expand Up @@ -704,7 +707,7 @@ export const checkoutRestorePayloadSchema = z.object({
export type CheckpointRestorePayload = z.infer<typeof checkoutRestorePayloadSchema>

export interface IndexingStatusPayload {
state: "Standby" | "Indexing" | "Indexed" | "Error"
state: "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping"
message: string
}

Expand Down Expand Up @@ -738,6 +741,8 @@ export interface IndexingStatus {
totalItems: number
currentItemUnit?: string
workspacePath?: string
workspaceEnabled?: boolean
autoEnableDefault?: boolean
}

export interface IndexingStatusUpdateMessage {
Expand Down
90 changes: 81 additions & 9 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { Package } from "../../shared/package"
import { type RouterName, toRouterName } from "../../shared/api"
import { MessageEnhancer } from "./messageEnhancer"

import { CodeIndexManager } from "../../services/code-index/manager"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { experimentDefault } from "../../shared/experiments"
import { Terminal } from "../../integrations/terminal/Terminal"
Expand Down Expand Up @@ -2611,7 +2612,6 @@ export const webviewMessageHandler = async (
try {
const manager = provider.getCurrentWorkspaceCodeIndexManager()
if (!manager) {
// No workspace open - send error status
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: {
Expand All @@ -2625,23 +2625,19 @@ export const webviewMessageHandler = async (
provider.log("Cannot start indexing: No workspace folder open")
return
}

// "Start Indexing" implicitly enables the workspace
await manager.setWorkspaceEnabled(true)

if (manager.isFeatureEnabled && manager.isFeatureConfigured) {
// Mimic extension startup behavior: initialize first, which will
// check if Qdrant container is active and reuse existing collection
await manager.initialize(provider.contextProxy)

// Only call startIndexing if we're in a state that requires it
// (e.g., Standby or Error). If already Indexed or Indexing, the
// initialize() call above will have already started the watcher.
const currentState = manager.state
if (currentState === "Standby" || currentState === "Error") {
// startIndexing now handles error recovery internally
manager.startIndexing()

// If startIndexing recovered from error, we need to reinitialize
if (!manager.isInitialized) {
await manager.initialize(provider.contextProxy)
// Try starting again after initialization
if (manager.state === "Standby" || manager.state === "Error") {
manager.startIndexing()
}
Expand All @@ -2653,6 +2649,82 @@ export const webviewMessageHandler = async (
}
break
}
case "stopIndexing": {
try {
const manager = provider.getCurrentWorkspaceCodeIndexManager()
if (!manager) {
provider.log("Cannot stop indexing: No workspace folder open")
return
}
manager.stopIndexing()
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})
} catch (error) {
provider.log(`Error stopping indexing: ${error instanceof Error ? error.message : String(error)}`)
}
break
}
case "toggleWorkspaceIndexing": {
try {
const manager = provider.getCurrentWorkspaceCodeIndexManager()
if (!manager) {
provider.log("Cannot toggle workspace indexing: No workspace folder open")
return
}
const enabled = message.bool ?? false
await manager.setWorkspaceEnabled(enabled)
if (enabled && manager.isFeatureEnabled && manager.isFeatureConfigured) {
await manager.initialize(provider.contextProxy)
manager.startIndexing()
} else if (!enabled) {
manager.stopIndexing()
}
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})
} catch (error) {
provider.log(
`Error toggling workspace indexing: ${error instanceof Error ? error.message : String(error)}`,
)
}
break
}
case "setAutoEnableDefault": {
try {
const manager = provider.getCurrentWorkspaceCodeIndexManager()
if (!manager) {
provider.log("Cannot set auto-enable default: No workspace folder open")
return
}
// Capture prior state for every manager before persisting the global change
const allManagers = CodeIndexManager.getAllInstances()
const priorStates = new Map(allManagers.map((m) => [m, m.isWorkspaceEnabled]))
await manager.setAutoEnableDefault(message.bool ?? true)
// Apply stop/start to every affected manager
for (const m of allManagers) {
const wasEnabled = priorStates.get(m)!
const isNowEnabled = m.isWorkspaceEnabled
if (wasEnabled && !isNowEnabled) {
m.stopIndexing()
} else if (!wasEnabled && isNowEnabled && m.isFeatureEnabled && m.isFeatureConfigured) {
await m.initialize(provider.contextProxy)
m.startIndexing()
}
}
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})
} catch (error) {
provider.log(
`Error setting auto-enable default: ${error instanceof Error ? error.message : String(error)}`,
)
}
break
}
case "clearIndexData": {
try {
const manager = provider.getCurrentWorkspaceCodeIndexManager()
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/ca/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/de/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/en/embeddings.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
"fileWatcherStopped": "File watcher stopped.",
"failedDuringInitialScan": "Failed during initial scan: {{errorMessage}}",
"unknownError": "Unknown error",
"indexingRequiresWorkspace": "Indexing requires an open workspace folder"
"indexingRequiresWorkspace": "Indexing requires an open workspace folder",
"indexingStopped": "Indexing stopped by user.",
"indexingStoppedPartial": "Indexing stopped. Partial index data preserved."
}
}
4 changes: 3 additions & 1 deletion src/i18n/locales/es/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/fr/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/hi/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/id/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/it/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/ja/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/ko/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/nl/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/pl/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/pt-BR/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/ru/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/tr/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/vi/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/zh-CN/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/i18n/locales/zh-TW/embeddings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading