From 67ea856fd0d252d39e517b5c30779bc264c832b4 Mon Sep 17 00:00:00 2001 From: James Mtendamema <59908268+JamesRobert20@users.noreply.github.com> Date: Wed, 18 Feb 2026 23:09:17 -0700 Subject: [PATCH] feat: add per-workspace indexing opt-in and stop/cancel control (#11456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add per-workspace indexing opt-in and stop/cancel control - Add codeIndexWorkspaceEnabled flag in workspaceState (default: false) - Thread AbortController/AbortSignal through orchestrator → scanner - Add Stop Indexing button and Stopping state to UI - Fix handleSettingsChange() to abort active scan when disabling toggle - Add translations for all 18 locales * fix: correct abort handling in indexing scanner and orchestrator - Re-throw AbortError in scanner's file processing catch block to prevent abort signals from being silently swallowed as file errors - Reorder stopWatcher() before setSystemState() in orchestrator abort catch path to ensure watcher cleanup before state transition - Update scanner test to assert AbortError propagation on mid-scan abort * fix: optimize workspace check ordering, translate new i18n keys, fix abort handling - Move workspace-enabled check before _recreateServices() in initialize() to avoid creating Qdrant/embedder connections for disabled workspaces - Translate new i18n keys (indexingStopped, indexingStoppedPartial, stopping, stopIndexingButton, stoppingButton, workspaceToggleLabel, workspaceDisabledMessage) in all 17 non-English locales - Re-throw AbortError in scanner catch block to prevent silent swallowing - Reorder stopWatcher() before setSystemState() in orchestrator abort path - Update scanner test to assert AbortError propagation on mid-scan abort - Fix recoverFromError test for workspace-enabled check ordering * fix: per-folder enablement key, abort-safe dispose and back-pressure, translate i18n Addresses 0xMink review feedback: - Store workspace enablement keyed by folder path to support multi-root workspaces (codeIndexWorkspaceEnabled: instead of single boolean) - Add test proving folder A enabled does not enable folder B - dispose() now calls stopIndexing() to abort orphaned scans on folder removal - Scanner back-pressure loop checks abort signal to avoid spin-waiting - Move workspace-enabled check before _recreateServices() in initialize() - Translate new i18n keys in all 17 non-English locales - Fix abort handling in orchestrator and scanner catch blocks * fix: flush debounced cache writes on abort to preserve indexing progress * feat: add global auto-enable default for backward-compatible workspace indexing * fix: stop/start indexer when auto-enable default changes effective state * fix: URI-keyed enablement, throw AbortError in back-pressure, stopWatcher on early-return * fix: iterate all managers when auto-enable default changes in multi-root workspaces --------- Co-authored-by: James Mtendamema --- ...exing-workspace-opt-in-and-stop-control.md | 9 + packages/types/src/vscode-extension-host.ts | 7 +- src/core/webview/webviewMessageHandler.ts | 90 +++++++- src/i18n/locales/ca/embeddings.json | 4 +- src/i18n/locales/de/embeddings.json | 4 +- src/i18n/locales/en/embeddings.json | 4 +- src/i18n/locales/es/embeddings.json | 4 +- src/i18n/locales/fr/embeddings.json | 4 +- src/i18n/locales/hi/embeddings.json | 4 +- src/i18n/locales/id/embeddings.json | 4 +- src/i18n/locales/it/embeddings.json | 4 +- src/i18n/locales/ja/embeddings.json | 4 +- src/i18n/locales/ko/embeddings.json | 4 +- src/i18n/locales/nl/embeddings.json | 4 +- src/i18n/locales/pl/embeddings.json | 4 +- src/i18n/locales/pt-BR/embeddings.json | 4 +- src/i18n/locales/ru/embeddings.json | 4 +- src/i18n/locales/tr/embeddings.json | 4 +- src/i18n/locales/vi/embeddings.json | 4 +- src/i18n/locales/zh-CN/embeddings.json | 4 +- src/i18n/locales/zh-TW/embeddings.json | 4 +- .../code-index/__tests__/manager.spec.ts | 208 +++++++++++++++++- .../code-index/__tests__/orchestrator.spec.ts | 176 +++++++++++++++ src/services/code-index/cache-manager.ts | 7 + src/services/code-index/interfaces/cache.ts | 1 + .../code-index/interfaces/file-processor.ts | 1 + src/services/code-index/interfaces/manager.ts | 7 +- src/services/code-index/manager.ts | 106 ++++++--- src/services/code-index/orchestrator.ts | 43 +++- .../processors/__tests__/scanner.spec.ts | 63 ++++++ src/services/code-index/processors/scanner.ts | 39 +++- src/services/code-index/state-manager.ts | 6 +- .../src/components/chat/CodeIndexPopover.tsx | 66 ++++++ .../components/chat/IndexingStatusBadge.tsx | 3 + webview-ui/src/i18n/locales/ca/chat.json | 3 +- webview-ui/src/i18n/locales/ca/settings.json | 7 +- webview-ui/src/i18n/locales/de/chat.json | 3 +- webview-ui/src/i18n/locales/de/settings.json | 7 +- webview-ui/src/i18n/locales/en/chat.json | 3 +- webview-ui/src/i18n/locales/en/settings.json | 7 +- webview-ui/src/i18n/locales/es/chat.json | 3 +- webview-ui/src/i18n/locales/es/settings.json | 7 +- webview-ui/src/i18n/locales/fr/chat.json | 3 +- webview-ui/src/i18n/locales/fr/settings.json | 7 +- webview-ui/src/i18n/locales/hi/chat.json | 3 +- webview-ui/src/i18n/locales/hi/settings.json | 7 +- webview-ui/src/i18n/locales/id/chat.json | 3 +- webview-ui/src/i18n/locales/id/settings.json | 7 +- webview-ui/src/i18n/locales/it/chat.json | 3 +- webview-ui/src/i18n/locales/it/settings.json | 7 +- webview-ui/src/i18n/locales/ja/chat.json | 3 +- webview-ui/src/i18n/locales/ja/settings.json | 7 +- webview-ui/src/i18n/locales/ko/chat.json | 3 +- webview-ui/src/i18n/locales/ko/settings.json | 7 +- webview-ui/src/i18n/locales/nl/chat.json | 3 +- webview-ui/src/i18n/locales/nl/settings.json | 7 +- webview-ui/src/i18n/locales/pl/chat.json | 3 +- webview-ui/src/i18n/locales/pl/settings.json | 7 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 3 +- .../src/i18n/locales/pt-BR/settings.json | 7 +- webview-ui/src/i18n/locales/ru/chat.json | 3 +- webview-ui/src/i18n/locales/ru/settings.json | 7 +- webview-ui/src/i18n/locales/tr/chat.json | 3 +- webview-ui/src/i18n/locales/tr/settings.json | 7 +- webview-ui/src/i18n/locales/vi/chat.json | 3 +- webview-ui/src/i18n/locales/vi/settings.json | 7 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 3 +- .../src/i18n/locales/zh-CN/settings.json | 7 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 3 +- .../src/i18n/locales/zh-TW/settings.json | 7 +- 70 files changed, 983 insertions(+), 101 deletions(-) create mode 100644 .changeset/indexing-workspace-opt-in-and-stop-control.md diff --git a/.changeset/indexing-workspace-opt-in-and-stop-control.md b/.changeset/indexing-workspace-opt-in-and-stop-control.md new file mode 100644 index 0000000000..27a1fbe0b0 --- /dev/null +++ b/.changeset/indexing-workspace-opt-in-and-stop-control.md @@ -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. diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 38bccc53b5..47b574e4b7 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -508,9 +508,12 @@ export interface WebviewMessage { | "condenseTaskContextRequest" | "requestIndexingStatus" | "startIndexing" + | "stopIndexing" | "clearIndexData" | "indexingStatusUpdate" | "indexCleared" + | "toggleWorkspaceIndexing" + | "setAutoEnableDefault" | "focusPanelRequest" | "openExternal" | "filterMarketplaceItems" @@ -705,7 +708,7 @@ export const checkoutRestorePayloadSchema = z.object({ export type CheckpointRestorePayload = z.infer export interface IndexingStatusPayload { - state: "Standby" | "Indexing" | "Indexed" | "Error" + state: "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping" message: string } @@ -739,6 +742,8 @@ export interface IndexingStatus { totalItems: number currentItemUnit?: string workspacePath?: string + workspaceEnabled?: boolean + autoEnableDefault?: boolean } export interface IndexingStatusUpdateMessage { diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index dc8f073bf1..4f8d431724 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -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" @@ -2608,7 +2609,6 @@ export const webviewMessageHandler = async ( try { const manager = provider.getCurrentWorkspaceCodeIndexManager() if (!manager) { - // No workspace open - send error status provider.postMessageToWebview({ type: "indexingStatusUpdate", values: { @@ -2622,23 +2622,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() } @@ -2650,6 +2646,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() diff --git a/src/i18n/locales/ca/embeddings.json b/src/i18n/locales/ca/embeddings.json index 21a4a27ab4..9ceec7d05c 100644 --- a/src/i18n/locales/ca/embeddings.json +++ b/src/i18n/locales/ca/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Monitor de fitxers aturat.", "failedDuringInitialScan": "Ha fallat durant l'escaneig inicial: {{errorMessage}}", "unknownError": "Error desconegut", - "indexingRequiresWorkspace": "Indexació requereix una carpeta de workspace oberta" + "indexingRequiresWorkspace": "Indexació requereix una carpeta de workspace oberta", + "indexingStopped": "Indexació aturada per l'usuari.", + "indexingStoppedPartial": "Indexació aturada. Dades d'índex parcials conservades." } } diff --git a/src/i18n/locales/de/embeddings.json b/src/i18n/locales/de/embeddings.json index 0297ec0309..766d31d5ba 100644 --- a/src/i18n/locales/de/embeddings.json +++ b/src/i18n/locales/de/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Datei-Watcher gestoppt.", "failedDuringInitialScan": "Fehler während des ersten Scans: {{errorMessage}}", "unknownError": "Unbekannter Fehler", - "indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner" + "indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner", + "indexingStopped": "Indexierung vom Benutzer gestoppt.", + "indexingStoppedPartial": "Indexierung gestoppt. Teilweise Indexdaten beibehalten." } } diff --git a/src/i18n/locales/en/embeddings.json b/src/i18n/locales/en/embeddings.json index 5819e45c1a..7777af9027 100644 --- a/src/i18n/locales/en/embeddings.json +++ b/src/i18n/locales/en/embeddings.json @@ -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." } } diff --git a/src/i18n/locales/es/embeddings.json b/src/i18n/locales/es/embeddings.json index eca9efcc07..930404de1f 100644 --- a/src/i18n/locales/es/embeddings.json +++ b/src/i18n/locales/es/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Monitor de archivos detenido.", "failedDuringInitialScan": "Falló durante el escaneo inicial: {{errorMessage}}", "unknownError": "Error desconocido", - "indexingRequiresWorkspace": "La indexación requiere una carpeta de workspace abierta" + "indexingRequiresWorkspace": "La indexación requiere una carpeta de workspace abierta", + "indexingStopped": "Indexación detenida por el usuario.", + "indexingStoppedPartial": "Indexación detenida. Datos de índice parciales conservados." } } diff --git a/src/i18n/locales/fr/embeddings.json b/src/i18n/locales/fr/embeddings.json index fa92217987..7de086307e 100644 --- a/src/i18n/locales/fr/embeddings.json +++ b/src/i18n/locales/fr/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Surveillant de fichiers arrêté.", "failedDuringInitialScan": "Échec lors du scan initial : {{errorMessage}}", "unknownError": "Erreur inconnue", - "indexingRequiresWorkspace": "L'indexation nécessite l'ouverture d'un dossier workspace" + "indexingRequiresWorkspace": "L'indexation nécessite l'ouverture d'un dossier workspace", + "indexingStopped": "Indexation arrêtée par l'utilisateur.", + "indexingStoppedPartial": "Indexation arrêtée. Données d'index partielles conservées." } } diff --git a/src/i18n/locales/hi/embeddings.json b/src/i18n/locales/hi/embeddings.json index eb7f066c56..9c7f9ca50a 100644 --- a/src/i18n/locales/hi/embeddings.json +++ b/src/i18n/locales/hi/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "फाइल वॉचर रुक गया।", "failedDuringInitialScan": "प्रारंभिक स्कैन के दौरान असफल: {{errorMessage}}", "unknownError": "अज्ञात त्रुटि", - "indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है" + "indexingRequiresWorkspace": "इंडेक्सिंग के लिए एक खुला वर्कस्पेस फ़ोल्डर आवश्यक है", + "indexingStopped": "उपयोगकर्ता द्वारा इंडेक्सिंग रोकी गई।", + "indexingStoppedPartial": "इंडेक्सिंग रोकी गई। आंशिक इंडेक्स डेटा संरक्षित।" } } diff --git a/src/i18n/locales/id/embeddings.json b/src/i18n/locales/id/embeddings.json index cceb965430..955a039eff 100644 --- a/src/i18n/locales/id/embeddings.json +++ b/src/i18n/locales/id/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Pemantau file dihentikan.", "failedDuringInitialScan": "Gagal selama pemindaian awal: {{errorMessage}}", "unknownError": "Kesalahan tidak diketahui", - "indexingRequiresWorkspace": "Pengindeksan memerlukan folder workspace yang terbuka" + "indexingRequiresWorkspace": "Pengindeksan memerlukan folder workspace yang terbuka", + "indexingStopped": "Pengindeksan dihentikan oleh pengguna.", + "indexingStoppedPartial": "Pengindeksan dihentikan. Data indeks parsial dipertahankan." } } diff --git a/src/i18n/locales/it/embeddings.json b/src/i18n/locales/it/embeddings.json index 2e339ef5d8..b7314c244d 100644 --- a/src/i18n/locales/it/embeddings.json +++ b/src/i18n/locales/it/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Monitoraggio file fermato.", "failedDuringInitialScan": "Fallito durante la scansione iniziale: {{errorMessage}}", "unknownError": "Errore sconosciuto", - "indexingRequiresWorkspace": "L'indicizzazione richiede una cartella di workspace aperta" + "indexingRequiresWorkspace": "L'indicizzazione richiede una cartella di workspace aperta", + "indexingStopped": "Indicizzazione interrotta dall'utente.", + "indexingStoppedPartial": "Indicizzazione interrotta. Dati di indice parziali conservati." } } diff --git a/src/i18n/locales/ja/embeddings.json b/src/i18n/locales/ja/embeddings.json index 5223c204e0..ce7150cf1c 100644 --- a/src/i18n/locales/ja/embeddings.json +++ b/src/i18n/locales/ja/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "ファイルウォッチャーが停止されました。", "failedDuringInitialScan": "初期スキャン中に失敗しました:{{errorMessage}}", "unknownError": "不明なエラー", - "indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です" + "indexingRequiresWorkspace": "インデックス作成には、開かれたワークスペースフォルダーが必要です", + "indexingStopped": "ユーザーによりインデックス作成が停止されました。", + "indexingStoppedPartial": "インデックス作成が停止されました。部分的なインデックスデータは保持されています。" } } diff --git a/src/i18n/locales/ko/embeddings.json b/src/i18n/locales/ko/embeddings.json index 236662eea2..436fa985c0 100644 --- a/src/i18n/locales/ko/embeddings.json +++ b/src/i18n/locales/ko/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "파일 감시자가 중지되었습니다.", "failedDuringInitialScan": "초기 스캔 중 실패: {{errorMessage}}", "unknownError": "알 수 없는 오류", - "indexingRequiresWorkspace": "인덱싱에는 열린 워크스페이스 폴더가 필요합니다" + "indexingRequiresWorkspace": "인덱싱에는 열린 워크스페이스 폴더가 필요합니다", + "indexingStopped": "사용자에 의해 인덱싱이 중지되었습니다.", + "indexingStoppedPartial": "인덱싱이 중지되었습니다. 부분 인덱스 데이터가 보존되었습니다." } } diff --git a/src/i18n/locales/nl/embeddings.json b/src/i18n/locales/nl/embeddings.json index cce3f05c62..01e68683d3 100644 --- a/src/i18n/locales/nl/embeddings.json +++ b/src/i18n/locales/nl/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Bestandsmonitor gestopt.", "failedDuringInitialScan": "Mislukt tijdens initiële scan: {{errorMessage}}", "unknownError": "Onbekende fout", - "indexingRequiresWorkspace": "Indexering vereist een geopende workspace map" + "indexingRequiresWorkspace": "Indexering vereist een geopende workspace map", + "indexingStopped": "Indexering gestopt door gebruiker.", + "indexingStoppedPartial": "Indexering gestopt. Gedeeltelijke indexgegevens bewaard." } } diff --git a/src/i18n/locales/pl/embeddings.json b/src/i18n/locales/pl/embeddings.json index 133f9f40da..0ef846b2cc 100644 --- a/src/i18n/locales/pl/embeddings.json +++ b/src/i18n/locales/pl/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Monitor plików zatrzymany.", "failedDuringInitialScan": "Niepowodzenie podczas początkowego skanowania: {{errorMessage}}", "unknownError": "Nieznany błąd", - "indexingRequiresWorkspace": "Indeksowanie wymaga otwartego folderu workspace" + "indexingRequiresWorkspace": "Indeksowanie wymaga otwartego folderu workspace", + "indexingStopped": "Indeksowanie zatrzymane przez użytkownika.", + "indexingStoppedPartial": "Indeksowanie zatrzymane. Częściowe dane indeksu zachowane." } } diff --git a/src/i18n/locales/pt-BR/embeddings.json b/src/i18n/locales/pt-BR/embeddings.json index 09f4a55787..9cdf775e76 100644 --- a/src/i18n/locales/pt-BR/embeddings.json +++ b/src/i18n/locales/pt-BR/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Monitor de arquivos parado.", "failedDuringInitialScan": "Falhou durante a varredura inicial: {{errorMessage}}", "unknownError": "Erro desconhecido", - "indexingRequiresWorkspace": "A indexação requer uma pasta de workspace aberta" + "indexingRequiresWorkspace": "A indexação requer uma pasta de workspace aberta", + "indexingStopped": "Indexação interrompida pelo usuário.", + "indexingStoppedPartial": "Indexação interrompida. Dados de índice parciais preservados." } } diff --git a/src/i18n/locales/ru/embeddings.json b/src/i18n/locales/ru/embeddings.json index 9e94082bbf..873b1c0630 100644 --- a/src/i18n/locales/ru/embeddings.json +++ b/src/i18n/locales/ru/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Наблюдатель файлов остановлен.", "failedDuringInitialScan": "Ошибка во время первоначального сканирования: {{errorMessage}}", "unknownError": "Неизвестная ошибка", - "indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства" + "indexingRequiresWorkspace": "Для индексации требуется открытая папка рабочего пространства", + "indexingStopped": "Индексация остановлена пользователем.", + "indexingStoppedPartial": "Индексация остановлена. Частичные данные индекса сохранены." } } diff --git a/src/i18n/locales/tr/embeddings.json b/src/i18n/locales/tr/embeddings.json index 411ed7ab52..30b703a93f 100644 --- a/src/i18n/locales/tr/embeddings.json +++ b/src/i18n/locales/tr/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Dosya izleyici durduruldu.", "failedDuringInitialScan": "İlk tarama sırasında başarısız: {{errorMessage}}", "unknownError": "Bilinmeyen hata", - "indexingRequiresWorkspace": "İndeksleme açık bir workspace klasörü gerektirir" + "indexingRequiresWorkspace": "İndeksleme açık bir workspace klasörü gerektirir", + "indexingStopped": "İndeksleme kullanıcı tarafından durduruldu.", + "indexingStoppedPartial": "İndeksleme durduruldu. Kısmi indeks verileri korundu." } } diff --git a/src/i18n/locales/vi/embeddings.json b/src/i18n/locales/vi/embeddings.json index c9f9880df0..c92ebba276 100644 --- a/src/i18n/locales/vi/embeddings.json +++ b/src/i18n/locales/vi/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "Trình theo dõi tệp đã dừng.", "failedDuringInitialScan": "Thất bại trong quá trình quét ban đầu: {{errorMessage}}", "unknownError": "Lỗi không xác định", - "indexingRequiresWorkspace": "Lập chỉ mục yêu cầu một thư mục workspace đang mở" + "indexingRequiresWorkspace": "Lập chỉ mục yêu cầu một thư mục workspace đang mở", + "indexingStopped": "Lập chỉ mục đã bị dừng bởi người dùng.", + "indexingStoppedPartial": "Lập chỉ mục đã dừng. Dữ liệu chỉ mục một phần được bảo toàn." } } diff --git a/src/i18n/locales/zh-CN/embeddings.json b/src/i18n/locales/zh-CN/embeddings.json index c27bc07801..b4f4eaad1d 100644 --- a/src/i18n/locales/zh-CN/embeddings.json +++ b/src/i18n/locales/zh-CN/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "文件监控已停止。", "failedDuringInitialScan": "初始扫描失败:{{errorMessage}}", "unknownError": "未知错误", - "indexingRequiresWorkspace": "索引需要打开的工作区文件夹" + "indexingRequiresWorkspace": "索引需要打开的工作区文件夹", + "indexingStopped": "用户已停止索引。", + "indexingStoppedPartial": "索引已停止。部分索引数据已保留。" } } diff --git a/src/i18n/locales/zh-TW/embeddings.json b/src/i18n/locales/zh-TW/embeddings.json index 744e7022ea..26845ed948 100644 --- a/src/i18n/locales/zh-TW/embeddings.json +++ b/src/i18n/locales/zh-TW/embeddings.json @@ -69,6 +69,8 @@ "fileWatcherStopped": "檔案監控已停止。", "failedDuringInitialScan": "初始掃描失敗:{{errorMessage}}", "unknownError": "未知錯誤", - "indexingRequiresWorkspace": "索引需要開啟的工作區資料夾" + "indexingRequiresWorkspace": "索引需要開啟的工作區資料夾", + "indexingStopped": "使用者已停止索引。", + "indexingStoppedPartial": "索引已停止。部分索引資料已保留。" } } diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts index 929f6f93c8..49a6d91c76 100644 --- a/src/services/code-index/__tests__/manager.spec.ts +++ b/src/services/code-index/__tests__/manager.spec.ts @@ -3,18 +3,45 @@ import { CodeIndexServiceFactory } from "../service-factory" import type { MockedClass } from "vitest" import * as path from "path" +// Helper: create a mock vscode.Uri from an fsPath +function mockUri(fsPath: string, scheme = "file") { + return { + fsPath, + scheme, + authority: "", + path: fsPath, + toString: (skipEncoding?: boolean) => `${scheme}://${fsPath}`, + } +} + // Mock vscode module vi.mock("vscode", () => { const testPath = require("path") const testWorkspacePath = testPath.join(testPath.sep, "test", "workspace") return { + Uri: { + file: (p: string) => ({ + fsPath: p, + scheme: "file", + authority: "", + path: p, + toString: (_skipEncoding?: boolean) => `file://${p}`, + }), + joinPath: vi.fn((...args: any[]) => ({ fsPath: args.join("/") })), + }, window: { activeTextEditor: null, }, workspace: { workspaceFolders: [ { - uri: { fsPath: testWorkspacePath }, + uri: { + fsPath: testWorkspacePath, + scheme: "file", + authority: "", + path: testWorkspacePath, + toString: (_skipEncoding?: boolean) => `file://${testWorkspacePath}`, + }, name: "test", index: 0, }, @@ -25,8 +52,9 @@ vi.mock("vscode", () => { onDidDelete: vi.fn().mockReturnValue({ dispose: vi.fn() }), dispose: vi.fn(), }), + getWorkspaceFolder: vi.fn(), }, - RelativePattern: vi.fn().mockImplementation((base, pattern) => ({ base, pattern })), + RelativePattern: vi.fn().mockImplementation((base: any, pattern: any) => ({ base, pattern })), } }) @@ -95,10 +123,22 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { // Clear all instances before each test CodeIndexManager.disposeAll() + const workspaceStateStore: Record = {} + const globalStateStore: Record = {} mockContext = { subscriptions: [], - workspaceState: {} as any, - globalState: {} as any, + workspaceState: { + get: vi.fn((key: string, defaultValue?: any) => workspaceStateStore[key] ?? defaultValue), + update: vi.fn(async (key: string, value: any) => { + workspaceStateStore[key] = value + }), + } as any, + globalState: { + get: vi.fn((key: string, defaultValue?: any) => globalStateStore[key] ?? defaultValue), + update: vi.fn(async (key: string, value: any) => { + globalStateStore[key] = value + }), + } as any, extensionUri: {} as any, extensionPath: testExtensionPath, asAbsolutePath: vi.fn(), @@ -222,7 +262,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { ;(manager as any)._cacheManager = mockCacheManager // Simulate an initialized manager by setting the required properties - ;(manager as any)._orchestrator = { stopWatcher: vi.fn() } + ;(manager as any)._orchestrator = { stopWatcher: vi.fn(), stopIndexing: vi.fn() } ;(manager as any)._searchService = {} // Verify manager is considered initialized @@ -456,7 +496,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { }) // Mock orchestrator and search service to simulate initialized state - ;(manager as any)._orchestrator = { stopWatcher: vi.fn(), state: "Error" } + ;(manager as any)._orchestrator = { stopWatcher: vi.fn(), stopIndexing: vi.fn(), state: "Error" } ;(manager as any)._searchService = {} ;(manager as any)._serviceFactory = {} }) @@ -540,6 +580,9 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { }), } + // Enable workspace indexing before re-initialization + await manager.setWorkspaceEnabled(true) + // Re-initialize await manager.initialize(mockContextProxy as any) @@ -583,7 +626,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { // Setup manager with service instances ;(manager as any)._configManager = mockConfigManager ;(manager as any)._serviceFactory = {} - ;(manager as any)._orchestrator = { stopWatcher: vi.fn() } + ;(manager as any)._orchestrator = { stopWatcher: vi.fn(), stopIndexing: vi.fn() } ;(manager as any)._searchService = {} // Spy on console.error @@ -608,4 +651,155 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { consoleErrorSpy.mockRestore() }) }) + + describe("workspace-enabled gating", () => { + it("should not start indexing when workspace is not enabled", async () => { + await manager.setAutoEnableDefault(false) + + const mockStateManager = (manager as any)._stateManager + mockStateManager.setSystemState = vi.fn() + mockStateManager.getCurrentStatus = vi.fn().mockReturnValue({ + systemStatus: "Standby", + message: "", + processedItems: 0, + totalItems: 0, + currentItemUnit: "items", + }) + + expect(manager.isWorkspaceEnabled).toBe(false) + + await manager.startIndexing() + + expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Indexing", expect.any(String)) + }) + + it("should include workspaceEnabled in getCurrentStatus", async () => { + await manager.setAutoEnableDefault(false) + + const mockStateManager = (manager as any)._stateManager + mockStateManager.getCurrentStatus = vi.fn().mockReturnValue({ + systemStatus: "Standby", + message: "", + processedItems: 0, + totalItems: 0, + currentItemUnit: "items", + }) + + const status = manager.getCurrentStatus() + expect(status.workspaceEnabled).toBe(false) + }) + + it("should persist workspace enabled state", async () => { + await manager.setAutoEnableDefault(false) + expect(manager.isWorkspaceEnabled).toBe(false) + + await manager.setWorkspaceEnabled(true) + expect(manager.isWorkspaceEnabled).toBe(true) + + await manager.setWorkspaceEnabled(false) + expect(manager.isWorkspaceEnabled).toBe(false) + }) + + it("should store enablement per folder URI, not per window", async () => { + CodeIndexManager.disposeAll() + + const vscode = await import("vscode") + + const folderAPath = path.join(path.sep, "test", "folderA") + const folderBPath = path.join(path.sep, "test", "folderB") + const folderAUri = mockUri(folderAPath) + const folderBUri = mockUri(folderBPath) + + // Both folders share the same workspaceState (same window) + const sharedStore: Record = {} + const sharedContext = { + ...mockContext, + workspaceState: { + get: vi.fn((key: string, defaultValue?: any) => sharedStore[key] ?? defaultValue), + update: vi.fn(async (key: string, value: any) => { + sharedStore[key] = value + }), + } as any, + globalState: { + get: vi.fn((_key: string, _defaultValue?: any) => false), + update: vi.fn(), + } as any, + } + + // Patch workspaceFolders to include both folders + ;(vscode.workspace as any).workspaceFolders = [ + { uri: folderAUri, name: "folderA", index: 0 }, + { uri: folderBUri, name: "folderB", index: 1 }, + ] + + const managerA = CodeIndexManager.getInstance(sharedContext as any, folderAPath)! + const managerB = CodeIndexManager.getInstance(sharedContext as any, folderBPath)! + + // Both start disabled (autoEnableDefault is false via globalState mock) + expect(managerA.isWorkspaceEnabled).toBe(false) + expect(managerB.isWorkspaceEnabled).toBe(false) + + // Enable A only + await managerA.setWorkspaceEnabled(true) + + expect(managerA.isWorkspaceEnabled).toBe(true) + expect(managerB.isWorkspaceEnabled).toBe(false) + + // Enable B, disable A + await managerB.setWorkspaceEnabled(true) + await managerA.setWorkspaceEnabled(false) + + expect(managerA.isWorkspaceEnabled).toBe(false) + expect(managerB.isWorkspaceEnabled).toBe(true) + + CodeIndexManager.disposeAll() + }) + }) + + describe("stopIndexing", () => { + it("should delegate to orchestrator.stopIndexing()", () => { + const mockOrchestrator = { + stopIndexing: vi.fn(), + stopWatcher: vi.fn(), + state: "Indexing", + } + ;(manager as any)._orchestrator = mockOrchestrator + + manager.stopIndexing() + + expect(mockOrchestrator.stopIndexing).toHaveBeenCalled() + }) + + it("should be safe to call when orchestrator is not set", () => { + ;(manager as any)._orchestrator = undefined + + expect(() => manager.stopIndexing()).not.toThrow() + }) + }) + + describe("handleSettingsChange - disable toggle bug fix", () => { + it("should abort active indexing when feature is disabled", async () => { + const mockOrchestrator = { + stopIndexing: vi.fn(), + stopWatcher: vi.fn(), + state: "Indexing", + } + ;(manager as any)._orchestrator = mockOrchestrator + + const mockConfigManager = { + loadConfiguration: vi.fn().mockResolvedValue({ requiresRestart: false }), + isFeatureConfigured: true, + isFeatureEnabled: false, + } + ;(manager as any)._configManager = mockConfigManager + + const mockStateManager = (manager as any)._stateManager + mockStateManager.setSystemState = vi.fn() + + await manager.handleSettingsChange() + + expect(mockOrchestrator.stopIndexing).toHaveBeenCalled() + expect(mockStateManager.setSystemState).toHaveBeenCalledWith("Standby", "Code indexing is disabled") + }) + }) }) diff --git a/src/services/code-index/__tests__/orchestrator.spec.ts b/src/services/code-index/__tests__/orchestrator.spec.ts index aab1ef888d..e940ea04c2 100644 --- a/src/services/code-index/__tests__/orchestrator.spec.ts +++ b/src/services/code-index/__tests__/orchestrator.spec.ts @@ -79,6 +79,7 @@ describe("CodeIndexOrchestrator - error path cleanup gating", () => { cacheManager = { clearCacheFile: vi.fn().mockResolvedValue(undefined), + flush: vi.fn().mockResolvedValue(undefined), } vectorStore = { @@ -158,3 +159,178 @@ describe("CodeIndexOrchestrator - error path cleanup gating", () => { expect(lastCall[0]).toBe("Error") }) }) + +describe("CodeIndexOrchestrator - stopIndexing", () => { + const workspacePath = "/test/workspace" + + let configManager: any + let stateManager: any + let cacheManager: any + let vectorStore: any + let scanner: any + let fileWatcher: any + + beforeEach(() => { + vi.clearAllMocks() + + configManager = { + isFeatureConfigured: true, + } + + let currentState = "Standby" + stateManager = { + get state() { + return currentState + }, + setSystemState: vi.fn().mockImplementation((state: string, _msg: string) => { + currentState = state + }), + reportFileQueueProgress: vi.fn(), + reportBlockIndexingProgress: vi.fn(), + } + + cacheManager = { + clearCacheFile: vi.fn().mockResolvedValue(undefined), + flush: vi.fn().mockResolvedValue(undefined), + } + + vectorStore = { + initialize: vi.fn().mockResolvedValue(false), + hasIndexedData: vi.fn().mockResolvedValue(false), + markIndexingIncomplete: vi.fn().mockResolvedValue(undefined), + markIndexingComplete: vi.fn().mockResolvedValue(undefined), + clearCollection: vi.fn().mockResolvedValue(undefined), + } + + scanner = { + scanDirectory: vi.fn(), + } + + fileWatcher = { + initialize: vi.fn().mockResolvedValue(undefined), + onDidStartBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }), + onBatchProgressUpdate: vi.fn().mockReturnValue({ dispose: vi.fn() }), + onDidFinishBatchProcessing: vi.fn().mockReturnValue({ dispose: vi.fn() }), + dispose: vi.fn(), + } + }) + + it("should abort indexing when stopIndexing() is called", async () => { + // Make scanner hang until aborted + scanner.scanDirectory.mockImplementation( + async (_dir: string, _onError?: any, _onBlocksIndexed?: any, _onFileParsed?: any, signal?: AbortSignal) => { + // Wait for abort signal + await new Promise((resolve) => { + if (signal?.aborted) { + resolve() + return + } + signal?.addEventListener("abort", () => resolve()) + }) + return { stats: { processed: 0, skipped: 0 }, totalBlockCount: 0 } + }, + ) + + const orchestrator = new CodeIndexOrchestrator( + configManager, + stateManager, + workspacePath, + cacheManager, + vectorStore, + scanner, + fileWatcher, + ) + + // Start indexing (async, don't await) + const indexingPromise = orchestrator.startIndexing() + + // Give it a tick to begin + await new Promise((resolve) => setTimeout(resolve, 10)) + + // Stop indexing + orchestrator.stopIndexing() + + // Wait for indexing to complete + await indexingPromise + + // State should be Standby (not Error) + const setStateCalls = stateManager.setSystemState.mock.calls + const lastCall = setStateCalls[setStateCalls.length - 1] + expect(lastCall[0]).toBe("Standby") + }) + + it("should set state to Standby after abort, not Error", async () => { + // Make scanner throw AbortError when signal is aborted + scanner.scanDirectory.mockImplementation( + async (_dir: string, _onError?: any, _onBlocksIndexed?: any, _onFileParsed?: any, signal?: AbortSignal) => { + await new Promise((resolve) => { + if (signal?.aborted) { + resolve() + return + } + signal?.addEventListener("abort", () => resolve()) + }) + throw new DOMException("Indexing aborted", "AbortError") + }, + ) + + const orchestrator = new CodeIndexOrchestrator( + configManager, + stateManager, + workspacePath, + cacheManager, + vectorStore, + scanner, + fileWatcher, + ) + + const indexingPromise = orchestrator.startIndexing() + await new Promise((resolve) => setTimeout(resolve, 10)) + + orchestrator.stopIndexing() + await indexingPromise + + // Should NOT have set Error state — abort is handled gracefully + const errorCalls = stateManager.setSystemState.mock.calls.filter((call: any[]) => call[0] === "Error") + expect(errorCalls).toHaveLength(0) + + // Should NOT have cleared collection on abort + expect(vectorStore.clearCollection).not.toHaveBeenCalled() + }) + + it("should preserve partial index data after stop", async () => { + scanner.scanDirectory.mockImplementation( + async (_dir: string, _onError?: any, _onBlocksIndexed?: any, _onFileParsed?: any, signal?: AbortSignal) => { + await new Promise((resolve) => { + if (signal?.aborted) { + resolve() + return + } + signal?.addEventListener("abort", () => resolve()) + }) + return { stats: { processed: 5, skipped: 0 }, totalBlockCount: 5 } + }, + ) + + const orchestrator = new CodeIndexOrchestrator( + configManager, + stateManager, + workspacePath, + cacheManager, + vectorStore, + scanner, + fileWatcher, + ) + + const indexingPromise = orchestrator.startIndexing() + await new Promise((resolve) => setTimeout(resolve, 10)) + + orchestrator.stopIndexing() + await indexingPromise + + // Cache should NOT be cleared on user-initiated stop + expect(cacheManager.clearCacheFile).not.toHaveBeenCalled() + // Collection should NOT be cleared on user-initiated stop + expect(vectorStore.clearCollection).not.toHaveBeenCalled() + }) +}) diff --git a/src/services/code-index/cache-manager.ts b/src/services/code-index/cache-manager.ts index a9a4f0ac47..eadaa9e346 100644 --- a/src/services/code-index/cache-manager.ts +++ b/src/services/code-index/cache-manager.ts @@ -110,6 +110,13 @@ export class CacheManager implements ICacheManager { this._debouncedSaveCache() } + /** + * Flushes any pending debounced cache writes to disk immediately. + */ + async flush(): Promise { + await this._performSave() + } + /** * Gets a copy of all file hashes * @returns A copy of the file hashes record diff --git a/src/services/code-index/interfaces/cache.ts b/src/services/code-index/interfaces/cache.ts index a2e62bcac1..01931a3a8b 100644 --- a/src/services/code-index/interfaces/cache.ts +++ b/src/services/code-index/interfaces/cache.ts @@ -2,5 +2,6 @@ export interface ICacheManager { getHash(filePath: string): string | undefined updateHash(filePath: string, hash: string): void deleteHash(filePath: string): void + flush(): Promise getAllHashes(): Record } diff --git a/src/services/code-index/interfaces/file-processor.ts b/src/services/code-index/interfaces/file-processor.ts index 88b19007c3..8ecdc518c8 100644 --- a/src/services/code-index/interfaces/file-processor.ts +++ b/src/services/code-index/interfaces/file-processor.ts @@ -37,6 +37,7 @@ export interface IDirectoryScanner { onError?: (error: Error) => void, onBlocksIndexed?: (indexedCount: number) => void, onFileParsed?: (fileBlockCount: number) => void, + signal?: AbortSignal, ): Promise<{ stats: { processed: number diff --git a/src/services/code-index/interfaces/manager.ts b/src/services/code-index/interfaces/manager.ts index 28ff552327..d657ad667c 100644 --- a/src/services/code-index/interfaces/manager.ts +++ b/src/services/code-index/interfaces/manager.ts @@ -39,6 +39,11 @@ export interface ICodeIndexManager { */ startIndexing(): Promise + /** + * Stops any in-progress indexing operation and the file watcher + */ + stopIndexing(): void + /** * Stops the file watcher */ @@ -69,7 +74,7 @@ export interface ICodeIndexManager { dispose(): void } -export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" +export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping" export type EmbedderProvider = | "openai" | "ollama" diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts index dd79a3f161..91ea515e40 100644 --- a/src/services/code-index/manager.ts +++ b/src/services/code-index/manager.ts @@ -32,30 +32,47 @@ export class CodeIndexManager { private _isRecoveringFromError = false public static getInstance(context: vscode.ExtensionContext, workspacePath?: string): CodeIndexManager | undefined { - // If workspacePath is not provided, try to get it from the active editor or first workspace folder - if (!workspacePath) { + // Resolve the workspace folder to get both fsPath and the real URI + let folder: vscode.WorkspaceFolder | undefined + + if (workspacePath) { + folder = vscode.workspace.workspaceFolders?.find((f) => f.uri.fsPath === workspacePath) + } else { const activeEditor = vscode.window.activeTextEditor if (activeEditor) { - const workspaceFolder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri) - workspacePath = workspaceFolder?.uri.fsPath + folder = vscode.workspace.getWorkspaceFolder(activeEditor.document.uri) } - - if (!workspacePath) { + if (!folder) { const workspaceFolders = vscode.workspace.workspaceFolders if (!workspaceFolders || workspaceFolders.length === 0) { return undefined } - // Use the first workspace folder as fallback - workspacePath = workspaceFolders[0].uri.fsPath + folder = workspaceFolders[0] } + workspacePath = folder.uri.fsPath } if (!CodeIndexManager.instances.has(workspacePath)) { - CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, context)) + // folder may be undefined when workspacePath was provided but doesn't match + // any workspace folder (e.g. cwd passed from a tool). Fall back to file:// URI. + const folderUri = + folder?.uri ?? + ({ + fsPath: workspacePath, + scheme: "file", + authority: "", + path: workspacePath, + toString: () => `file://${workspacePath}`, + } as unknown as vscode.Uri) + CodeIndexManager.instances.set(workspacePath, new CodeIndexManager(workspacePath, folderUri, context)) } return CodeIndexManager.instances.get(workspacePath)! } + public static getAllInstances(): CodeIndexManager[] { + return Array.from(CodeIndexManager.instances.values()) + } + public static disposeAll(): void { for (const instance of CodeIndexManager.instances.values()) { instance.dispose() @@ -64,17 +81,45 @@ export class CodeIndexManager { } private readonly workspacePath: string + private readonly _folderUri: vscode.Uri private readonly context: vscode.ExtensionContext // Private constructor for singleton pattern - private constructor(workspacePath: string, context: vscode.ExtensionContext) { + private constructor(workspacePath: string, folderUri: vscode.Uri, context: vscode.ExtensionContext) { this.workspacePath = workspacePath + this._folderUri = folderUri this.context = context this._stateManager = new CodeIndexStateManager() } // --- Public API --- + /** + * Returns the workspaceState key for per-folder indexing enablement, + * keyed by the real workspace folder URI so local/remote schemes cannot collide. + */ + private _workspaceEnabledKey(): string { + return "codeIndexWorkspaceEnabled:" + this._folderUri.toString(true) + } + + public get isWorkspaceEnabled(): boolean { + const explicit = this.context.workspaceState.get(this._workspaceEnabledKey(), undefined) + if (explicit !== undefined) return explicit + return this.autoEnableDefault + } + + public async setWorkspaceEnabled(enabled: boolean): Promise { + await this.context.workspaceState.update(this._workspaceEnabledKey(), enabled) + } + + public get autoEnableDefault(): boolean { + return this.context.globalState.get("codeIndexAutoEnableDefault", true) + } + + public async setAutoEnableDefault(enabled: boolean): Promise { + await this.context.globalState.update("codeIndexAutoEnableDefault", enabled) + } + public get onProgressUpdate() { return this._stateManager.onProgressUpdate } @@ -138,28 +183,32 @@ export class CodeIndexManager { return { requiresRestart } } - // 4. CacheManager Initialization + // 4. Check workspace-level enablement (before creating expensive services) + if (!this.isWorkspaceEnabled) { + this._stateManager.setSystemState("Standby", "Indexing not enabled for this workspace") + return { requiresRestart } + } + + // 5. CacheManager Initialization if (!this._cacheManager) { this._cacheManager = new CacheManager(this.context, this.workspacePath) await this._cacheManager.initialize() } - // 4. Determine if Core Services Need Recreation + // 6. Determine if Core Services Need Recreation const needsServiceRecreation = !this._serviceFactory || requiresRestart if (needsServiceRecreation) { await this._recreateServices() } - // 5. Handle Indexing Start/Restart - // The enhanced vectorStore.initialize() in startIndexing() now handles dimension changes automatically - // by detecting incompatible collections and recreating them, so we rely on that for dimension changes + // 7. Handle Indexing Start/Restart const shouldStartOrRestartIndexing = requiresRestart || (needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing")) if (shouldStartOrRestartIndexing) { - this._orchestrator?.startIndexing() // This method is async, but we don't await it here + this._orchestrator?.startIndexing() } return { requiresRestart } @@ -173,7 +222,7 @@ export class CodeIndexManager { * The indexing will continue asynchronously and progress will be reported through events. */ public async startIndexing(): Promise { - if (!this.isFeatureEnabled) { + if (!this.isFeatureEnabled || !this.isWorkspaceEnabled) { return } @@ -191,6 +240,15 @@ export class CodeIndexManager { await this._orchestrator!.startIndexing() } + /** + * Stops any in-progress indexing operation and the file watcher. + */ + public stopIndexing(): void { + if (this._orchestrator) { + this._orchestrator.stopIndexing() + } + } + /** * Stops the file watcher and potentially cleans up resources. */ @@ -247,9 +305,7 @@ export class CodeIndexManager { * Cleans up the manager instance. */ public dispose(): void { - if (this._orchestrator) { - this.stopWatcher() - } + this.stopIndexing() this._stateManager.dispose() } @@ -273,6 +329,8 @@ export class CodeIndexManager { return { ...status, workspacePath: this.workspacePath, + workspaceEnabled: this.isWorkspaceEnabled, + autoEnableDefault: this.autoEnableDefault, } } @@ -384,13 +442,9 @@ export class CodeIndexManager { const isFeatureEnabled = this.isFeatureEnabled const isFeatureConfigured = this.isFeatureConfigured - // If feature is disabled, stop the service + // If feature is disabled, stop the service (including any active scan) if (!isFeatureEnabled) { - // Stop the orchestrator if it exists - if (this._orchestrator) { - this._orchestrator.stopWatcher() - } - // Set state to indicate service is disabled + this.stopIndexing() this._stateManager.setSystemState("Standby", "Code indexing is disabled") return } diff --git a/src/services/code-index/orchestrator.ts b/src/services/code-index/orchestrator.ts index 99f317882b..cd65fceb5e 100644 --- a/src/services/code-index/orchestrator.ts +++ b/src/services/code-index/orchestrator.ts @@ -15,6 +15,7 @@ import { t } from "../../i18n" export class CodeIndexOrchestrator { private _fileWatcherSubscriptions: vscode.Disposable[] = [] private _isProcessing: boolean = false + private _abortController: AbortController | null = null constructor( private readonly configManager: CodeIndexConfigManager, @@ -121,6 +122,8 @@ export class CodeIndexOrchestrator { } this._isProcessing = true + this._abortController = new AbortController() + const signal = this._abortController.signal this.stateManager.setSystemState("Indexing", "Initializing services...") // Track whether we successfully connected to Qdrant and started indexing @@ -178,8 +181,16 @@ export class CodeIndexOrchestrator { }, handleBlocksIndexed, handleFileParsed, + signal, ) + if (signal.aborted) { + await this.cacheManager.flush() + this.stopWatcher() + this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped")) + return + } + if (!result) { throw new Error("Incremental scan failed, is scanner initialized?") } @@ -231,8 +242,16 @@ export class CodeIndexOrchestrator { }, handleBlocksIndexed, handleFileParsed, + signal, ) + if (signal.aborted) { + await this.cacheManager.flush() + this.stopWatcher() + this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped")) + return + } + if (!result) { throw new Error("Scan failed, is scanner initialized?") } @@ -282,6 +301,15 @@ export class CodeIndexOrchestrator { this.stateManager.setSystemState("Indexed", t("embeddings:orchestrator.fileWatcherStarted")) } } catch (error: any) { + // Handle abort gracefully — not an error, just a user-initiated stop + if (error?.name === "AbortError" || signal.aborted) { + console.log("[CodeIndexOrchestrator] Indexing aborted by user.") + await this.cacheManager.flush() + this.stopWatcher() + this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.indexingStopped")) + return + } + console.error("[CodeIndexOrchestrator] Error during indexing:", error) TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { error: error instanceof Error ? error.message : String(error), @@ -325,9 +353,22 @@ export class CodeIndexOrchestrator { this.stopWatcher() } finally { this._isProcessing = false + this._abortController = null } } + /** + * Stops any in-progress indexing by aborting the scan and stopping the file watcher. + */ + public stopIndexing(): void { + if (this._abortController) { + this.stateManager.setSystemState("Stopping", t("embeddings:orchestrator.indexingStoppedPartial")) + this._abortController.abort() + this._abortController = null + } + this.stopWatcher() + } + /** * Stops the file watcher and cleans up resources. */ @@ -336,7 +377,7 @@ export class CodeIndexOrchestrator { this._fileWatcherSubscriptions.forEach((sub) => sub.dispose()) this._fileWatcherSubscriptions = [] - if (this.stateManager.state !== "Error") { + if (this.stateManager.state !== "Error" && this.stateManager.state !== "Stopping") { this.stateManager.setSystemState("Standby", t("embeddings:orchestrator.fileWatcherStopped")) } this._isProcessing = false diff --git a/src/services/code-index/processors/__tests__/scanner.spec.ts b/src/services/code-index/processors/__tests__/scanner.spec.ts index 4d4150b443..a6e68bc96b 100644 --- a/src/services/code-index/processors/__tests__/scanner.spec.ts +++ b/src/services/code-index/processors/__tests__/scanner.spec.ts @@ -394,5 +394,68 @@ describe("DirectoryScanner", () => { expect(points[1].payload.segmentHash).toBe("unique-segment-hash-2") expect(points[2].payload.segmentHash).toBe("unique-segment-hash-3") }) + + it("should stop processing files when signal is aborted", async () => { + const { listFiles } = await import("../../../glob/list-files") + vi.mocked(listFiles).mockResolvedValue([["test/file1.js", "test/file2.js", "test/file3.js"], false]) + + // Create an already-aborted signal + const controller = new AbortController() + controller.abort() + + const result = await scanner.scanDirectory("/test", undefined, undefined, undefined, controller.signal) + + // No files should have been processed since signal was already aborted + expect(mockCodeParser.parseFile).not.toHaveBeenCalled() + expect(result.stats.processed).toBe(0) + }) + + it("should stop processing batches when signal is aborted mid-scan", async () => { + const { listFiles } = await import("../../../glob/list-files") + vi.mocked(listFiles).mockResolvedValue([["test/file1.js", "test/file2.js"], false]) + + const controller = new AbortController() + + const mockBlocks: any[] = [ + { + file_path: "test/file1.js", + content: "function hello() {}", + start_line: 1, + end_line: 3, + identifier: "hello", + type: "function", + fileHash: "hash1", + segmentHash: "seg-hash-1", + }, + ] + + // Abort after first file is parsed + ;(mockCodeParser.parseFile as any).mockImplementation(async () => { + controller.abort() + return mockBlocks + }) + + // AbortError should propagate up (the orchestrator handles it in its catch block) + await expect( + scanner.scanDirectory("/test", undefined, undefined, undefined, controller.signal), + ).rejects.toThrow("Indexing aborted") + }) + + it("should not process deleted files when signal is aborted", async () => { + const { listFiles } = await import("../../../glob/list-files") + vi.mocked(listFiles).mockResolvedValue([[], false]) + + // Set up cached files that would normally be detected as deleted + ;(mockCacheManager.getAllHashes as any).mockReturnValue({ "old/file.js": "old-hash" }) + + // Create an already-aborted signal + const controller = new AbortController() + controller.abort() + + await scanner.scanDirectory("/test", undefined, undefined, undefined, controller.signal) + + // Deleted file cleanup should not have run + expect(mockVectorStore.deletePointsByFilePath).not.toHaveBeenCalled() + }) }) }) diff --git a/src/services/code-index/processors/scanner.ts b/src/services/code-index/processors/scanner.ts index 91689a56d7..5d9ff5e362 100644 --- a/src/services/code-index/processors/scanner.ts +++ b/src/services/code-index/processors/scanner.ts @@ -71,6 +71,7 @@ export class DirectoryScanner implements IDirectoryScanner { onError?: (error: Error) => void, onBlocksIndexed?: (indexedCount: number) => void, onFileParsed?: (fileBlockCount: number) => void, + signal?: AbortSignal, ): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> { const directoryPath = directory // Capture workspace context at scan start @@ -127,6 +128,9 @@ export class DirectoryScanner implements IDirectoryScanner { // Process all files in parallel with concurrency control const parsePromises = supportedPaths.map((filePath) => parseLimiter(async () => { + // Check abort signal before processing each file + if (signal?.aborted) return + try { // Check file size const stats = await stat(filePath) @@ -173,10 +177,17 @@ export class DirectoryScanner implements IDirectoryScanner { addedBlocksFromFile = true // Check if batch threshold is met + // Check abort signal before dispatching batch + if (signal?.aborted) { + throw new DOMException("Indexing aborted", "AbortError") + } + if (currentBatchBlocks.length >= this.batchSegmentThreshold) { // Wait if we've reached the maximum pending batches while (pendingBatchCount >= MAX_PENDING_BATCHES) { - // Wait for at least one batch to complete + if (signal?.aborted) { + throw new DOMException("Indexing aborted", "AbortError") + } await Promise.race(activeBatchPromises) } @@ -235,6 +246,10 @@ export class DirectoryScanner implements IDirectoryScanner { await this.cacheManager.updateHash(filePath, currentFileHash) } } catch (error) { + // Re-throw AbortError — it's not a file processing error, just a user-initiated stop + if (error instanceof DOMException && error.name === "AbortError") { + throw error + } console.error(`Error processing file ${filePath} in workspace ${scanWorkspace}:`, error) TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)), @@ -258,6 +273,17 @@ export class DirectoryScanner implements IDirectoryScanner { // Wait for all parsing to complete await Promise.all(parsePromises) + // Check abort signal before processing remaining batch + if (signal?.aborted) { + return { + stats: { + processed: processedCount, + skipped: skippedCount, + }, + totalBlockCount, + } + } + // Process any remaining items in batch if (currentBatchBlocks.length > 0) { const release = await mutex.acquire() @@ -292,6 +318,17 @@ export class DirectoryScanner implements IDirectoryScanner { // Wait for all batch processing to complete await Promise.all(activeBatchPromises) + // Check abort signal before handling deleted files + if (signal?.aborted) { + return { + stats: { + processed: processedCount, + skipped: skippedCount, + }, + totalBlockCount, + } + } + // Handle deleted files const oldHashes = this.cacheManager.getAllHashes() for (const cachedFilePath of Object.keys(oldHashes)) { diff --git a/src/services/code-index/state-manager.ts b/src/services/code-index/state-manager.ts index 90257fdfb1..b678825147 100644 --- a/src/services/code-index/state-manager.ts +++ b/src/services/code-index/state-manager.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode" -export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" +export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" | "Stopping" export class CodeIndexStateManager { private _systemStatus: IndexingState = "Standby" @@ -58,6 +58,8 @@ export class CodeIndexStateManager { public reportBlockIndexingProgress(processedItems: number, totalItems: number): void { const progressChanged = processedItems !== this._processedItems || totalItems !== this._totalItems + // Don't override Stopping state with progress updates + if (this._systemStatus === "Stopping") return // Update if progress changes OR if the system wasn't already in 'Indexing' state if (progressChanged || this._systemStatus !== "Indexing") { this._processedItems = processedItems @@ -81,6 +83,8 @@ export class CodeIndexStateManager { public reportFileQueueProgress(processedFiles: number, totalFiles: number, currentFileBasename?: string): void { const progressChanged = processedFiles !== this._processedItems || totalFiles !== this._totalItems + // Don't override Stopping state with progress updates + if (this._systemStatus === "Stopping") return if (progressChanged || this._systemStatus !== "Indexing") { this._processedItems = processedFiles this._totalItems = totalFiles diff --git a/webview-ui/src/components/chat/CodeIndexPopover.tsx b/webview-ui/src/components/chat/CodeIndexPopover.tsx index 4fcf6406e3..763c243ec1 100644 --- a/webview-ui/src/components/chat/CodeIndexPopover.tsx +++ b/webview-ui/src/components/chat/CodeIndexPopover.tsx @@ -1590,6 +1590,58 @@ export const CodeIndexPopover: React.FC = ({ )} + {/* Auto-enable default */} + {currentSettings.codebaseIndexEnabled && ( +
+ + vscode.postMessage({ + type: "setAutoEnableDefault", + bool: e.target.checked, + }) + } + className="accent-vscode-focusBorder" + /> + +
+ )} + + {/* Workspace Toggle */} + {currentSettings.codebaseIndexEnabled && ( +
+ + vscode.postMessage({ + type: "toggleWorkspaceIndexing", + bool: e.target.checked, + }) + } + className="accent-vscode-focusBorder" + /> + +
+ )} + + {currentSettings.codebaseIndexEnabled && !indexingStatus.workspaceEnabled && ( +

+ {t("settings:codeIndex.workspaceDisabledMessage")} +

+ )} + {/* Action Buttons */}
@@ -1603,6 +1655,20 @@ export const CodeIndexPopover: React.FC = ({ )} + {currentSettings.codebaseIndexEnabled && indexingStatus.systemStatus === "Indexing" && ( + + )} + + {currentSettings.codebaseIndexEnabled && indexingStatus.systemStatus === "Stopping" && ( + + )} + {currentSettings.codebaseIndexEnabled && (indexingStatus.systemStatus === "Indexed" || indexingStatus.systemStatus === "Error") && ( diff --git a/webview-ui/src/components/chat/IndexingStatusBadge.tsx b/webview-ui/src/components/chat/IndexingStatusBadge.tsx index 82f654a82f..227df3e645 100644 --- a/webview-ui/src/components/chat/IndexingStatusBadge.tsx +++ b/webview-ui/src/components/chat/IndexingStatusBadge.tsx @@ -64,6 +64,8 @@ export const IndexingStatusBadge: React.FC = ({ classN return t("chat:indexingStatus.indexing", { percentage: progressPercentage }) case "Indexed": return t("chat:indexingStatus.indexed") + case "Stopping": + return t("chat:indexingStatus.stopping") case "Error": return t("chat:indexingStatus.error") default: @@ -76,6 +78,7 @@ export const IndexingStatusBadge: React.FC = ({ classN Standby: "bg-vscode-descriptionForeground/60", Indexing: "bg-yellow-500 animate-pulse", Indexed: "bg-green-500", + Stopping: "bg-amber-500 animate-pulse", Error: "bg-red-500", } diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 230d730b13..c0fe2e0abf 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -428,7 +428,8 @@ "indexing": "Indexant {{percentage}}%", "indexed": "Indexat", "error": "Error d'índex", - "status": "Estat de l'índex" + "status": "Estat de l'índex", + "stopping": "Aturant la indexació..." }, "versionIndicator": { "ariaLabel": "Versió {{version}} - Feu clic per veure les notes de llançament" diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index d1edaf3deb..a741d9a3d7 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Restablir al valor per defecte (0.4)", "searchMaxResultsLabel": "Màxim de resultats de cerca", "searchMaxResultsDescription": "Nombre màxim de resultats de cerca a retornar quan es consulta l'índex de la base de codi. Els valors més alts proporcionen més context però poden incloure resultats menys rellevants.", - "resetToDefault": "Restablir al valor per defecte" + "resetToDefault": "Restablir al valor per defecte", + "stopIndexingButton": "Aturar indexació", + "stoppingButton": "Aturant...", + "workspaceToggleLabel": "Activar la indexació per a aquest espai de treball", + "workspaceDisabledMessage": "La indexació està configurada però no habilitada per a aquest espai de treball.", + "autoEnableDefaultLabel": "Habilitar automàticament la indexació per a nous espais de treball" }, "autoApprove": { "toggleShortcut": "Pots configurar una drecera global per a aquesta configuració a les preferències del teu IDE.", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 480d3197cd..c8aaeac4a6 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -428,7 +428,8 @@ "indexing": "Indizierung {{percentage}}%", "indexed": "Indiziert", "error": "Index-Fehler", - "status": "Index-Status" + "status": "Index-Status", + "stopping": "Indexierung wird gestoppt..." }, "versionIndicator": { "ariaLabel": "Version {{version}} - Klicken Sie, um die Versionshinweise anzuzeigen" diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index f244a09677..aed7867d80 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Auf Standardwert zurücksetzen (0.4)", "searchMaxResultsLabel": "Maximale Suchergebnisse", "searchMaxResultsDescription": "Maximale Anzahl von Suchergebnissen, die bei der Abfrage des Codebase-Index zurückgegeben werden. Höhere Werte bieten mehr Kontext, können aber weniger relevante Ergebnisse enthalten.", - "resetToDefault": "Auf Standard zurücksetzen" + "resetToDefault": "Auf Standard zurücksetzen", + "stopIndexingButton": "Indexierung stoppen", + "stoppingButton": "Wird gestoppt...", + "workspaceToggleLabel": "Indexierung für diesen Arbeitsbereich aktivieren", + "workspaceDisabledMessage": "Indexierung ist konfiguriert, aber nicht für diesen Arbeitsbereich aktiviert.", + "autoEnableDefaultLabel": "Indexierung für neue Arbeitsbereiche automatisch aktivieren" }, "autoApprove": { "toggleShortcut": "Du kannst in deinen IDE-Einstellungen einen globalen Shortcut für diese Einstellung konfigurieren.", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 10ef7c8811..4330895260 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -424,7 +424,8 @@ "indexing": "Indexing {{percentage}}%", "indexed": "Indexed", "error": "Index error", - "status": "Index status" + "status": "Index status", + "stopping": "Stopping indexing..." }, "versionIndicator": { "ariaLabel": "Version {{version}} - Click to view release notes" diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index a54184878c..af825fafe8 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -274,7 +274,12 @@ "baseUrlRequired": "Base URL is required", "modelDimensionMinValue": "Model dimension must be greater than 0" }, - "optional": "optional" + "optional": "optional", + "stopIndexingButton": "Stop Indexing", + "stoppingButton": "Stopping...", + "workspaceToggleLabel": "Enable indexing for this workspace", + "workspaceDisabledMessage": "Indexing is configured but not enabled for this workspace.", + "autoEnableDefaultLabel": "Auto-enable indexing for new workspaces" }, "autoApprove": { "description": "Run these actions without asking for permission. Only enable for actions you fully trust and if you understand the security risks.", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index b758c4eae2..d0651ec356 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -428,7 +428,8 @@ "indexing": "Indexando {{percentage}}%", "indexed": "Indexado", "error": "Error de índice", - "status": "Estado del índice" + "status": "Estado del índice", + "stopping": "Deteniendo la indexación..." }, "versionIndicator": { "ariaLabel": "Versión {{version}} - Haz clic para ver las notas de la versión" diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 397d2e3e36..946a6f87c0 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Restablecer al valor predeterminado (0.4)", "searchMaxResultsLabel": "Resultados máximos de búsqueda", "searchMaxResultsDescription": "Número máximo de resultados de búsqueda a devolver al consultar el índice de código. Valores más altos proporcionan más contexto pero pueden incluir resultados menos relevantes.", - "resetToDefault": "Restablecer al valor predeterminado" + "resetToDefault": "Restablecer al valor predeterminado", + "stopIndexingButton": "Detener indexación", + "stoppingButton": "Deteniendo...", + "workspaceToggleLabel": "Activar indexación para este espacio de trabajo", + "workspaceDisabledMessage": "La indexación está configurada pero no habilitada para este espacio de trabajo.", + "autoEnableDefaultLabel": "Habilitar automáticamente la indexación para nuevos espacios de trabajo" }, "autoApprove": { "toggleShortcut": "Puedes configurar un atajo global para esta configuración en las preferencias de tu IDE.", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 9080c3d29a..31177ea137 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -428,7 +428,8 @@ "indexing": "Indexation {{percentage}}%", "indexed": "Indexé", "error": "Erreur d'index", - "status": "Statut de l'index" + "status": "Statut de l'index", + "stopping": "Arrêt de l'indexation..." }, "versionIndicator": { "ariaLabel": "Version {{version}} - Cliquez pour voir les notes de version" diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index cac40e04cc..c833ed7950 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Réinitialiser à la valeur par défaut (0.4)", "searchMaxResultsLabel": "Résultats de recherche maximum", "searchMaxResultsDescription": "Nombre maximum de résultats de recherche à retourner lors de l'interrogation de l'index de code. Des valeurs plus élevées fournissent plus de contexte mais peuvent inclure des résultats moins pertinents.", - "resetToDefault": "Réinitialiser par défaut" + "resetToDefault": "Réinitialiser par défaut", + "stopIndexingButton": "Arrêter l'indexation", + "stoppingButton": "Arrêt en cours...", + "workspaceToggleLabel": "Activer l'indexation pour cet espace de travail", + "workspaceDisabledMessage": "L'indexation est configurée mais non activée pour cet espace de travail.", + "autoEnableDefaultLabel": "Activer automatiquement l'indexation pour les nouveaux espaces de travail" }, "autoApprove": { "toggleShortcut": "Vous pouvez configurer un raccourci global pour ce paramètre dans les préférences de votre IDE.", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index e4226e5ad5..3ce4f0a45b 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -428,7 +428,8 @@ "indexing": "इंडेक्सिंग {{percentage}}%", "indexed": "इंडेक्स किया गया", "error": "इंडेक्स त्रुटि", - "status": "इंडेक्स स्थिति" + "status": "इंडेक्स स्थिति", + "stopping": "इंडेक्सिंग रोक रहा है..." }, "versionIndicator": { "ariaLabel": "संस्करण {{version}} - रिलीज़ नोट्स देखने के लिए क्लिक करें" diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2131512a18..9c20bd4457 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "डिफ़ॉल्ट मान पर रीसेट करें (0.4)", "searchMaxResultsLabel": "अधिकतम खोज परिणाम", "searchMaxResultsDescription": "कोडबेस इंडेक्स को क्वेरी करते समय वापस करने के लिए खोज परिणामों की अधिकतम संख्या। उच्च मान अधिक संदर्भ प्रदान करते हैं लेकिन कम प्रासंगिक परिणाम शामिल कर सकते हैं।", - "resetToDefault": "डिफ़ॉल्ट पर रीसेट करें" + "resetToDefault": "डिफ़ॉल्ट पर रीसेट करें", + "stopIndexingButton": "इंडेक्सिंग रोकें", + "stoppingButton": "रोक रहा है...", + "workspaceToggleLabel": "इस वर्कस्पेस के लिए इंडेक्सिंग सक्षम करें", + "workspaceDisabledMessage": "इंडेक्सिंग कॉन्फ़िगर की गई है लेकिन इस वर्कस्पेस के लिए सक्षम नहीं है।", + "autoEnableDefaultLabel": "नए वर्कस्पेस के लिए स्वचालित रूप से इंडेक्सिंग सक्षम करें" }, "autoApprove": { "toggleShortcut": "आप अपनी आईडीई वरीयताओं में इस सेटिंग के लिए एक वैश्विक शॉर्टकट कॉन्फ़िगर कर सकते हैं।", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 10b13bcb5b..aa576cf054 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -434,7 +434,8 @@ "indexing": "Mengindeks {{percentage}}%", "indexed": "Terindeks", "error": "Error indeks", - "status": "Status indeks" + "status": "Status indeks", + "stopping": "Menghentikan pengindeksan..." }, "versionIndicator": { "ariaLabel": "Versi {{version}} - Klik untuk melihat catatan rilis" diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 878abad259..6320d2bb34 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Reset ke nilai default (0.4)", "searchMaxResultsLabel": "Hasil Pencarian Maksimum", "searchMaxResultsDescription": "Jumlah maksimum hasil pencarian yang dikembalikan saat melakukan query indeks basis kode. Nilai yang lebih tinggi memberikan lebih banyak konteks tetapi mungkin menyertakan hasil yang kurang relevan.", - "resetToDefault": "Reset ke default" + "resetToDefault": "Reset ke default", + "stopIndexingButton": "Hentikan pengindeksan", + "stoppingButton": "Menghentikan...", + "workspaceToggleLabel": "Aktifkan pengindeksan untuk ruang kerja ini", + "workspaceDisabledMessage": "Pengindeksan dikonfigurasi tetapi tidak diaktifkan untuk ruang kerja ini.", + "autoEnableDefaultLabel": "Aktifkan pengindeksan secara otomatis untuk ruang kerja baru" }, "autoApprove": { "toggleShortcut": "Anda dapat mengonfigurasi pintasan global untuk pengaturan ini di preferensi IDE Anda.", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 9d4b03bd73..c3da510ac3 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -428,7 +428,8 @@ "indexing": "Indicizzazione {{percentage}}%", "indexed": "Indicizzato", "error": "Errore indice", - "status": "Stato indice" + "status": "Stato indice", + "stopping": "Interruzione dell'indicizzazione..." }, "versionIndicator": { "ariaLabel": "Versione {{version}} - Clicca per visualizzare le note di rilascio" diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 062c2119f2..4b29c33247 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Ripristina al valore predefinito (0.4)", "searchMaxResultsLabel": "Risultati di ricerca massimi", "searchMaxResultsDescription": "Numero massimo di risultati di ricerca da restituire quando si interroga l'indice del codice. Valori più alti forniscono più contesto ma possono includere risultati meno pertinenti.", - "resetToDefault": "Ripristina al valore predefinito" + "resetToDefault": "Ripristina al valore predefinito", + "stopIndexingButton": "Interrompi indicizzazione", + "stoppingButton": "Interruzione...", + "workspaceToggleLabel": "Abilita l'indicizzazione per questo workspace", + "workspaceDisabledMessage": "L'indicizzazione è configurata ma non abilitata per questo workspace.", + "autoEnableDefaultLabel": "Abilita automaticamente l'indicizzazione per i nuovi workspace" }, "autoApprove": { "toggleShortcut": "Puoi configurare una scorciatoia globale per questa impostazione nelle preferenze del tuo IDE.", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index a648393df7..1746aff184 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -428,7 +428,8 @@ "indexing": "インデックス作成中 {{percentage}}%", "indexed": "インデックス作成済み", "error": "インデックスエラー", - "status": "インデックス状態" + "status": "インデックス状態", + "stopping": "インデックス作成を停止中..." }, "versionIndicator": { "ariaLabel": "バージョン {{version}} - クリックしてリリースノートを表示" diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 468aa1b95a..3aab3c7962 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "デフォルト値(0.4)にリセット", "searchMaxResultsLabel": "最大検索結果数", "searchMaxResultsDescription": "コードベースインデックスをクエリする際に返される検索結果の最大数。値を高くするとより多くのコンテキストが提供されますが、関連性の低い結果が含まれる可能性があります。", - "resetToDefault": "デフォルトにリセット" + "resetToDefault": "デフォルトにリセット", + "stopIndexingButton": "インデックス作成を停止", + "stoppingButton": "停止中...", + "workspaceToggleLabel": "このワークスペースのインデックス作成を有効にする", + "workspaceDisabledMessage": "インデックス作成は設定済みですが、このワークスペースでは有効になっていません。", + "autoEnableDefaultLabel": "新しいワークスペースのインデックス作成を自動的に有効にする" }, "autoApprove": { "toggleShortcut": "IDEの環境設定で、この設定のグローバルショートカットを設定できます。", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 8d686b9496..db322a30e5 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -428,7 +428,8 @@ "indexing": "인덱싱 중 {{percentage}}%", "indexed": "인덱싱 완료", "error": "인덱스 오류", - "status": "인덱스 상태" + "status": "인덱스 상태", + "stopping": "인덱싱 중지 중..." }, "versionIndicator": { "ariaLabel": "버전 {{version}} - 릴리스 노트를 보려면 클릭하세요" diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index ab5b993e77..7a522e5706 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "기본값(0.4)으로 재설정", "searchMaxResultsLabel": "최대 검색 결과", "searchMaxResultsDescription": "코드베이스 인덱스를 쿼리할 때 반환할 최대 검색 결과 수입니다. 값이 높을수록 더 많은 컨텍스트를 제공하지만 관련성이 낮은 결과가 포함될 수 있습니다.", - "resetToDefault": "기본값으로 재설정" + "resetToDefault": "기본값으로 재설정", + "stopIndexingButton": "인덱싱 중지", + "stoppingButton": "중지 중...", + "workspaceToggleLabel": "이 워크스페이스에 대한 인덱싱 활성화", + "workspaceDisabledMessage": "인덱싱이 구성되었지만 이 워크스페이스에서는 활성화되지 않았습니다.", + "autoEnableDefaultLabel": "새 워크스페이스에 대한 인덱싱 자동 활성화" }, "autoApprove": { "toggleShortcut": "IDE 환경 설정에서 이 설정에 대한 전역 바로 가기를 구성할 수 있습니다.", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index eb134cfca4..84cddcdac7 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -428,7 +428,8 @@ "indexing": "Indexeren {{percentage}}%", "indexed": "Geïndexeerd", "error": "Index fout", - "status": "Index status" + "status": "Index status", + "stopping": "Indexering wordt gestopt..." }, "versionIndicator": { "ariaLabel": "Versie {{version}} - Klik om release notes te bekijken" diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 8e0d7a8b55..854376b2fd 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Reset naar standaardwaarde (0.4)", "searchMaxResultsLabel": "Maximum Zoekresultaten", "searchMaxResultsDescription": "Maximum aantal zoekresultaten dat wordt geretourneerd bij het doorzoeken van de codebase-index. Hogere waarden bieden meer context maar kunnen minder relevante resultaten bevatten.", - "resetToDefault": "Reset naar standaard" + "resetToDefault": "Reset naar standaard", + "stopIndexingButton": "Indexering stoppen", + "stoppingButton": "Stoppen...", + "workspaceToggleLabel": "Indexering inschakelen voor deze werkruimte", + "workspaceDisabledMessage": "Indexering is geconfigureerd maar niet ingeschakeld voor deze werkruimte.", + "autoEnableDefaultLabel": "Indexering automatisch inschakelen voor nieuwe werkruimtes" }, "autoApprove": { "toggleShortcut": "U kunt een globale sneltoets voor deze instelling configureren in de voorkeuren van uw IDE.", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index d2ee83879f..c59a410cf3 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -428,7 +428,8 @@ "indexing": "Indeksowanie {{percentage}}%", "indexed": "Zaindeksowane", "error": "Błąd indeksu", - "status": "Status indeksu" + "status": "Status indeksu", + "stopping": "Zatrzymywanie indeksowania..." }, "versionIndicator": { "ariaLabel": "Wersja {{version}} - Kliknij, aby wyświetlić informacje o wydaniu" diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index b064eeabcc..85094cabfb 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Zresetuj do wartości domyślnej (0.4)", "searchMaxResultsLabel": "Maksymalna liczba wyników wyszukiwania", "searchMaxResultsDescription": "Maksymalna liczba wyników wyszukiwania zwracanych podczas zapytania do indeksu bazy kodu. Wyższe wartości zapewniają więcej kontekstu, ale mogą zawierać mniej istotne wyniki.", - "resetToDefault": "Przywróć domyślne" + "resetToDefault": "Przywróć domyślne", + "stopIndexingButton": "Zatrzymaj indeksowanie", + "stoppingButton": "Zatrzymywanie...", + "workspaceToggleLabel": "Włącz indeksowanie dla tego workspace'a", + "workspaceDisabledMessage": "Indeksowanie jest skonfigurowane, ale nie włączone dla tego workspace'a.", + "autoEnableDefaultLabel": "Automatycznie włączaj indeksowanie dla nowych workspace'ów" }, "autoApprove": { "toggleShortcut": "Możesz skonfigurować globalny skrót dla tego ustawienia w preferencjach swojego IDE.", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 2c339defd4..72e463311f 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -428,7 +428,8 @@ "indexing": "Indexando {{percentage}}%", "indexed": "Indexado", "error": "Erro do índice", - "status": "Status do índice" + "status": "Status do índice", + "stopping": "Parando a indexação..." }, "versionIndicator": { "ariaLabel": "Versão {{version}} - Clique para ver as notas de lançamento" diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 01a72ce29c..3a59ce226a 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Redefinir para o valor padrão (0.4)", "searchMaxResultsLabel": "Resultados máximos de busca", "searchMaxResultsDescription": "Número máximo de resultados de busca a retornar ao consultar o índice de código. Valores mais altos fornecem mais contexto, mas podem incluir resultados menos relevantes.", - "resetToDefault": "Redefinir para o padrão" + "resetToDefault": "Redefinir para o padrão", + "stopIndexingButton": "Parar indexação", + "stoppingButton": "Parando...", + "workspaceToggleLabel": "Ativar indexação para este workspace", + "workspaceDisabledMessage": "A indexação está configurada, mas não ativada para este workspace.", + "autoEnableDefaultLabel": "Ativar indexação automaticamente para novos workspaces" }, "autoApprove": { "toggleShortcut": "Você pode configurar um atalho global para esta configuração nas preferências do seu IDE.", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index fd3718f28a..20c658bc9e 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -429,7 +429,8 @@ "indexing": "Индексация {{percentage}}%", "indexed": "Проиндексировано", "error": "Ошибка индекса", - "status": "Статус индекса" + "status": "Статус индекса", + "stopping": "Остановка индексации..." }, "versionIndicator": { "ariaLabel": "Версия {{version}} - Нажмите, чтобы просмотреть примечания к выпуску" diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index e0abd7dd5e..7b7197d956 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Сбросить к значению по умолчанию (0.4)", "searchMaxResultsLabel": "Максимальное количество результатов поиска", "searchMaxResultsDescription": "Максимальное количество результатов поиска, возвращаемых при запросе индекса кодовой базы. Более высокие значения предоставляют больше контекста, но могут включать менее релевантные результаты.", - "resetToDefault": "Сбросить к значению по умолчанию" + "resetToDefault": "Сбросить к значению по умолчанию", + "stopIndexingButton": "Остановить индексацию", + "stoppingButton": "Остановка...", + "workspaceToggleLabel": "Включить индексацию для этого рабочего пространства", + "workspaceDisabledMessage": "Индексация настроена, но не включена для этого рабочего пространства.", + "autoEnableDefaultLabel": "Автоматически включать индексацию для новых рабочих пространств" }, "autoApprove": { "toggleShortcut": "Вы можете настроить глобальное сочетание клавиш для этого параметра в настройках вашей IDE.", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 2a6b776f78..a0dbd54083 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -429,7 +429,8 @@ "indexing": "İndeksleniyor {{percentage}}%", "indexed": "İndekslendi", "error": "İndeks hatası", - "status": "İndeks durumu" + "status": "İndeks durumu", + "stopping": "İndeksleme durduruluyor..." }, "versionIndicator": { "ariaLabel": "Sürüm {{version}} - Sürüm notlarını görüntülemek için tıklayın" diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 21d037c2ce..766b829964 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Varsayılan değere sıfırla (0.4)", "searchMaxResultsLabel": "Maksimum Arama Sonuçları", "searchMaxResultsDescription": "Kod tabanı dizinini sorgularken döndürülecek maksimum arama sonucu sayısı. Daha yüksek değerler daha fazla bağlam sağlar ancak daha az alakalı sonuçlar içerebilir.", - "resetToDefault": "Varsayılana sıfırla" + "resetToDefault": "Varsayılana sıfırla", + "stopIndexingButton": "İndekslemeyi durdur", + "stoppingButton": "Durduruluyor...", + "workspaceToggleLabel": "Bu çalışma alanı için indekslemeyi etkinleştir", + "workspaceDisabledMessage": "İndeksleme yapılandırıldı ancak bu çalışma alanı için etkinleştirilmedi.", + "autoEnableDefaultLabel": "Yeni çalışma alanları için indekslemeyi otomatik etkinleştir" }, "autoApprove": { "toggleShortcut": "IDE tercihlerinizde bu ayar için genel bir kısayol yapılandırabilirsiniz.", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 8e3841f334..eafd28c90b 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -429,7 +429,8 @@ "indexing": "Đang lập chỉ mục {{percentage}}%", "indexed": "Đã lập chỉ mục", "error": "Lỗi chỉ mục", - "status": "Trạng thái chỉ mục" + "status": "Trạng thái chỉ mục", + "stopping": "Đang dừng lập chỉ mục..." }, "versionIndicator": { "ariaLabel": "Phiên bản {{version}} - Nhấp để xem ghi chú phát hành" diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 776d17ffa5..fd2fd64885 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "Đặt lại về giá trị mặc định (0.4)", "searchMaxResultsLabel": "Số Kết Quả Tìm Kiếm Tối Đa", "searchMaxResultsDescription": "Số lượng kết quả tìm kiếm tối đa được trả về khi truy vấn chỉ mục cơ sở mã. Giá trị cao hơn cung cấp nhiều ngữ cảnh hơn nhưng có thể bao gồm các kết quả ít liên quan hơn.", - "resetToDefault": "Đặt lại về mặc định" + "resetToDefault": "Đặt lại về mặc định", + "stopIndexingButton": "Dừng lập chỉ mục", + "stoppingButton": "Đang dừng...", + "workspaceToggleLabel": "Bật lập chỉ mục cho không gian làm việc này", + "workspaceDisabledMessage": "Lập chỉ mục đã được cấu hình nhưng chưa được bật cho không gian làm việc này.", + "autoEnableDefaultLabel": "Tự động bật lập chỉ mục cho không gian làm việc mới" }, "autoApprove": { "toggleShortcut": "Bạn có thể định cấu hình một phím tắt chung cho cài đặt này trong tùy chọn IDE của bạn.", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 012797cc51..3b92b7f1c3 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -429,7 +429,8 @@ "indexing": "索引中 {{percentage}}%", "indexed": "已索引", "error": "索引错误", - "status": "索引状态" + "status": "索引状态", + "stopping": "正在停止索引..." }, "versionIndicator": { "ariaLabel": "版本 {{version}} - 点击查看发布说明" diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index baa4c1c1f3..40d0f4eda3 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -211,7 +211,12 @@ "searchMinScoreResetTooltip": "恢复默认值 (0.4)", "searchMaxResultsLabel": "最大搜索结果数", "searchMaxResultsDescription": "查询代码库索引时返回的最大搜索结果数。较高的值提供更多上下文,但可能包含相关性较低的结果。", - "resetToDefault": "恢复默认值" + "resetToDefault": "恢复默认值", + "stopIndexingButton": "停止索引", + "stoppingButton": "正在停止...", + "workspaceToggleLabel": "为此工作区启用索引", + "workspaceDisabledMessage": "索引已配置,但尚未为此工作区启用。", + "autoEnableDefaultLabel": "自动为新工作区启用索引" }, "autoApprove": { "toggleShortcut": "您可以在 IDE 首选项中为此设置配置全局快捷方式。", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 4acb77715a..95c1ea51ca 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -427,7 +427,8 @@ "indexing": "索引中 {{percentage}}%", "indexed": "已索引", "error": "索引錯誤", - "status": "索引狀態" + "status": "索引狀態", + "stopping": "正在停止索引..." }, "versionIndicator": { "ariaLabel": "版本 {{version}} - 點選查看發布說明" diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 2c45d327b1..691873ef20 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -221,7 +221,12 @@ "baseUrlRequired": "需要基礎 URL", "modelDimensionMinValue": "模型維度必須大於 0" }, - "optional": "選用" + "optional": "選用", + "stopIndexingButton": "停止索引", + "stoppingButton": "正在停止...", + "workspaceToggleLabel": "為此工作區啟用索引", + "workspaceDisabledMessage": "索引已設定,但尚未為此工作區啟用。", + "autoEnableDefaultLabel": "自動為新工作區啟用索引" }, "autoApprove": { "description": "無需詢問許可即可執行下列動作。請僅在您完全信任且了解安全風險的情況下啟用此功能。",