From 4d4cfbfcd377f52b0518aa8f611eb3a1774b575a Mon Sep 17 00:00:00 2001 From: kirillk Date: Fri, 19 Jun 2026 16:28:19 -0400 Subject: [PATCH] fix(jetbrains): clean up workspace path followups --- .kilo/plans/basalt-dirt-fixes.md | 172 --------- .../plans/basalt-dirt-internal-api-removal.md | 222 ----------- ...etbrains-completion-no-match-fasttyping.md | 134 ------- .../jetbrains-empty-mention-completion.md | 162 -------- .kilo/plans/jetbrains-file-mention-parity.md | 60 --- .kilo/plans/jetbrains-mention-actions.md | 96 ----- .../jetbrains-mention-completion-flicker.md | 172 --------- ...etbrains-mention-completion-reliability.md | 243 ------------ .kilo/plans/jetbrains-mention-completion.md | 214 ----------- .../jetbrains-mention-file-search-refresh.md | 60 --- .../plans/jetbrains-mention-icons-priority.md | 59 --- .kilo/plans/jetbrains-mention-slash-input.md | 263 ------------- ...rains-prompt-mention-slash-highlighting.md | 214 ----------- .../jetbrains-root-mention-completions.md | 65 ---- .kilo/plans/jetbrains-slash-actions.md | 63 --- .kilo/plans/jetbrains-slash-aliases.md | 75 ---- .../jetbrains-slash-mention-completion.md | 363 ------------------ .../backend/rpc/KiloWorkspaceRpcApiImpl.kt | 7 +- .../backend/rpc/WorkspacePathScopingTest.kt | 23 +- 19 files changed, 19 insertions(+), 2648 deletions(-) delete mode 100644 .kilo/plans/basalt-dirt-fixes.md delete mode 100644 .kilo/plans/basalt-dirt-internal-api-removal.md delete mode 100644 .kilo/plans/jetbrains-completion-no-match-fasttyping.md delete mode 100644 .kilo/plans/jetbrains-empty-mention-completion.md delete mode 100644 .kilo/plans/jetbrains-file-mention-parity.md delete mode 100644 .kilo/plans/jetbrains-mention-actions.md delete mode 100644 .kilo/plans/jetbrains-mention-completion-flicker.md delete mode 100644 .kilo/plans/jetbrains-mention-completion-reliability.md delete mode 100644 .kilo/plans/jetbrains-mention-completion.md delete mode 100644 .kilo/plans/jetbrains-mention-file-search-refresh.md delete mode 100644 .kilo/plans/jetbrains-mention-icons-priority.md delete mode 100644 .kilo/plans/jetbrains-mention-slash-input.md delete mode 100644 .kilo/plans/jetbrains-prompt-mention-slash-highlighting.md delete mode 100644 .kilo/plans/jetbrains-root-mention-completions.md delete mode 100644 .kilo/plans/jetbrains-slash-actions.md delete mode 100644 .kilo/plans/jetbrains-slash-aliases.md delete mode 100644 .kilo/plans/jetbrains-slash-mention-completion.md diff --git a/.kilo/plans/basalt-dirt-fixes.md b/.kilo/plans/basalt-dirt-fixes.md deleted file mode 100644 index 6ebde7104f8..00000000000 --- a/.kilo/plans/basalt-dirt-fixes.md +++ /dev/null @@ -1,172 +0,0 @@ -# Plan: Address `basalt-dirt` review findings (JetBrains slash/mention completion) - -Companion to `.kilo/plans/basalt-dirt-review.md`. All work is in `packages/kilo-jetbrains/` -(Kilo-owned — no `kilocode_change` markers, no shared opencode edits). - -Decision captured: **remove `@terminal` until implemented** (keep `@git-changes`, which is fully wired). - -Execution order below is also the recommended priority. Each task lists files + intent; keep edits -minimal and follow the JetBrains `AGENTS.md` (single-word names, `@RequiresEdt` where applicable, -no test-only production methods, prefer pure helpers for testability). - ---- - -## Task 1 — Remove the half-wired `@terminal` mention - -Goal: no dead UX. `@terminal` must not be suggested, highlighted, or leave stub RPC plumbing. - -**Frontend** -- `session/ui/prompt/KiloPromptCompletionProvider.kt` - - In `mention()`: delete the `if ("terminal".startsWith(prefix …) && search.terminal)` block. - - In `highlights()`: change the special set from `setOf("terminal", "git-changes")` to - `setOf("git-changes")`. -- `session/SessionUi.kt` - - In `mentionParts()`: delete the `@terminal` block (and its `runBlocking { workspaces.terminalOutput(...) }`). -- `app/KiloWorkspaceService.kt`: delete `terminalOutput(...)`. -- `resources/messages/KiloBundle.properties`: delete `prompt.mention.terminal`. - -**Shared** -- `rpc/KiloWorkspaceRpcApi.kt`: delete `terminalOutput(directory)`. -- `rpc/dto/WorkspaceFileDto.kt`: remove the unused `terminal: Boolean = false` field from `FileSearchResultDto`. - -**Backend** -- `rpc/KiloWorkspaceRpcApiImpl.kt`: delete the `terminalOutput()` override. `searchFiles()` already - returns no `terminal` flag — confirm it still compiles after the DTO field removal. - -**Tests / fakes to update (compile breakers)** -- `testing/FakeWorkspaceRpcApi.kt`: remove `var terminalOutput` + the `terminalOutput()` override. -- `KiloPromptCompletionProviderTest.kt`: - - `test mention completion includes matching special items`: drop `terminal = true` and the - terminal assertion; keep `git-changes`. - - `test highlights special mentions without tracked paths`: assert only the `@git-changes` - highlight (recompute offsets for a text containing just `@git-changes`). -- `PromptPanelTest.kt`: in the three highlight tests, replace `@terminal` usages with `@git-changes` - (or a tracked file mention) so the highlight/clear/bounded assertions still exercise real spans. - ---- - -## Task 2 — Make the completion provider the single owner of command knowledge (de-dup) - -Goal: eliminate the triplicated client-command-name list and the duplicated `/name` parsing. - -- `session/ui/prompt/KiloPromptCompletionProvider.kt` - - Add `fun serverCommand(text: String): Pair?` that reuses `clientNames()` and - `workspace.state.value.commands` (same rules as today: leading `/`, non-blank name, not a client - name, must match a known server command; returns `name to args`). This centralizes the parsing - already echoed in `highlights()`. -- `session/SessionUi.kt` - - Promote the completion provider to a field (e.g. `private lateinit var completion` assigned in the - existing init block, or store it) so `sendPrompt()` can reach it. - - Replace `SessionUi.serverCommand(text)` with a call to `completion.serverCommand(text)`. - - **Delete `SessionUi.clientCommandNames()`** entirely. - -Result: client-action names exist once (in `slashActions()` → provider `actions` → `clientNames()`), -and `/name` routing logic lives once in the provider. - ---- - -## Task 3 — Extract pure mention-assembly helper + fix frontend `runBlocking` - -Goal: testable submit-side assembly and AGENTS-compliant blocking. - -- New pure helper (top-level functions or a small object in `session/ui/prompt/`, e.g. - `PromptMentionParts.kt`): - - `fun mentionFileParts(text: String, paths: Set, directory: String): List` - — the current tracked-path → `file` part logic (path resolve, `toUri()`, `source(...)`), pure. - - `fun gitChangesPart(text: String, diff: String?): PromptPartDto?` — builds the `data:` URL part - when `@git-changes` is present and `diff` is non-blank (current `dataPart`/`source` logic), pure. - - Move `dataPart()` and `source()` here (or keep private and expose only the two functions). -- `session/SessionUi.kt` `mentionParts()` becomes a thin wrapper: - - `mentionFileParts(text, paths, workspace.directory)` + - - `gitChangesPart(text, runBlockingCancellable { workspaces.gitChanges(workspace.directory) })`. - - **Replace `runBlocking` with `com.intellij.openapi.progress.runBlockingCancellable`** (still on the - pooled thread from `PromptPanel.submit`; cancellation/progress aware). Remove the `kotlinx.coroutines.runBlocking` import. -- While here, tighten the `@git-changes` match to require a word boundary (end-of-token), so - `@git-changes-foo` does not match. Apply the same boundary rule already used in `highlights()`. - ---- - -## Task 4 — Tests for `SessionController.command()` - -Goal: cover the new ~50-line controller method (mirrors `prompt()`). - -- `testing/FakeSessionRpcApi.kt`: add `var commandThrows: Exception? = null`; in `command(...)` - throw it before recording, so error paths are testable (mirrors `enhanceThrows`). The existing - `commands` list is the assertion target. -- New `controller/CommandLifecycleTest.kt` (extends `SessionControllerTestBase`), modeled on - `PromptLifecycleTest`: - - **New session**: `controller()` + ready app/workspace with a server command named e.g. `deploy`; - `edt { m.command("deploy", "prod") }`; `flush()`; assert one `rpc.commands` entry with - `command="deploy"`, `arguments="prod"`; assert a session was created and events subscribed. - - **Existing session**: from `prompted()`, run `m.command(...)`; assert it reuses the session id and - does not create a new one. - - **Telemetry**: assert `appRpc.telemetry` has `"Conversation Send Clicked"` with `source="command"` - and `"Conversation Message"` with `source="command"`. - - **Error path**: set `rpc.commandThrows`; assert `m.model.state is SessionState.Error` and a - `"Session Error"` telemetry event with `context="command"`. - ---- - -## Task 5 — Tests for submit-side assembly (uses the Task 3 helper) - -Goal: cover routing + part building without test-only production methods. - -- New `session/ui/prompt/PromptMentionPartsTest.kt` (plain unit test; `BasePlatformTestCase` only if - needed for `PromptPartDto`): - - `mentionFileParts`: tracked path present in text → one `file` part with absolute `file://` url, - `filename`, and `source.path`/`source.text` offsets; untracked path → no part; absolute path in - `paths` kept absolute. - - `gitChangesPart`: `@git-changes` present + non-blank diff → one `data:text/plain` part with - URL-encoded content (`+` → `%20`) and `source.uri="git-changes"`; null/blank diff → no part; - `@git-changes-foo` (no boundary) → no part. -- `serverCommand` routing: add a case to `KiloPromptCompletionProviderTest`: - - With a workspace command `deploy` and client action `new`: `serverCommand("/deploy x")` → - `"deploy" to "x"`; `serverCommand("/new")` → null (client action); `serverCommand("hi /deploy")` - → null (not leading); `serverCommand("/unknown")` → null. - ---- - -## Task 6 — Add the missing feature changeset - -- New `.changeset/jetbrains-prompt-completion.md`, bump `minor`: - - `"@kilocode/kilo-jetbrains": minor` - - User-facing summary, e.g.: "Add `/` slash commands and `@` file/git-changes mentions to the - JetBrains chat prompt with native completion." Imperative, no implementation detail. -- Leave the two existing `patch` changesets (`jetbrains-file-mention-parity`, - `jetbrains-stable-mention-completion`) as-is — they describe follow-up fixes. - ---- - -## Task 7 — Minor cleanups - -- `session/SessionUi.kt`: add `import ai.kilocode.client.plugin.KiloBundle` and replace the 8 - fully-qualified `ai.kilocode.client.plugin.KiloBundle.message(...)` calls in `slashActions()` with - `KiloBundle.message(...)`. -- `session/ui/prompt/PromptPanel.kt` `showCompletion()`: add a short comment noting it relies on - IntelliJ impl/internal completion APIs (`CodeCompletionHandlerBase`, `LookupImpl`, - `LookupPresentation`, `LookupPositionStrategy`) and may need revisiting on platform upgrades. - (Keep `@Suppress("UnstableApiUsage")` as-is.) - ---- - -## Out of scope (documented limitation, no change) - -- Manually typed `@path` mentions (no popup selection) still won't attach a file part — only - popup-inserted paths are tracked. This matches the original design decision; revisit only if - parity with VS Code's text-scan approach is requested. - ---- - -## Verification - -From `packages/kilo-jetbrains/` (requires Java 21 — check `java -version`; install via -`sdk install java 21-tem && sdk use java 21-tem` if missing): - -- `./gradlew typecheck` — compiles all Kotlin incl. terminal removal across shared/backend/frontend. -- `./gradlew test` (or targeted: `./gradlew test --tests '*KiloPromptCompletionProviderTest*'` - `--tests '*CommandLifecycleTest*'` `--tests '*PromptMentionPartsTest*'` `--tests '*PromptPanelTest*'`). -- Sanity: `bunx changeset status` (or confirm the new changeset parses) from repo root. - -Done when: `@terminal` is fully gone, `clientCommandNames()` is deleted, `command()` and -mention-assembly have tests, frontend uses `runBlockingCancellable`, and the `minor` changeset exists, -with typecheck + tests green. diff --git a/.kilo/plans/basalt-dirt-internal-api-removal.md b/.kilo/plans/basalt-dirt-internal-api-removal.md deleted file mode 100644 index 5e4700ef04e..00000000000 --- a/.kilo/plans/basalt-dirt-internal-api-removal.md +++ /dev/null @@ -1,222 +0,0 @@ -# Implementation plan — remove internal IntelliJ APIs from the JetBrains prompt completion - -Superseded note: Task 1 was intentionally not followed; `CodeCompletionHandlerBase` is retained for split-mode completion behavior. See `basalt-dirt-pr-review-v2.md` and `basalt-dirt-followup-fixes.md` for the current plan. - -Audience: an executing model that should follow these steps literally. Read-only research is already -done; the exact edits, rationale, and verification are below. Do **not** expand scope beyond the three -tasks. All paths are under `packages/kilo-jetbrains/`. - -## Goal - -Eliminate genuinely implementation-only IntelliJ APIs from the prompt slash/mention completion feature, -and bring the remaining unstable-but-public usage into AGENTS compliance (warn + isolate + degrade -gracefully). Keep behavior and all existing tests green. - -## Background (verified against `$INTELLIJ_REPO`, do not re-research) - -- `CodeCompletionHandlerBase` — `lang-impl`, implementation. Public replacement: perform the standard - `IdeActions.ACTION_CODE_COMPLETION` action against the editor (this is what `myFixture.completeBasic()` - does under the hood; runs synchronously in unit-test mode). -- `com.intellij.codeInsight.lookup.impl.LookupImpl` — `impl` package. Public super-interface - `com.intellij.codeInsight.lookup.LookupEx` (`analysis-api`) declares `getEditor()`, - `getPresentation()`, and `setPresentation(LookupPresentation)`. -- `LookupPresentation` / `LookupPositionStrategy` — public but `@ApiStatus.Experimental`. **Keep** (no - non-experimental way to force the lookup above the caret); just add a one-line warning comment. -- Backend `GotoFileModel`, `ChooseByNameViewModel`, `ChooseByNameInScopeItemProvider`, - `ChooseByNameWeightedItemProvider.filterElementsWithWeights`, `ChooseByNamePopup.getTransformedPattern`, - `FindSymbolParameters.wrap` — all plain `public` members in `lang-impl`. **None are `@Internal` or - `@Experimental`.** This is the proven Go-to-File / Search Everywhere engine. **Keep it**, do not - reimplement; harden with `@Suppress` + comment + a `LinkageError` fallback. - -## Scope - -- In scope: `frontend/.../session/ui/prompt/PromptPanel.kt`, - `backend/.../rpc/KiloWorkspaceRpcApiImpl.kt`. -- Out of scope (do **not** touch in this task): the larger refactor that deletes `triggerCompletion`/ - the custom completion-shortcut action (would require rewriting `PromptPanelTest`), the model-package - moves, the `Dispatchers.IO`/cache items, and any test files. No test edits are required by this plan. - ---- - -## Task 1 — Frontend: replace `CodeCompletionHandlerBase` with the public completion action - -File: `frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` - -The custom action `COMPLETION_ACTION_TEXT = "Kilo Prompt Completion"`, `triggerCompletion`, and the -shortcut machinery all stay (tests depend on them). Only the body of `showCompletion` changes. - -1.1 Rewrite `showCompletion` (currently ~lines 550-554): - -```kotlin - private fun showCompletion(ed: com.intellij.openapi.editor.Editor) { - // Uses IntelliJ impl/internal completion APIs; revisit on platform upgrades. - CodeCompletionHandlerBase.createHandler(CompletionType.BASIC, true, false, true) - .invokeCompletion(project, ed, 1) - } -``` - -to: - -```kotlin - private fun showCompletion(ed: com.intellij.openapi.editor.Editor) { - // Trigger the standard public code-completion action against this editor instead of the - // impl-only CodeCompletionHandlerBase. Runs synchronously in unit-test mode. - val action = ActionManager.getInstance().getAction(IdeActions.ACTION_CODE_COMPLETION) ?: return - val ctx = DataContext { id -> - when (id) { - CommonDataKeys.EDITOR.name -> ed - CommonDataKeys.CARET.name -> ed.caretModel.currentCaret - CommonDataKeys.PROJECT.name -> project - else -> null - } - } - val event = AnActionEvent.createEvent(action, ctx, null, ActionPlaces.UNKNOWN, ActionUiKind.NONE, null) - ActionUtil.updateAction(action, event) - ActionUtil.performAction(action, event) - } -``` - -Rationale for the explicit `DataContext` lambda (not `DataManager.getDataContext`): it deterministically -supplies EDITOR/CARET/PROJECT and mirrors exactly the context the existing `PromptPanelTest` helpers use, -so the headless completion tests stay reliable. - -1.2 Imports — remove the two now-unused completion imports, add the two data-context imports: - -- Remove `import com.intellij.codeInsight.completion.CodeCompletionHandlerBase` -- Remove `import com.intellij.codeInsight.completion.CompletionType` -- Add `import com.intellij.openapi.actionSystem.CommonDataKeys` -- Add `import com.intellij.openapi.actionSystem.DataContext` - -Keep all other imports (`ActionManager`, `ActionUtil`, `AnActionEvent`, `ActionPlaces`, `ActionUiKind`, -`IdeActions`, `DataManager`, `DumbAwareAction`, `AnAction` are still used elsewhere in the file). - ---- - -## Task 2 — Frontend: replace `LookupImpl` with public `LookupEx` - -File: `frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` - -2.1 In `bindLookup` (currently ~lines 715-727), change the cast and add the experimental-API note: - -```kotlin - connection.subscribe(LookupManagerListener.TOPIC, LookupManagerListener { _, next -> - val lookup = next as? LookupImpl ?: return@LookupManagerListener - if (lookup.editor !== editor.getEditor(false)) return@LookupManagerListener - lookup.presentation = LookupPresentation.Builder(lookup.presentation) - .withPositionStrategy(LookupPositionStrategy.ONLY_ABOVE) - .build() - }) -``` - -to: - -```kotlin - connection.subscribe(LookupManagerListener.TOPIC, LookupManagerListener { _, next -> - // LookupPresentation/LookupPositionStrategy are @ApiStatus.Experimental (no stable API - // forces the lookup above the caret). LookupEx is the public lookup interface. - val lookup = next as? LookupEx ?: return@LookupManagerListener - if (lookup.editor !== editor.getEditor(false)) return@LookupManagerListener - lookup.presentation = LookupPresentation.Builder(lookup.presentation) - .withPositionStrategy(LookupPositionStrategy.ONLY_ABOVE) - .build() - }) -``` - -2.2 Imports: - -- Remove `import com.intellij.codeInsight.lookup.impl.LookupImpl` -- Add `import com.intellij.codeInsight.lookup.LookupEx` -- Keep `LookupManagerListener`, `LookupPresentation`, `LookupPositionStrategy` imports. - -Note: `PromptPanelTest` still references `LookupImpl` on the test side (`acceptLookup`, the -positioned-above-caret assertion). That is fine — tests may inspect the impl; do **not** change them. - ---- - -## Task 3 — Backend: keep the proven Go-to-File engine, harden it - -File: `backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt` - -3.1 Annotate the `search(...)` adapter (currently starts ~line 316) and document the unstable-API reliance: - -```kotlin - private fun search(project: Project, base: Path, query: String, limit: Int): List { -``` - -to: - -```kotlin - // Uses the IDE Go-to-File engine (com.intellij.ide.util.gotoByName.*). These are public but - // unstable lang-impl classes (not @ApiStatus.Internal) — the same engine behind Search Everywhere, - // chosen for proven large-repo performance. searchFiles() degrades gracefully on LinkageError. - @Suppress("UnstableApiUsage") - private fun search(project: Project, base: Path, query: String, limit: Int): List { -``` - -(If the DevKit inspection still flags the import statements at file scope after this, fall back to a -file-level `@file:Suppress("UnstableApiUsage")` at the top of the file. Prefer the function-level form.) - -3.2 Add a `LinkageError` fallback in `searchFiles` (currently ~lines 194-205) so an API drift in a future -platform degrades to "no suggestions" instead of crashing the prompt. Change: - -```kotlin - return try { - val files = readAction { search(project, base, query, limit.coerceIn(1, 200)) } - FileSearchResultDto(files = files, git = git) - } catch (e: IndexNotReadyException) { - FileSearchResultDto(indexing = true, git = git) - } -``` - -to: - -```kotlin - return try { - val files = readAction { search(project, base, query, limit.coerceIn(1, 200)) } - FileSearchResultDto(files = files, git = git) - } catch (e: IndexNotReadyException) { - FileSearchResultDto(indexing = true, git = git) - } catch (e: LinkageError) { - LOG.warn("file search API unavailable; returning no suggestions", e) - FileSearchResultDto(git = git) - } -``` - -No new imports needed (`LinkageError` is `java.lang`; `LOG` already exists in the class). - ---- - -## Verification (run from `packages/kilo-jetbrains/`, requires Java 21) - -1. Typecheck: `./gradlew typecheck` (or `bun run typecheck`). -2. Frontend tests (must pass unchanged): - - `./gradlew test --tests "ai.kilocode.client.session.ui.PromptPanelTest"` - - `./gradlew test --tests "ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest"` - - In particular these must stay green: `test prompt local completion shortcut opens mention lookup`, - `test prompt local completion shortcut opens slash lookup`, `test prompt completion lookup is - positioned above caret`, `test accepted file mention highlights immediately`. -3. Manual (run IDE): typing `@` and a leading `/` opens the popup; Ctrl+Space opens it; the popup renders - above the caret. Open DevKit → `Frontend and Backend API Usage` (and `Unstable API Usage`): the - frontend completion/lookup usages should no longer be flagged; the backend `search` usages are - acknowledged via `@Suppress` + comment. - -## Acceptance criteria - -- `PromptPanel.kt` (production) contains no references to `CodeCompletionHandlerBase`, completion - `CompletionType`, or `com.intellij.codeInsight.lookup.impl.LookupImpl`. -- `bindLookup` casts to `LookupEx`; lookup positioning behavior unchanged. -- `KiloWorkspaceRpcApiImpl.search` is `@Suppress("UnstableApiUsage")` with the explanatory comment; - `searchFiles` catches `LinkageError` and degrades. -- Typecheck and the listed tests pass; no test files were modified. - -## Risk + fallback - -- Primary risk: performing `ACTION_CODE_COMPLETION` via the `DataContext` lambda does not open the - lookup in the headless `PromptPanelTest` (e.g. the action's `update()` disables itself). -- Fallback (only if Task 1 tests fail): keep the action approach but additionally force autopopup — - in the editor settings provider (`addSettingsProvider { ed -> ... }`) add - `ed.putUserData(com.intellij.codeInsight.AutoPopupController.ALWAYS_AUTO_POPUP, true)`, and implement - `showCompletion` as `com.intellij.codeInsight.AutoPopupController.getInstance(project).scheduleAutoPopup(ed)`. - `ALWAYS_AUTO_POPUP` is a public `Key` and `scheduleAutoPopup(Editor)` is public (this is exactly how the - platform's `TextFieldWithCompletion(forceAutoPopup = true)` works). This is async, so rely on the - tests' existing `waitForLookupItems` polling. diff --git a/.kilo/plans/jetbrains-completion-no-match-fasttyping.md b/.kilo/plans/jetbrains-completion-no-match-fasttyping.md deleted file mode 100644 index 89b3ec31942..00000000000 --- a/.kilo/plans/jetbrains-completion-no-match-fasttyping.md +++ /dev/null @@ -1,134 +0,0 @@ -# JetBrains prompt completion follow-up: cancellation noise and explicit shortcut - -## Goal - -Finish the JetBrains prompt completion fix by addressing the two issues reported after the first implementation pass: - -1. Typing `@...` rapidly logs repeated `workspace file search failed ... IndicatorCancellationException` warnings. -2. Pressing the code-completion shortcut (`Control-Space` in the user's keymap) in the prompt editor does not reliably invoke slash/mention completion. - -The existing no-match placeholder, fast-typing lookup retention, and mid-token token detection changes stay in place. - -## Current state - -Already implemented in Kilo-owned JetBrains files: - -- `KiloPromptCompletionProvider` adds a non-destructive `No matches` info row for slash and mention no-results. -- Slash and mention result sets call `restartCompletionOnAnyPrefixChange()`. -- Mention indexing row uses the same no-op info-row behavior. -- `PromptPanel` sets `AutoPopupController.ALWAYS_AUTO_POPUP` on the prompt editor. -- `token()` now recognizes the full whitespace-bounded token around the caret, while `prefix` remains the text before the caret. -- Tests cover no-match placeholders, accepting placeholders, real matches, and mid-token explicit completion through the provider fixture. - -## New findings - -### Cancellation warnings while typing - -The warning stack is expected IntelliJ completion cancellation, not a real backend failure: - -- `KiloPromptCompletionProvider.search()` runs `service.searchFiles(...)` inside `runBlockingCancellable` during completion calculation. -- IntelliJ cancels stale completion work on each prefix change while the user types. -- IntelliJ source confirms `IndicatorCancellationException` extends `java.util.concurrent.CancellationException`, and `ProcessCanceledException` extends Kotlin/JVM `CancellationException`. -- `KiloWorkspaceService.searchFiles()` currently catches broad `Exception`, logs it as a warning, and returns `FileSearchResultDto()`. -- IntelliJ source explicitly says `ProcessCanceledException` should not be logged or swallowed. - -Conclusion: `searchFiles()` must not log completion/progress cancellation. It should rethrow cancellation before the generic failure catch so the completion infrastructure can discard stale results. - -### `Control-Space` in the prompt editor - -Provider tests prove `TextCompletionContributor` can invoke `KiloPromptCompletionProvider`, but the real prompt editor shortcut path can diverge: - -- `EditorTextField` and its content component already publish `CommonDataKeys.EDITOR`, so editor data context is probably not the primary issue. -- IntelliJ `BaseCodeCompletionAction` uses `ActionRemoteBehavior.FrontendOtherwiseBackend`. -- In remote/split frontend mode, completion can fall back to backend execution or use frontend RD completion filtering. -- IntelliJ `CompletionContributor.forLanguage()` filters contributors to `FrontendCompletionContributor` when RD frontend completion is enabled; platform `TextCompletionContributor` is not marked as such. -- Our prompt completion provider is frontend UI state installed through `TextCompletionUtil`, so relying on the global code-completion action is fragile in split/RD mode. - -Conclusion: keep using the existing prompt-local `showCompletion(editor)` path, but bind the active code-completion shortcut directly to it for the prompt editor component. This keeps the behavior frontend-local and avoids the RD contributor/action fallback path. - -## Implementation plan - -### 1. Stop logging stale completion cancellation - -File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloWorkspaceService.kt` - -- In `searchFiles(directory, query, limit)`, add a cancellation catch before the generic catch: - -```kotlin -} catch (e: CancellationException) { - throw e -} catch (e: Exception) { - LOG.warn(...) - FileSearchResultDto() -} -``` - -- Use the JVM/Kotlin cancellation type that catches both IntelliJ `IndicatorCancellationException` and `ProcessCanceledException` on this target. -- Do not add logging for cancellation. -- Leave non-cancellation failures unchanged: still warn and return `FileSearchResultDto()`. -- Do not catch cancellation inside `KiloPromptCompletionProvider.search()`; let completion infrastructure cancel stale calculations and avoid caching a cancelled result. - -Tests: - -- Add `KiloWorkspaceServiceTest` coverage where `FakeWorkspaceRpcApi.search` throws `CancellationException` and `service.searchFiles(...)` rethrows it. -- Keep existing behavior testable for non-cancellation failures if a small fake failure case is easy to add, but do not expand the scope unnecessarily. - -### 2. Bind explicit completion shortcut locally in `PromptPanel` - -File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` - -- Add a prompt-local action field, for example a private `AnAction?`, that calls `showCompletion(editor)` for the initialized prompt editor. -- When the editor is configured in `addSettingsProvider`, install this action on `ed.contentComponent` using the current shortcut set from `IdeActions.ACTION_CODE_COMPLETION`: - -```kotlin -val base = ActionManager.getInstance().getAction(IdeActions.ACTION_CODE_COMPLETION) -completionAction.registerCustomShortcutSet(base.shortcutSet, ed.contentComponent) -``` - -- Use `DumbAwareAction.create { ... }` or a small private action object; keep it local to `PromptPanel`. -- Only install this action when `completion != null`. -- Unregister the action in `removeNotify()` with `unregisterCustomShortcutSet(ed.contentComponent)` to avoid retaining disposed editor components. -- On keymap changes for `IdeActions.ACTION_CODE_COMPLETION`, unregister and reinstall so custom keymaps are respected. The existing `bindKeymap()` listener already watches shortcut changes; extend that path. -- Keep the existing document-listener trigger for inserted `@` and leading `/`, because native auto-popup still does not fire for those trigger chars. -- Keep `showCompletion()` unchanged for now, including the `ONLY_ABOVE` lookup positioning. It already uses the frontend-local handler path the manual `@`/`/` trigger relies on. - -Tests: - -- Add `PromptPanelTest` coverage with a real `PromptPanel` plus `KiloPromptCompletionProvider`: - - Realize the panel. - - Set the prompt editor text to `@dep` and place the caret at the end. - - Configure `rpc.searchResult` with `src/deploy.ts`. - - Invoke the registered prompt-local completion action and assert the active lookup contains `src/deploy.ts`. - - Repeat or add a slash case for `/ne` showing `new`. -- Prefer invoking the registered action via `ActionUtil.getActions(editor.contentComponent)` and an `AnActionEvent` built from the editor component data context. This verifies the component-local shortcut binding without depending on brittle headless key-event dispatch. -- If action ordering produces multiple registered actions, identify the prompt-local action by shortcut set and by invoking only actions registered on the editor content component after `PromptPanel` initialization. - -### 3. Re-run focused verification - -From `packages/kilo-jetbrains/`: - -- `./gradlew :frontend:test --tests ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest` -- `./gradlew :frontend:test --tests ai.kilocode.client.session.ui.PromptPanelTest` -- `./gradlew :frontend:test --tests ai.kilocode.client.app.KiloWorkspaceServiceTest` -- `./gradlew typecheck` - -Known pre-existing caveat: - -- Full `./gradlew test` was previously failing with unrelated frontend `NoSuchMethodError` failures in existing UI constructor tests. Do not treat that as introduced by this completion fix unless it changes shape after these edits. - -## Manual verification - -In a sandbox IDE: - -- Type `@d`, `@de`, `@dep` quickly. No `workspace file search failed ... IndicatorCancellationException` warnings should appear. -- Type an unmatched mention such as `@zzzz`; the popup should stay open with `No matches`. -- Put the caret in or after `@dep` and press the code-completion shortcut; mention completions should open. -- Type `/ne` and press the code-completion shortcut; slash completions should open. -- Confirm the popup still appears above the prompt editor for manual `@` and leading `/` triggers. - -## Scope and constraints - -- Touch only Kilo-owned JetBrains plugin files; no `kilocode_change` markers are needed. -- Do not introduce a new global completion contributor unless the prompt-local shortcut binding proves insufficient. -- Do not rewrite completion UI into a custom popup for this pass. -- Avoid swallowing IntelliJ cancellation exceptions; cancellation is control flow, not a user-visible failure. diff --git a/.kilo/plans/jetbrains-empty-mention-completion.md b/.kilo/plans/jetbrains-empty-mention-completion.md deleted file mode 100644 index 9605ff3e84f..00000000000 --- a/.kilo/plans/jetbrains-empty-mention-completion.md +++ /dev/null @@ -1,162 +0,0 @@ -# Fix JetBrains empty `@` mention completion (shows only "Searching files…") - -Scope: Kilo-owned frontend code under -`packages/kilo-jetbrains/frontend/.../session/ui/prompt/`. No shared/upstream files, so -no `kilocode_change` markers needed. - -## Symptom (from user) - -- Type `@` with no further typing → the lookup shows only `Searching files…` and never - fills with the root file/dir entries. -- Typing past `@` (e.g. `@backend`) works and shows results. "The rest works ok." -- Doing `@` a **second time** shows entries. - -## Root cause - -`KiloPromptCompletionProvider.mention("")` (`KiloPromptCompletionProvider.kt:165-188`) -runs a **non-blocking** search via `search("")` (`:190-199`): - -- `cache[""]` is empty on the first `@`, so it launches a debounced background fetch - (`refresh()`, `:207-220`) and renders the `Searching files…` placeholder. -- When the fetch returns it stores `cache[""]` and posts `onRefresh()` → - `PromptPanel.restartCompletion()` (`PromptPanel.kt:561-569`) which calls - `CompletionServiceImpl.currentCompletionProgressIndicator?.scheduleRestart()` to repaint - the open lookup in place. - -The **second** `@` works because `cache[""]` is now populated, so `search("")` returns -synchronously (`:191`) and the lookup is filled on first paint with no async restart. - -So the failing case is exactly the one that depends on the async -`onRefresh → restartCompletion → scheduleRestart()` repaint of an already-open lookup, -and that in-place repaint is the fragile/internal-API path. Typing past `@` does **not** -depend on it: each keystroke routes through the platform's own -`LookupTypedHandler.prefixUpdated()` → `scheduleRestart()` -(`$INTELLIJ_REPO/platform/lang-impl/.../lookup/impl/LookupTypedHandler.java:176-179`), -which re-runs `fillCompletionVariants` and warms/serves the cache reliably. The empty `@` -is the only path with no keystroke to drive that robust restart. - -### Investigation note (why we are not relying on a "race" theory) - -`@` is not a letter/digit/`_`, so `CompletionAutoPopupHandler.checkAutoPopup` returns -CONTINUE for it (`.../editorActions/CompletionAutoPopupHandler.java:43-50`) and -`TypedAutoPopupImpl` only auto-pops on `.`/`/`/contributor opt-in -(`.../editorActions/TypedAutoPopupImpl.java:30-37`). Typing `@` therefore never triggers -the platform autopopup; the popup is opened solely by our explicit `showCompletion` -(`PromptPanel.kt:551-559`, `invokeCompletion(project, ed, 1)` → `invocationCount == 1` → -`isAutopopupCompletion() == false`, `CompletionProgressIndicator.java:842-844`). So the -empty-`@` failure is **not** an autopopup-vs-explicit race on `@`. Removing -`ALWAYS_AUTO_POPUP` is correct hygiene but is not, by itself, a guaranteed fix — so we add -a deterministic cache prewarm. - -## Plan - -### Part 1 — Prewarm `cache[""]` (deterministic fix) - -Make the first `@` take the same path that already works the second time: serve the root -listing synchronously from a warm cache. - -**1a. Add `prewarm()` to `KiloPromptCompletionProvider`.** - -```kotlin -fun prewarm() { - if (cache.containsKey("")) return - scope.launch { - val result = service.searchFiles(workspace.directory, "", 50) - // Only pin a settled, useful result. Skip empty/failed/indexing results so a - // later `@` retries instead of caching a transient "nothing" state. - if (result.files.isNotEmpty() || result.git) cache.putIfAbsent("", result) - } -} -``` - -- Launches on the existing `scope` (off-EDT, satisfies `searchFiles`' not-EDT - requirement). No unit-test-mode early return, so it is directly testable. -- `cache[prefix]` is checked before everything else in `search()` - (`KiloPromptCompletionProvider.kt:191`), so once `cache[""]` is warm the first `@` - renders root entries on first paint with no placeholder and no async restart. -- Does not pin `indexing`/empty/failed results (`searchFiles` returns - `FileSearchResultDto()` on failure), avoiding a stuck "No matches"/empty cache. - -**1b. Re-warm at the right lifecycle points in `PromptPanel`.** - -- In `setReady(true)` (`PromptPanel.kt:304-308`): call `completion?.prewarm()` — the - workspace is ready so the backend can search. This warms the cache before the user's - first `@`. -- In `clear()` (`PromptPanel.kt:383-391`), after `completion?.clearMentions()` (which - empties the cache, `:68-76`): call `completion?.prewarm()` so the next `@` after a send - is warm again. - -`prewarm()` is idempotent (`containsKey("")` guard), so repeated `setReady(true)` / -`clear()` calls are cheap. - -### Part 2 — Remove `ALWAYS_AUTO_POPUP` (requested restructure) - -Drive completion solely through the explicit triggers we already own and the active -lookup's own prefix-change restart, so every completion in a session is explicit -(`invocationCount == 1`, non-autopopup) and `scheduleRestart()` always takes the -non-autopopup async path. - -**2a. Remove** `ed.putUserData(AutoPopupController.ALWAYS_AUTO_POPUP, true)` -(`PromptPanel.kt:172`) and the now-unused `import com.intellij.codeInsight.AutoPopupController` -(`:23`). - -Why this is safe (verified against `$INTELLIJ_REPO`): -- Opening: `triggerCompletion` opens the popup on a single-char `@` (anywhere) and a - leading `/` (`PromptPanel.kt:537-549`); the bound completion shortcut also calls - `showCompletion` (`installCompletionShortcut`, `:571-584`). -- Narrowing while open: typing routes through `LookupTypedHandler.prefixUpdated()` → - `scheduleRestart()` and `restartCompletionOnAnyPrefixChange()` - (`KiloPromptCompletionProvider.kt:152,166`) — independent of `ALWAYS_AUTO_POPUP`. -- All completions stay non-autopopup: `showCompletion` uses `invocationCount = 1`, and - restarts inherit it (`phase.indicator?.invocationCount`, - `.../completion/CompletionPhase.kt:339`). -- Net effect: removes wasted autopopup completion attempts on ordinary (non-`@`/`/`) - typing and guarantees the async `scheduleRestart()` never routes through the gated - autopopup path (`shouldSkipAutoPopup` / activity guards, `CompletionPhase.kt:285,326-333`). - -No behavior the tests depend on changes: `KiloPromptCompletionProviderTest` and -`PromptPanelTest` drive completion via `myFixture.completeBasic()` / the explicit -"Kilo Prompt Completion" action (`invokeCompletionAction`), neither of which uses -`ALWAYS_AUTO_POPUP`. - -## Tests - -- `KiloPromptCompletionProviderTest` (provider constructed directly, no panel → `prewarm` - is only called explicitly by the test, so `searchQueries` assertions in other tests are - unaffected). Add: - - **prewarm warms the empty-prefix cache:** set `rpc.searchResult` with a couple of root - entries, call `provider.prewarm()`, `waitFor { rpc.searchQueries.contains("") }`, then - `complete("@")` and assert the lookup contains those entries **and** - `assertEquals(listOf(""), rpc.searchQueries)` (the `@` was served from the prewarmed - cache, no second query). - - Existing `test blank mention completion includes special and root entries` and the - `searchQueries` expectations stay green (no prewarm there). -- `PromptPanelTest`: existing completion/highlight tests must stay green. `setReady(true)` - / `clear()` already appear in many tests; confirm none assert `searchQueries` (they do - not) so the added prewarm fetch is harmless. Optionally add a panel-level check that the - prewarm fetch is issued after `setReady(true)`. - -## Files to change - -- `frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt` — add `prewarm()`. -- `frontend/.../session/ui/prompt/PromptPanel.kt` — call `prewarm()` from `setReady(true)` - and `clear()`; remove `ALWAYS_AUTO_POPUP` and its import. -- `frontend/src/test/.../prompt/KiloPromptCompletionProviderTest.kt` — prewarm test. - -## Verification - -From `packages/kilo-jetbrains/`: `./gradlew :frontend:test` (targeted to the two prompt -test classes) and `./gradlew typecheck` (Java 21). Manual check in `./gradlew runIde`: -open a session, type `@` and wait — root files/dirs appear immediately (no stuck -`Searching files…`); type `@backend` — narrows correctly; send a prompt, type `@` again — -still immediate. - -## Decisions / tradeoffs - -1. **Prewarm is the primary, deterministic fix.** It reproduces the working "second time" - path on the first `@` and does not depend on the fragile in-place async restart. -2. **`ALWAYS_AUTO_POPUP` removal is included as requested** and is safe hygiene, but the - investigation shows `@` does not go through the platform autopopup, so it is paired with - prewarm rather than relied on alone. -3. Only settled, useful (`files.isNotEmpty() || git`) results are pinned in `cache[""]` to - avoid caching transient empty/indexing/failed states. diff --git a/.kilo/plans/jetbrains-file-mention-parity.md b/.kilo/plans/jetbrains-file-mention-parity.md deleted file mode 100644 index 94d78209b29..00000000000 --- a/.kilo/plans/jetbrains-file-mention-parity.md +++ /dev/null @@ -1,60 +0,0 @@ -# JetBrains File Mention Rendering Parity - -## Goal - -Make JetBrains user-message file mentions render like VS Code: show the user's original prompt and file reference, do not show the CLI's synthetic read-tool payload (``, ``, ``) as visible user text. - -## Findings - -- VS Code renders user messages through `packages/kilo-vscode/webview-ui/src/components/chat/VscodeUserMessage.tsx` and `packages/kilo-ui/src/components/message-part.tsx`. -- VS Code chooses the first non-synthetic text part for the visible prompt: `part.type === "text" && !part.synthetic`. -- VS Code keeps file parts available for user-message rendering, but text-file mentions are not shown as raw read output. -- The CLI expands text file parts in `packages/opencode/src/session/prompt.ts` into synthetic text parts containing read-tool input/output plus the original file part. -- JetBrains currently drops the `synthetic` part field in `PartDto` / `KiloCliDataParser.parsePart`, so synthetic read output is converted into normal `Text` and rendered by `MessageView` / `PromptView`. -- JetBrains already has `PromptAttachmentView` for user `FileAttachment` parts, but it cannot distinguish mention-derived text files from explicit text-file attachments because `PromptPartDto` does not carry `source` metadata. - -## Implementation Plan - -1. Preserve CLI part metadata in JetBrains DTOs. - - Add `synthetic: Boolean? = null` to `PartDto` in `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ChatDto.kt`. - - Add small serializable source DTOs for file-part source metadata, e.g. `PartSourceDto` and `PartSourceTextDto`. - - Add `source: PartSourceDto? = null` to both `PartDto` and `PromptPartDto`. - -2. Parse and serialize the new fields in the backend bridge. - - Update `KiloCliDataParser.parsePart(...)` to read top-level `synthetic` and `source` from CLI message parts. - - Update `KiloCliDataParser.buildPromptPartJson(...)` to include `source` for outgoing file prompt parts. - - Keep the existing `sanitizeUserPromptText(...)` fallback for older or malformed events that do not include `synthetic`. - - Add parser tests for `synthetic`, source parsing, and prompt JSON source serialization. - -3. Send source metadata for JetBrains mentions. - - Update `SessionUi.mentionParts(...)` so each `@path` file mention includes `source = { type: "file", path, text: { value: "@path", start, end } }`. - - Update `dataPart(...)` call sites for `@terminal` and `@git-changes` to include equivalent source text metadata when feasible. - - Use the exact mention token offsets from the submitted prompt so downstream rendering can distinguish mention-backed context from manually attached files. - -4. Hide synthetic user text in the JetBrains session model/rendering path. - - Add `synthetic` to the `Text` model or otherwise track hidden synthetic part IDs in `SessionModel`. - - In `SessionModel.updateContent(...)` and `loadHistory(...)`, skip user `text` parts where `dto.synthetic == true`. - - Ensure a later update that marks an existing user text part synthetic removes the visible content. - - Ensure deltas for already-hidden synthetic part IDs do not create a visible prompt part. - - Do not hide assistant synthetic text unless there is an explicit existing behavior to match. - -5. Match VS Code's text-file mention display more closely. - - Extend `FileAttachment` with optional source metadata. - - In `MessageView`, do not add source-backed `text/plain` user file parts to `PromptAttachmentView`; keep source-less text files as cards so explicit attachments still render. - - Keep image/PDF user attachments as attachment cards, matching VS Code's separate attachment preview behavior. - - Optional polish if scope allows: make source-backed `@file` tokens in `PromptView` clickable via `openFile(source.path)` using existing markdown/link styling. Do this only if it stays small and does not require rebuilding the retained Swing view architecture. - -6. Add focused tests. - - `KiloCliDataParserTest`: parse `synthetic: true`; parse file `source`; build prompt JSON with source; history preserves source and synthetic flags. - - `SessionModelTest`: user synthetic text is skipped in history and live updates; assistant text is unchanged; synthetic deltas do not create visible text after a synthetic update. - - `SessionUiUpdateTest`: a user message containing original text, a synthetic read-output text part, and a source-backed text/plain file shows only the prompt text and no raw `/` text; source-less text/plain attachments still render as `PromptAttachmentView`. - -7. Release note and verification. - - Add a patch changeset for the JetBrains plugin: "Hide raw file contents from mentioned files in JetBrains chat messages." - - Run the smallest relevant checks from `packages/kilo-jetbrains/`: targeted Gradle tests for parser/model/UI update tests, then `./gradlew typecheck` if the targeted tests pass. - -## Non-Goals - -- Do not change `packages/opencode/src/session/prompt.ts`; the CLI already marks these parts as synthetic. -- Do not add brittle string stripping for the full `//` block as the main fix. Metadata-based filtering matches VS Code and avoids hiding user-authored text. -- Do not remove attachment cards for manually attached text files that are not source-backed mentions. diff --git a/.kilo/plans/jetbrains-mention-actions.md b/.kilo/plans/jetbrains-mention-actions.md deleted file mode 100644 index 5adcdbbd4bd..00000000000 --- a/.kilo/plans/jetbrains-mention-actions.md +++ /dev/null @@ -1,96 +0,0 @@ -# JetBrains Mention Actions Organization Plan - -## Goal - -Organize known JetBrains prompt mentions the same way as built-in slash actions, so built-in mention names and metadata are centralized instead of hardcoded across completion, highlighting, and prompt part conversion. - -## Current Findings - -- There is one known built-in mention today: `@git-changes`. -- File mentions are dynamic backend search results from `KiloWorkspaceService.searchFiles(...)`; they are not built-in mention actions and should stay dynamic. -- `KiloPromptCompletionProvider` hardcodes `git-changes` in three places: - - Completion availability and lookup insertion when `FileSearchResultDto.git` is true. - - Highlighting as a valid mention without requiring a tracked file path. - - Validation skip so it is not resolved as a file path. -- `PromptMentionParts.kt` hardcodes the same token, output filename, and resource URI in `gitChangesPart(...)`. -- `SessionUi.mentionParts(...)` hardcodes `@git-changes` before calling `gitChangesPart(...)`. -- Tests assert `git-changes` and `@git-changes` literals in completion, highlighting, prompt panel, and prompt part conversion tests. - -## Relevant Files - -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProvider.kt` - - Known mention completion, highlighting, validation skip, and lookup element construction. -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptMentionParts.kt` - - Converts `@git-changes` into a synthetic text attachment from the git diff. -- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt` - - Detects the git changes mention before fetching git diff. -- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties` - - Existing localization key `prompt.mention.gitChanges`. -- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProviderTest.kt` - - Completion and highlighting tests for `git-changes`. -- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/prompt/PromptMentionPartsTest.kt` - - Prompt part conversion tests for `@git-changes`. -- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt` - - Editor highlighting and highlighter lifecycle tests involving `@git-changes`. - -## Implementation Plan - -1. Add a top-level `MentionAction` class in the prompt package: - - Path: `frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/MentionAction.kt` - - Package: `ai.kilocode.client.session.ui.prompt` - - Shape should mirror `SlashAction` where practical, but without an executable callback: - - `name` - - `description` - - `hints` - - `available(search: FileSearchResultDto): Boolean` or equivalent, so availability rules live with known mention metadata - - Add an inner `Spec` class for built-in metadata: - - `name` - - `descriptionKey` - - `hints` - - `filename` for resource-backed prompt parts where applicable - - `uri` for resource-backed prompt parts where applicable - - `available(search: FileSearchResultDto): Boolean` for completion visibility - - Add `GIT_CHANGES` and ordered `ALL` constants. - - Add small token helpers if useful, for example `Spec.token` returning `@name`, to avoid reconstructing token literals in multiple files. - -2. Bind mention specs where localized descriptions are needed: - - Add a small binder close to completion construction, analogous to slash action binding, or inside `KiloPromptCompletionProvider` if that keeps the constructor simpler. - - Prefer passing `mentions: List` into `KiloPromptCompletionProvider` if this keeps known mention completion explicit and testable. - - Keep `KiloBundle.message(...)` resolution in frontend/UI code, not in static metadata. - -3. Update `KiloPromptCompletionProvider` to use known mention actions: - - Add a `mentions` constructor parameter if using explicit binding. - - Replace hardcoded highlight checks with a known mention name set. - - Replace validation skip literal checks with the known mention name set. - - Replace completion logic for `git-changes` with iteration over known mentions whose names or hints match the prefix and whose `available(search)` condition is satisfied. - - Preserve current availability behavior: show `git-changes` only when `search.git` is true. - - Preserve ordering: known mentions before file results, with the existing priority behavior so `git-changes` remains first for blank mention completion when available. - - Preserve no-match behavior: no placeholder if a known mention matches; otherwise placeholder only when there are no matching known mentions and no file results. - -4. Update prompt part conversion to use centralized metadata: - - Replace `"@git-changes"`, `"git-changes"`, and `"git-changes.txt"` in `PromptMentionParts.kt` with `MentionAction.GIT_CHANGES` metadata. - - Keep the function name `gitChangesPart(...)` because the implementation still specifically converts git diff text. - - Preserve the boundary check that ignores `@git-changes-foo`. - -5. Update `SessionUi.mentionParts(...)`: - - Replace `text.contains("@git-changes")` with the centralized token from `MentionAction.GIT_CHANGES`. - - Keep diff fetching lazy and only when the prompt contains the known mention token. - -6. Update tests: - - Replace hardcoded built-in mention names with `MentionAction.GIT_CHANGES.name` or `MentionAction.GIT_CHANGES.token` where practical. - - Keep test strings readable where the full prompt text is the important fixture, but derive expected values from `MentionAction` to reduce duplicated metadata. - - Add or adjust one provider test to assert known mention metadata is used for completion description if there is an existing practical assertion path; otherwise do not add brittle UI presentation assertions. - -## Non-Goals - -- Do not turn dynamic file mentions into `MentionAction` entries. -- Do not add new built-in mentions. -- Do not change `@git-changes` behavior, availability, inserted text, highlighting, validation, prompt part source metadata, or output filename. -- Do not rename `gitChangesPart(...)` unless the implementation later supports multiple resource mention conversions. - -## Verification - -- Run targeted frontend tests from `packages/kilo-jetbrains`: - - `./gradlew :frontend:test --tests "ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest" --tests "ai.kilocode.client.session.ui.prompt.PromptMentionPartsTest" --tests "ai.kilocode.client.session.ui.PromptPanelTest"` -- Run JetBrains typecheck from `packages/kilo-jetbrains`: - - `./gradlew typecheck` diff --git a/.kilo/plans/jetbrains-mention-completion-flicker.md b/.kilo/plans/jetbrains-mention-completion-flicker.md deleted file mode 100644 index 97449853511..00000000000 --- a/.kilo/plans/jetbrains-mention-completion-flicker.md +++ /dev/null @@ -1,172 +0,0 @@ -# Fix flickering `@`-mention / `/`-slash prompt completion (JetBrains) - -## Goal - -Stop the prompt completion lookup from flickering as the user types an `@`-mention, -without changing the inline-dropdown UX. Keep reusing IntelliJ's standard completion -Lookup and the backend `GotoFileModel` ("Go to File") search engine that already power -the feature. This is **Option 1** from the investigation (smallest change, fully -Kilo-owned code, no UX change). - -## Background / root cause - -All touched code lives under Kilo-owned paths (`ai/kilocode/client/...`), so **no -`kilocode_change` markers are needed**. - -Current flow: - -- `PromptPanel` editor uses `TextCompletionUtil.DocumentWithCompletionCreator(provider, autoPopup=true)`. -- `PromptPanel.triggerCompletion()` manually invokes completion when `@` or a leading `/` is typed - (`PromptPanel.kt:457-478`), forcing `LookupPositionStrategy.ONLY_ABOVE`. -- `KiloPromptCompletionProvider.mention()` (`KiloPromptCompletionProvider.kt:73-93`) runs a - **blocking RPC search per completion pass** (`runBlockingCancellable { service.searchFiles(...) }`) - and applies a local `PlainPrefixMatcher(prefix)`. -- Backend `KiloWorkspaceRpcApiImpl.search()` (`KiloWorkspaceRpcApiImpl.kt:317-367`) uses IntelliJ's - `GotoFileModel` for fuzzy/camel-hump ranking. - -Why it flickers (collapse → re-query → refill cycle every keystroke): - -1. Backend does fuzzy/camel matching; the frontend re-filters with `PlainPrefixMatcher` - (substring match), which **hides server results that don't literally contain the typed - substring** (e.g. `sfb` → `src/foo/Bar.kt`). -2. A blank query returns `emptyList()` from the backend, so right after `@` only the - git-changes/terminal specials show; the first typed char tends to empty the locally - filtered list. -3. `restartCompletionWhenNothingMatches()` only re-queries **after** the list is already - empty, so the user sees empty → (async RPC gap) → refill on nearly every keystroke. -4. Variable RPC latency makes the refill land at inconsistent times. - -## The change (precise edits) - -### 1. `KiloPromptCompletionProvider.mention()` — let the server own filtering - -File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProvider.kt` - -- Replace `result.restartCompletionWhenNothingMatches()` with - `result.restartCompletionOnAnyPrefixChange()` so the platform keeps the lookup open and - deterministically re-runs the query on every prefix change (replacing items in place) - instead of waiting for the list to empty. -- Use an **accept-everything matcher** for the mention path so the platform never hides the - server's ranked results: wrap with `PlainPrefixMatcher.ALWAYS_TRUE` - (`com.intellij.codeInsight.completion.PlainPrefixMatcher`) instead of - `applyPrefixMatcher(result, prefix)`. - -Sketch: - -```kotlin -private fun mention(prefix: String, result: CompletionResultSet) { - result.restartCompletionOnAnyPrefixChange() - val out = result.withPrefixMatcher(PlainPrefixMatcher.ALWAYS_TRUE) - val search = searchFiles(prefix) // see step 2 (cache) - // ...unchanged: specials (git-changes, terminal), indexing advertisement, files... -} -``` - -Notes: - -- Keep `applyPrefixMatcher` (the `TextCompletionProvider` override) as-is — it is still used - by the `slash` path, where a static command list should be filtered locally. Only the - mention path overrides the matcher to accept-all. `TextCompletionContributor` calls - `applyPrefixMatcher` before `fillCompletionVariants`, so re-wrapping inside `mention` - with `ALWAYS_TRUE` is the correct seam. -- Items are added in server-weight order; with `ALWAYS_TRUE` there is no client-side - highlight/filter, which is intended (server is the source of truth). - -### 2. Small last-prefix cache to cut redundant RPC (optional but recommended) - -Still in `KiloPromptCompletionProvider`. With `restartCompletionOnAnyPrefixChange`, the -platform re-invokes the provider on each change and `runBlockingCancellable` cancels stale -passes. Add a **1-entry cache** keyed by `prefix` so identical prefixes (e.g. delete + -retype, or a duplicate pass for the same prefix) don't re-hit RPC: - -```kotlin -@Volatile private var cached: Pair? = null - -private fun searchFiles(prefix: String): FileSearchResultDto { - cached?.takeIf { it.first == prefix }?.let { return it.second } - val result = runBlockingCancellable { service.searchFiles(workspace.directory, prefix, 50) } - cached = prefix to result - return result -} -``` - -- Clear `cached` in `clearMentions()` (called from `PromptPanel.clear()`) to avoid stale - results across prompt resets. -- Keep it a single entry; do not build an unbounded map. - -### 3. Verify the `@` trigger does not double-fire - -File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/PromptPanel.kt` - -- `autoPopup=true` plus the manual `triggerCompletion()` could both fire on the first `@`. - During implementation, confirm (via `runIde`) whether typing `@` briefly shows a double - session. If it does, prefer keeping the **manual** trigger (autopopup is unreliable for - non-identifier chars like `@`) and confirm the second `scheduleAutoPopup` is a no-op while - a lookup is already active. Only adjust if a real double-flash is observed — otherwise - leave `triggerCompletion`/`showCompletion` and the `ONLY_ABOVE` strategy unchanged. -- Keep `LookupPositionStrategy.ONLY_ABOVE` (added in `7fd80d0af9`) — it prevents the popup - from flipping above/below as item counts change. - -## Edge cases / considerations - -- **Empty prefix right after `@`**: backend returns no files (blank query), so only - git-changes/terminal specials show until the user types. This is acceptable and not part - of the flicker bug; showing recent files for an empty prefix is a separate enhancement and - is **out of scope**. -- **Indexing state**: the existing `search.indexing` branch (advertisement + single info - element, early return) is unchanged. -- **Slash path**: unchanged. It must keep local `PlainPrefixMatcher` filtering of the static - command list and must NOT call `restartCompletionOnAnyPrefixChange`. -- **Threading**: `searchFiles` stays a cancellable blocking call inside the completion - background pass (as today). No EDT work added. - -## Testing - -Add `KiloPromptCompletionProviderTest` (frontend test, `BasePlatformTestCase`), modeled on -existing `KiloWorkspaceService(scope, FakeWorkspaceRpcApi())` usage -(e.g. `KiloWorkspaceServiceTest`, `SessionUiTestBase`) and `workspaces.workspace("/test")`. - -Use `myFixture` to drive the real completion machinery against a plain-text document with the -provider installed via `TextCompletionUtil.installProvider(file, provider, true)`: - -- **No local collapse of fuzzy results**: set `FakeWorkspaceRpcApi.searchResult` to files - whose paths do NOT contain the typed substring (simulating camel/fuzzy matches, e.g. - query `sfb` returning `src/foo/Bar.kt`). After completing on `@sfb`, assert all - server files appear in `myFixture.lookupElementStrings` (proves `ALWAYS_TRUE` matcher). -- **Re-query on prefix change**: extend `FakeWorkspaceRpcApi.searchFiles` to **record the - query string** (and optionally return per-query results). Assert that changing the prefix - triggers a new `searchFiles` call for the new prefix (proves - `restartCompletionOnAnyPrefixChange`). -- **Cache**: assert a repeated identical prefix does not issue a second `searchFiles` call, - and that `clearMentions()` resets the cache. -- **Specials still surface**: with `searchResult.git = true` / `terminal = true`, assert - `@git-changes` / `@terminal` appear for matching prefixes. - -Test-support change required: add query recording to `FakeWorkspaceRpcApi.searchFiles` -(e.g. `val searchQueries = mutableListOf()` and optionally a -`searchByQuery: (String) -> FileSearchResultDto` override). This is in the `testing` package -and acceptable. - -If the full `myFixture.complete` lookup proves too fiddly for deterministic assertions, fall -back to asserting the visible lookup element strings after `myFixture.type(...)` of each -character, which is the user-facing behavior we care about. Do not add production methods -that exist only for test access. - -## Verification - -- Unit/integration: `./gradlew test` (or targeted `./gradlew test --tests '*KiloPromptCompletionProviderTest*'`) - from `packages/kilo-jetbrains/`. Requires Java 21 (`java -version`; install via - `sdk install java 21-tem && sdk use java 21-tem` if missing). -- Typecheck: `./gradlew typecheck` from `packages/kilo-jetbrains/`. -- Manual: `./gradlew runIde`, open the Kilo tool window, type `@` then progressively type a - fuzzy filename; confirm the lookup stays open and updates in place with no empty→refill - flash, and that selecting a file inserts `@`. - -## Out of scope / fallback - -- No switch to the Search Everywhere / Go-to-File chooser (Option 2) and no custom - `JBPopup` + `JBList` (Option 3). If, after this change, RPC latency still dominates and the - lookup is not smooth enough, escalate to Option 3 (custom popup with debounced async list - updates) as a follow-up. -- No changes to backend `search()` / `GotoFileModel` ranking. -- No new behavior for empty-prefix `@` (recent files). diff --git a/.kilo/plans/jetbrains-mention-completion-reliability.md b/.kilo/plans/jetbrains-mention-completion-reliability.md deleted file mode 100644 index b3958b47787..00000000000 --- a/.kilo/plans/jetbrains-mention-completion-reliability.md +++ /dev/null @@ -1,243 +0,0 @@ -# Make JetBrains `@`/`/` prompt completion as reliable as editor completion - -Scope: Kilo-owned frontend code under -`packages/kilo-jetbrains/frontend/.../session/ui/prompt/`. No shared/upstream files, so -no `kilocode_change` markers needed. - -This is a follow-up to `jetbrains-empty-mention-completion.md` (prewarm + -`ALWAYS_AUTO_POPUP` removal, already implemented). It fixes the remaining instability. - -## Symptoms (from user) - -- Open `@`, then **type fast** (e.g. `@backend`) → the lookup **disappears**. -- **Type slowly** → it stays and narrows fine. -- The popup also **flickers** and **shows below the caret for a moment** before settling - above. - -## Root cause (verified against `$INTELLIJ_REPO`) - -The provider fetches results **asynchronously on its own coroutine scope** and then forces -the open lookup to repaint via a **manual restart**: - -- `KiloPromptCompletionProvider.search()` (`KiloPromptCompletionProvider.kt:198-207`) - returns an **empty placeholder** immediately for a cold prefix and launches a debounced - `refresh()` (`:215-228`). -- When `refresh()` finishes it stores `cache[prefix]` and posts `onRefresh()` - (`:224-225`) → `PromptPanel.restartCompletion()` (`PromptPanel.kt:561-569`) → - `CompletionProgressIndicator.scheduleRestart()`. - -`scheduleRestart()` does not repaint in place. It `cancel()`s the current indicator and -schedules a brand-new completion through the `CommittingDocuments` phase -(`CompletionProgressIndicator.java:913-941` → `CompletionPhase.kt:234-262`). That async -path commits documents in a non-blocking read action and then, on the EDT, runs -`startAsyncCompletionIfNotExpired` (`CompletionPhase.kt:294-340`). It contains: - -```kotlin -if (phase.myTracker.hasAnythingHappened() && (phase.indicator == null || !phase.indicator.lookup.isShown)) { - phase.cancelPhase() - CompletionServiceImpl.setCompletionPhase(NoCompletion) // <- popup disappears - return -} -``` - -So when our async `onRefresh → scheduleRestart` lands **while the user is still typing** -(`ActionTracker.hasAnythingHappened()` is true) and the lookup is in a transient -not-`isShown` window (it is, because our cold-prefix path produced only the transient -"Searching files…" element), the phase is dropped to `NoCompletion` and the lookup is -gone. Slow typing avoids the overlap, which is why it only reproduces under fast typing. - -Why the editor's own completion does **not** disappear under fast typing: every keystroke -restart goes through the same code, but the editor's contributors return their items -**synchronously inside the background completion calculation**, so the lookup is already -`isShown` with real items and the `hasAnythingHappened()` branch is skipped (the comment -on `CompletionPhase.kt:327-328` says a shown lookup "is going to handle close by itself"). -Our extra, arbitrarily-timed manual restart is the thing that races. - -The "shows below then jumps above" flicker is a related but separate timing bug: -`showCompletion` sets `LookupPositionStrategy.ONLY_ABOVE` **after** `invokeCompletion` -returns (`PromptPanel.kt:555-558`), i.e. after the lookup has already been created and -positioned at the default `PREFER_BELOW` (`LookupPresentation.kt:24`). The disappear → -recreate cycle makes this visible repeatedly. - -### Key platform facts that make the fix safe - -1. **Completion contributors run off the EDT, on a background thread, inside a cancellable - read action.** `CodeCompletionHandlerBase.startContributorThread` - (`CodeCompletionHandlerBase.java:414-438`) runs `indicator.runContributors` via - `AsyncCompletion.startThread` (`CompletionThreading.kt:118-153`, - `Dispatchers.IO.limitedParallelism(5)`). The indicator picks `AsyncCompletion` whenever - completion is not invoked inside a write action - (`CompletionProgressIndicator.java:200-202`). Therefore a **blocking** `searchFiles` - call inside `fillCompletionVariants`, guarded by `runBlockingCancellable`, is the - platform-intended pattern (same as index-backed file/class completion) — it does not - touch the EDT, and `FakeWorkspaceRpcApi.searchFiles` already asserts not-EDT, which the - existing unit-test path satisfies today via `fetch()`. -2. **The platform already drives "background updates" of an open lookup.** While the - contributor runs, the lookup shows the native calculating state and the - `CompletionConsumer` adds items to the live lookup as they are produced. This is the - mechanism to "reuse" — we do not need our own debounce/refresh/scheduleRestart. -3. **Restart reuses the same lookup instance.** `obtainLookup` - (`CodeCompletionHandlerBase.java:274-298`) returns the existing completion lookup via - `markReused()`, so a `LookupPresentation` set once persists across prefix-change - restarts. -4. **The lookup is activated before it is shown.** `ClientLookupManager.createLookup` - fires `LookupManagerListener.activeLookupChanged(null, lookup)` - (`ClientLookupManager.kt:91-93`) before `indicator.showLookup()`. Setting the position - strategy in that listener positions the lookup correctly on first paint — no flash. -5. `restartCompletionOnAnyPrefixChange()` (`KiloPromptCompletionProvider.kt:160,174`) is - what makes the platform re-run our contributor (and thus a fresh search) for each new - prefix. Keep it; it is the supported per-keystroke restart trigger. - -## Plan - -### Part 1 — Compute results synchronously on the completion background thread - -Make the production path do what the unit-test path already does: block on the backend -search (with cancellation) inside `fillCompletionVariants`, and let the platform manage -the lookup. Delete the async refresh + manual restart entirely. - -In `KiloPromptCompletionProvider.kt`: - -- **Replace `search()`** (`:198-207`) with a cache-or-fetch that always returns a settled - result: - - ```kotlin - private fun search(prefix: String): FileSearchResultDto = cache[prefix] ?: fetch(prefix) - ``` - - `fetch()` (`:209-213`) stays as-is: `runBlockingCancellable { service.searchFiles(...) }` - + `cache[prefix] = result`. It now runs on the async completion thread in production too. -- **Delete** `refresh()` (`:215-228`), the `onRefresh` field (`:48-49`), the `job`/`want` - `@Volatile` fields (`:42-46`), and `SEARCH_DELAY_MS` (`:51-53`). -- **Delete** the `Search` data class (`:322`) and the `pending` plumbing. Simplify - `mention()` (`:173-196`): drop the `search.pending` branch and the - `prompt.mention.searching` placeholder; keep the `indexing` branch and the `noMatches` - fallback. The native lookup spinner now covers the "in flight" state. -- **`clearMentions()`** (`:68-76`): keep clearing `paths/exists/pending/cache`; drop - `want`/`job` resets. -- Remove now-unused imports: `kotlinx.coroutines.Job`, `kotlinx.coroutines.delay`, - `com.intellij.openapi.application.ApplicationManager` (was only used for the - unit-test-mode branch and the `invokeLater` in `refresh`). Keep `launch`/`CoroutineScope` - (used by `prewarm` and `validate`) and `runBlockingCancellable`. -- Keep `prewarm()` and `restartCompletionOnAnyPrefixChange()` unchanged. Prewarm keeps the - first `@` instant (cache hit, no blocking); per-prefix restarts hit the cache when the - prefix repeats and otherwise block briefly with native cancellation. - -In `PromptPanel.kt`: - -- **Delete** the `completion?.onRefresh = { ... restartCompletion() }` wiring in `init` - (`:250-252`), the `completion?.onRefresh = null` line in `removeNotify` (`:451`), and the - `restartCompletion()` method (`:561-569`). -- Remove the now-unused import - `com.intellij.codeInsight.completion.impl.CompletionServiceImpl` (`:25`). - -Remove the unused `prompt.mention.searching` key from -`frontend/src/main/resources/messages/KiloBundle.properties:167` (no locale file -references it). Keep `prompt.mention.indexing`. - -### Part 2 — Position the lookup above the caret before first paint - -Set `ONLY_ABOVE` as soon as the completion lookup for our editor is created, via a -project-level `LookupManagerListener`, instead of after `invokeCompletion`. - -In `PromptPanel.kt`: - -- Add a `private var lookupBus: MessageBusConnection? = null` and a `bindLookup()` that - subscribes once to `LookupManagerListener.TOPIC` on `project.messageBus`: - - ```kotlin - connection.subscribe(LookupManagerListener.TOPIC, LookupManagerListener { _, next -> - val lookup = next as? LookupImpl ?: return@LookupManagerListener - if (lookup.editor !== editor.getEditor(false)) return@LookupManagerListener - lookup.presentation = LookupPresentation.Builder(lookup.presentation) - .withPositionStrategy(LookupPositionStrategy.ONLY_ABOVE) - .build() - }) - ``` - - Call `bindLookup()` from `addNotify()` (next to `bindRoot()`/`bindKeymap()`); disconnect - in `removeNotify()` (`lookupBus?.disconnect(); lookupBus = null`). -- **Simplify `showCompletion()`** (`:551-559`) to just open completion - (`invokeCompletion(project, ed, 1)`); drop the post-invoke presentation block — the - listener now owns positioning for both the `@`/`/` document-triggered open and the - completion-shortcut open, and it persists across restarts (reused lookup). -- Add import `com.intellij.codeInsight.lookup.LookupManagerListener`. Existing - `LookupManager`, `LookupImpl`, `LookupPresentation`, `LookupPositionStrategy` imports stay - (now used by the listener). - -## Tests - -`KiloPromptCompletionProviderTest` (provider built directly; `myFixture.completeBasic()` -runs the real off-EDT completion calculation): - -- All existing tests stay green. They already exercise the blocking `fetch()` path (the old - `isUnitTestMode` branch) and assert exact `rpc.searchQueries`; `completeBasic()` issues a - single search per invocation, so query counts are unchanged. `test prewarm serves blank - mention completion from cache` and `test mention completion reuses identical prefix - result` still hold (cache hit ⇒ no extra query); `test clearing mentions resets cached - prefix result` still holds (cache cleared ⇒ refetch). -- The `prompt.mention.searching` placeholder is no longer produced; confirm no test asserts - it (none do — only `noMatches()` and `indexing` are asserted/relevant). - -`PromptPanelTest` (real editor + lookup via `invokeCompletionAction` + `waitForLookupItems`): - -- Existing `test prompt local completion shortcut opens mention lookup` / `... slash lookup` - stay green and now cover the production blocking path end to end. -- **Add** `test prompt completion lookup is positioned above caret`: open the mention - lookup as in the existing shortcut test, then assert - `(LookupManager.getActiveLookup(editor) as LookupImpl).presentation.positionStrategy == - LookupPositionStrategy.ONLY_ABOVE`. - -Note on the disappear/flicker race: it is an EDT/timing interaction that cannot be asserted -deterministically in a unit test. Coverage is the deterministic position test above plus -the existing real-lookup tests; the fast-typing regression is validated manually in -`./gradlew runIde`. - -## Files to change - -- `frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt` — blocking - `search()`; delete `refresh`/`onRefresh`/`job`/`want`/`SEARCH_DELAY_MS`/`Search`/pending; - simplify `mention()`; trim imports. -- `frontend/.../session/ui/prompt/PromptPanel.kt` — delete `onRefresh` wiring and - `restartCompletion()`; add `LookupManagerListener` for `ONLY_ABOVE`; simplify - `showCompletion()`; adjust imports. -- `frontend/src/main/resources/messages/KiloBundle.properties` — remove - `prompt.mention.searching`. -- `frontend/src/test/.../prompt/KiloPromptCompletionProviderTest.kt` / - `.../session/ui/PromptPanelTest.kt` — keep existing green; add position test. - -## Verification - -From `packages/kilo-jetbrains/`: - -- `./gradlew :frontend:test --tests - ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest --tests - ai.kilocode.client.session.ui.PromptPanelTest` -- `./gradlew typecheck` -- Manual (`./gradlew runIde`): open a session, `@` then type `@backend` **fast** → lookup - stays open and narrows (no disappear); it opens **above** the caret with no below-flash; - `/` commands behave the same; send a prompt and repeat. - -## Decisions / tradeoffs / risks - -1. **Block on the background completion thread; drop the manual restart.** This is the - idiomatic platform pattern and removes the `onRefresh → scheduleRestart` race that - causes the disappear. It also removes our 120 ms debounce and the dual code paths - (test vs prod), unifying behavior. -2. **Chatty-RPC tradeoff.** Each prefix change re-runs the search. In practice superseded - calculations are cancelled by the platform (`runBlockingCancellable` throws on cancel, - `searchFiles` rethrows `CancellationException`), the per-prefix `cache` dedupes repeats, - and `prewarm` covers the empty prefix. Backend file search is cheap, so this is - acceptable and matches how index-backed completion behaves. -3. **Read lock held during the search.** The contributor runs inside a cancellable read - action; a localhost search blocks it briefly. `BgCalculation.restartOnWriteAction` - (`CompletionPhase.kt:446-470`) cancels the indicator the moment the user types (write - action), promptly releasing the lock. This is the same contract index-backed - contributors rely on. -4. **Indexing/transient results.** With no auto-refresh, if the backend reports `indexing` - the user sees the "Indexing…" message until the next keystroke or reopen re-queries - (rare, brief). `prewarm` already avoids pinning indexing/empty results in `cache[""]`. -5. **Experimental/internal APIs.** `LookupPresentation` / `LookupPositionStrategy` are - `@ApiStatus.Experimental` and `LookupImpl` is impl-level — already used here. The new - `LookupManagerListener` is a stable public topic. Revisit on platform upgrades (the - existing `showCompletion`/`restartCompletion` comments already flag this). diff --git a/.kilo/plans/jetbrains-mention-completion.md b/.kilo/plans/jetbrains-mention-completion.md deleted file mode 100644 index f031d739a3c..00000000000 --- a/.kilo/plans/jetbrains-mention-completion.md +++ /dev/null @@ -1,214 +0,0 @@ -# Fix JetBrains prompt mention completion + highlighting - -Scope: Kilo-owned frontend code under -`packages/kilo-jetbrains/frontend/.../session/ui/prompt/`. No shared/upstream files, -so no `kilocode_change` markers needed. - -## Symptoms (from user) - -Completion: -- Type `@` then quickly `ba` → the completion popup **disappears**. -- Typing slowly works, but there is a visible delay/rebuild between keystrokes. - -Highlighting: -- After accepting a file from completion, the mention is **not** highlighted as valid - until you type a space. -- Editing the tail of a valid mention so it becomes invalid does **not** turn it red. - -User goals: less blinking on rebuilds, do not hide the popup while typing between -rebuilds, correct path validation. - -## Root cause analysis - -### Why the popup disappears (completion) - -`KiloPromptCompletionProvider.mention()` calls `search(prefix)` which runs the backend -file search **synchronously** via `runBlockingCancellable` inside -`fillCompletionVariants` (`KiloPromptCompletionProvider.kt:166-171`). It also calls -`result.restartCompletionOnAnyPrefixChange()`, so every typed character restarts the -whole completion. - -IntelliJ's restart path (`CompletionPhase.scheduleAsyncCompletion` → -`startAsyncCompletionIfNotExpired`, verified in -`$INTELLIJ_REPO/platform/lang-impl/.../CompletionPhase.kt:326-333`) cancels a scheduled -completion when **activity happened in the editor AND the previous lookup is not yet -shown**: - -``` -if (phase.myTracker.hasAnythingHappened() && (phase.indicator == null || !phase.indicator.lookup.isShown)) { - phase.cancelPhase(); setCompletionPhase(NoCompletion); return // popup gone, not rescheduled -} -``` - -So while the first blocking `search()` is still running (lookup not shown yet), typing a -second character cancels the session entirely and nothing reopens it. Slow typing leaves -enough time for the blocking search to finish and the lookup to show; once a lookup is -shown, later restarts are kill-safe (`!lookup.isShown` is false). This exactly matches -"fast = disappears, slow = works". The blocking RPC is the core problem. - -The pattern (blocking + `restartCompletionOnAnyPrefixChange`) is what LSP uses -(`LspCompletionContributor.kt:44-48`), but LSP also suffers the same issue for slow -servers. We must make the **first paint non-blocking** so the lookup shows immediately. - -### Why blinking happens on rebuilds - -Each keystroke fully restarts completion, re-queries the backend (blocking), and tears -down/rebuilds the item list — even though we already have usable results for a shorter -prefix. Empty/"No matches" can flash mid-refresh. - -### Why highlighting is wrong - -Highlights only refresh on **document change** (`PromptPanel` document listener → -`syncHighlights()`, `PromptPanel.kt:241-249,377-403`). Two consequences: - -1. **Not valid after accept:** In `replace()` (`KiloPromptCompletionProvider.kt:228-241`) - the path is added to `paths`/`exists` **after** `ctx.document.replaceString(...)`. The - synchronous `documentChanged` → `syncHighlights()` therefore runs while `paths` is - still empty, so the just-inserted mention is not highlighted. The next edit (typing a - space) re-syncs and it finally turns valid. -2. **Doesn't turn red after editing tail:** `highlights()` suppresses any mention while - the caret is inside it (`under -> Unit`, `KiloPromptCompletionProvider.kt:84-90`), and - `validate()` skips tokens under the caret (`:99`). When the caret leaves the token via - click/arrow (no document change), nothing re-syncs, so validation never runs and the - now-invalid mention is never flagged. Typing a space forces a re-sync, which is the - only reason it eventually works. - -## Plan - -### Part A — Highlighting (low risk, do first) - -**A1. Valid immediately after accepting a completion.** -In `KiloPromptCompletionProvider.replace()`, move the `paths.add(path)` / -`exists[path] = true` update to **before** `ctx.document.replaceString(...)`. The -synchronous document-change → `syncHighlights()` then sees the new mention and highlights -it as `MENTION` right away. No behavior change to final text or `mentionPaths()`, so -existing `replace`-related tests still pass. - -**A2. Re-validate when the caret leaves a mention token.** -Add a `CaretListener` to the prompt editor (in `PromptPanel`'s `addSettingsProvider` -block alongside the existing focus listener, `PromptPanel.kt:178-186`) that calls -`syncHighlights()` when the caret crosses a mention-span boundary. Guard it so it only -re-syncs when the set of "caret is inside a mention" actually changes (compare previous -vs new offset against `mentionSpans`) to avoid churn on ordinary cursor movement. This -makes an edited-then-left mention run `validate()` and turn red (or back to valid). -Register on the per-editor `caretModel`; it is released with the editor, consistent with -the existing focus listener. - -### Part B — Completion popup (the disappearing/blinking fix) - -Make mention completion **non-blocking** and serve results from a cache, refreshing in -the background. Slash completion is already synchronous/in-memory and is left unchanged. - -**B1. Replace the single `cached` field with a small cache + async machinery** in -`KiloPromptCompletionProvider`: -- `private val cache = ConcurrentHashMap()` (thread-safe; - `fillCompletionVariants` runs on a background completion thread, the refresh writes from - a coroutine). -- `@Volatile private var job: Job? = null` (single in-flight fetch; cancel on new - request — debounced). -- `@Volatile private var want: String? = null` (latest requested prefix, to ignore stale - fetch completions). -- `var onRefresh: (() -> Unit)? = null` — set by `PromptPanel` to restart the active - completion when fresh data arrives. -- `clearMentions()` clears `cache`, cancels `job`, resets `want` (keeps existing reset - semantics so the "clearing resets cached prefix result" test still does two queries). - -**B2. Non-blocking `mention(prefix, result)`** decision order: -1. `cache[prefix] != null` → render those results (exact, instant). -2. Else pick the **longest cached key that is a prefix of `prefix`** as a stale base and - render its results with the existing `PlainPrefixMatcher.ALWAYS_TRUE` (instant, keeps - the lookup populated and shown); then request a background refresh for `prefix`. -3. Else (truly cold, no base) → render a "searching" placeholder via the existing - `info()` element (`NEVER_AUTOCOMPLETE`, prefix-preserving) so the lookup still shows; - request a background refresh. - -Because fill never blocks, the lookup always shows on the first paint → the platform's -"activity happened & lookup not shown" kill path can no longer fire → the popup stops -disappearing on fast typing. Keep `restartCompletionOnAnyPrefixChange()` so each -keystroke re-runs this cheap fill (re-evaluates cache/stale and kicks the next refresh). - -**B3. Background refresh (debounced) + in-place restart.** -- `refresh(prefix)`: set `want = prefix`; cancel previous `job`; launch on `scope`: - optional small `delay(~120ms)` debounce, then `service.searchFiles(dir, prefix, 50)`, - store in `cache[prefix]`, and if `prefix == want` post `onRefresh()` on the EDT. -- `PromptPanel` sets `completion.onRefresh = { restartActiveCompletion() }` where - `restartActiveCompletion()` is `@RequiresEdt`, checks `!project.isDisposed`, and — only - if a lookup is active on this editor — restarts it **in place** (replaces items without - closing/reopening) using the platform completion indicator. This is the existing - documented "uses IntelliJ impl/internal completion APIs; revisit on platform upgrades" - boundary already present for `showCompletion` (`PromptPanel.kt:533-541`); reuse the same - caveat comment. Guard to the current editor (`indicator.editor === ed`). - -This yields: instant first paint, stale results held across restarts (no empty/"No -matches" flash → less blinking), one debounced backend query per settled prefix, and an -in-place refresh when results arrive. - -**B4. Unit-test mode stays synchronous.** -`fillCompletionVariants`/`mention` is invoked through `myFixture.completeBasic()` in the -provider tests, which expect results synchronously. When -`ApplicationManager.getApplication().isUnitTestMode()` is true, perform the cold fetch -inline (`runBlockingCancellable`, current behavior) and skip the async -refresh/`onRefresh` path. This keeps every existing `KiloPromptCompletionProviderTest` -and `PromptPanelTest` completion test green with no rewrites, while production uses the -async path. (`isUnitTestMode()` branching is the platform's own convention in completion -code, e.g. `scheduleAutoPopup`/`handleEmptyLookup`.) Documented as a deliberate tradeoff -in the open questions. - -**B5. Optional refinement — avoid double-trigger blink.** -`@`/`/` triggers an explicit `showCompletion` (`PromptPanel.triggerCompletion`/ -`showCompletion`) while the editor also has `AutoPopupController.ALWAYS_AUTO_POPUP`, so -two completions can race on the first character. Guard `showCompletion` to no-op if a -lookup is already active for the editor. Keep as a follow-up tweak; the main fix is B1–B4. - -### New bundle key - -Add `prompt.mention.searching=Searching files…` to -`frontend/src/main/resources/messages/KiloBundle.properties` (English only; other locales -fall back). Used by the cold "searching" placeholder. - -## Tests - -- `KiloPromptCompletionProviderTest`: unchanged and must stay green (B4 keeps them - synchronous). Add: - - cache base reuse: after `complete("@back")` then `complete("@backend")`, the second - serves stale base results immediately (assert no regression in `searchQueries` - expectations as designed). -- `PromptPanelTest`: - - **Accept → valid immediately (A1):** type `@dep`, open lookup, accept `src/deploy.ts`, - and assert the mention span has `DefaultLanguageHighlighterColors.METADATA` - **without** typing a space (use `waitForSend`). - - **Edit tail → invalid on caret leave (A2):** set a valid mention (`fileResolver` - returns it), edit the tail to an unknown path, move the caret out of the token, and - assert `CodeInsightColors.WRONG_REFERENCES_ATTRIBUTES` appears via `waitForSend`. - - Existing highlight/lookup tests must remain green. - -## Files to change - -- `frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt` — A1 reorder; B1–B4 - cache/async/refresh; cold placeholder. -- `frontend/.../session/ui/prompt/PromptPanel.kt` — A2 caret listener; wire - `completion.onRefresh` + `restartActiveCompletion()`; optional B5 guard. -- `frontend/src/main/resources/messages/KiloBundle.properties` — `prompt.mention.searching`. -- Tests: `frontend/src/test/.../prompt/KiloPromptCompletionProviderTest.kt`, - `frontend/src/test/.../ui/PromptPanelTest.kt`. - -## Verification - -From `packages/kilo-jetbrains/`: `./gradlew typecheck` and `./gradlew test` (Java 21). -Manual check in `./gradlew runIde`: type `@` then quickly `ba` (popup stays, narrows to -backend entries); accept a file (highlights valid immediately); break a valid mention's -tail and click away (turns red). - -## Decisions - -1. **In-place restart (B3):** use the platform completion indicator (the same internal - boundary already used by `showCompletion`, with the existing "revisit on platform - upgrades" caveat comment). Lowest blink; replaces items without closing the lookup. -2. **Tests (B4):** branch on `ApplicationManager.getApplication().isUnitTestMode()` so the - cold fetch runs inline in tests (existing tests stay green, no rewrites); production - uses the async path. - -## Remaining tunable - -- **Debounce delay** for the background refresh: starting at ~120ms; tune during - implementation/manual testing. diff --git a/.kilo/plans/jetbrains-mention-file-search-refresh.md b/.kilo/plans/jetbrains-mention-file-search-refresh.md deleted file mode 100644 index 828b869f9ee..00000000000 --- a/.kilo/plans/jetbrains-mention-file-search-refresh.md +++ /dev/null @@ -1,60 +0,0 @@ -# Fix JetBrains Mention File Search Refresh - -## Diagnosis - -`@deploy.sh` can fail even when IntelliJ Find by Name can locate the file because mention completion currently performs backend file search only once, at the moment completion is invoked. - -Current flow: - -- Typing `@` explicitly invokes completion in `PromptPanel`. -- `KiloPromptCompletionProvider.mention()` receives an empty prefix and calls `searchFiles(workspace.directory, "", 50)`. -- The backend returns the first limited slice of indexed file names, not all repo files. -- As the user types `deploy.sh`, `acceptChar()` returns `ADD_TO_PREFIX`, so IntelliJ appends the typed chars to the active lookup prefix and filters the already-returned lookup items. -- The backend is not queried again unless completion is restarted. -- If `deploy.sh` was not in the initial empty-prefix result slice, it can never appear. - -Secondary backend issue: - -- `KiloWorkspaceRpcApiImpl.search()` uses `NameUtil.buildMatcher("*${query.trim()}")`. -- This behaves like a suffix-ish matcher rather than a broad contains/fuzzy search for partial file names. -- A partial query like `deploy` may not match `deploy.sh` until the extension is typed, depending on matcher semantics. - -Platform finding: - -- IntelliJ exposes public restart hooks on `CompletionResultSet`: `restartCompletionOnPrefixChange(...)`, `restartCompletionOnAnyPrefixChange()`, and `restartCompletionWhenNothingMatches()`. -- For `TextCompletionProvider`, these can be called from `fillCompletionVariants(...)`; no internal completion APIs are needed. -- Relevant IntelliJ source: - - `TextCompletionContributor.java`: computes prefix and delegates to the provider. - - `TextCompletionCharFilter.java`: `ADD_TO_PREFIX` keeps the lookup alive and updates prefix. - - `LookupTypedHandler.java`: typed chars only restart completion when requested by result-set restart hooks. - - `CompletionResultSet.java`: public restart methods. - -## Implementation Plan - -1. Update `KiloPromptCompletionProvider.mention()` to request backend re-query as the mention prefix changes. - - Prefer `result.restartCompletionOnAnyPrefixChange()` for file mentions, or `restartCompletionOnPrefixChange(StandardPatterns.string().longerThan(0))` if empty-prefix churn is undesirable. - - Also call `result.restartCompletionWhenNothingMatches()` as a fallback so missed initial slices recover as the user narrows the prefix. - - Keep slash command behavior static unless slash commands show a similar issue; slash options are small enough to filter locally. - -2. Improve backend filename matching in `KiloWorkspaceRpcApiImpl.search()`. - - Use a contains/fuzzy pattern such as `*${query.trim()}*` instead of `*${query.trim()}`. - - Treat blank query separately to avoid pathological wildcard behavior and keep initial `@` results capped. - - Continue sorting by matching degree, path length, then path. - -3. Add a targeted test if practical in the JetBrains test suite. - - Unit-test the provider behavior only if existing tests can invoke text completion without excessive platform setup. - - Otherwise, add coverage around backend search matching if there is already a backend/RPC test harness. - - Avoid mocks where possible; use actual implementation helpers if accessible. - -4. Verify. - - Run `./gradlew typecheck` from `packages/kilo-jetbrains/`. - - If tests are added, run the smallest relevant Gradle test task for the touched module. - -## Expected Result - -Typing `@deploy.sh` should re-run backend search as the prefix changes, so indexed repo files like `deploy.sh` appear even when they were not included in the initial empty-`@` result set. - -## Notes - -- The file still must be under the current workspace directory and visible to the IntelliJ project index. -- If standard IntelliJ Find by Name sees the file and it is under the workspace root, mention completion should see it after this fix. diff --git a/.kilo/plans/jetbrains-mention-icons-priority.md b/.kilo/plans/jetbrains-mention-icons-priority.md deleted file mode 100644 index 9355d57da9d..00000000000 --- a/.kilo/plans/jetbrains-mention-icons-priority.md +++ /dev/null @@ -1,59 +0,0 @@ -# JetBrains Mention Icons And Priority Plan - -## Goal - -- Show IntelliJ file type icons for file mention completions instead of the generic text icon. -- Keep predefined mention completions, currently `@git-changes`, above file/folder entries in the blank `@` default list. - -## Current State - -- `KiloPromptCompletionProvider.file(...)` uses `AllIcons.Nodes.Folder` for directories and `AllIcons.FileTypes.Text` for every non-directory file. -- `AttachmentOpeners.attachmentIcon(...)` already uses `FileTypeManager.getInstance().getFileTypeByFileName(name).icon ?: AllIcons.FileTypes.Text`, which is a suitable public IntelliJ API pattern to reuse. -- `mention(...)` adds `git-changes` before files, but the completion sorter can still influence presentation. Explicit priority will make the intended ordering stable. -- The backend/RPC DTO already provides `WorkspaceFileDto.name` and `directory`, so no shared DTO or backend changes are needed. - -## Implementation - -1. Update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProvider.kt`. -2. Import `com.intellij.openapi.fileTypes.FileTypeManager` and `com.intellij.codeInsight.lookup.PrioritizedLookupElement`. -3. Add a small helper for mention file icons: - - Return `AllIcons.Nodes.Folder` when `file.directory` is true. - - Otherwise return `FileTypeManager.getInstance().getFileTypeByFileName(file.name).icon ?: AllIcons.FileTypes.Text`. - - Use `file.name`, not `file.path`, so file type lookup uses the final path segment consistently. -4. Change `file(file)` to call the icon helper instead of hardcoding `AllIcons.FileTypes.Text`. -5. Add a small helper for predefined mention priority: - - Wrap predefined mention lookup elements with `PrioritizedLookupElement.withPriority(..., 100.0)`. - - Keep file lookup elements unprioritized. - - This should apply to `@git-changes` whenever it is included, including the blank `@` list and non-empty matching prefixes. -6. Keep the existing backend search behavior unchanged, including root folder/file listing for blank queries and fuzzy search for non-empty queries. - -## Tests - -Update `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProviderTest.kt`. - -1. Extend the blank mention completion test to assert ordering: - - `lookupElementStrings.orEmpty().take(3)` should be `listOf("git-changes", "src", "README.md")` when the fake backend returns `git = true` and root entries. -2. Add coverage for file type icons: - - Complete a file with a recognizable extension, e.g. `image.png`. - - Render the corresponding lookup element into `LookupElementPresentation`. - - Assert the icon equals `FileTypeManager.getInstance().getFileTypeByFileName("image.png").icon ?: AllIcons.FileTypes.Text`. -3. Keep or add directory icon coverage if cheap: - - Assert a directory entry renders `AllIcons.Nodes.Folder`. - -## Verification - -Run from `packages/kilo-jetbrains/`: - -```bash -./gradlew :frontend:test --tests ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest -./gradlew typecheck -``` - -## Release Note - -- This is user-facing JetBrains behavior. Add a patch changeset such as `.changeset/.md` for `@kilocode/kilo-jetbrains` with a concise user-facing description, for example: `Show file type icons and keep predefined mentions first in JetBrains mention completions.` - -## Risks - -- File type icons depend on installed IntelliJ file type registrations. Tests should compare against the platform API result instead of assuming a specific concrete icon for an extension. -- Completion ordering can be affected by IntelliJ sorters. Use `PrioritizedLookupElement` for predefined mentions instead of relying only on insertion order. diff --git a/.kilo/plans/jetbrains-mention-slash-input.md b/.kilo/plans/jetbrains-mention-slash-input.md deleted file mode 100644 index cf8d8575f43..00000000000 --- a/.kilo/plans/jetbrains-mention-slash-input.md +++ /dev/null @@ -1,263 +0,0 @@ -# JetBrains prompt input: undo, clean completion, not-found mention highlight - -Improve the JetBrains plugin chat prompt input (mentions `@` and slash `/`). Three -independent fixes, all in the `frontend` module under -`packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/`. - -These are all Kilo-owned files (path contains `kilocode`/`client` and is not shared with -upstream opencode), so **no `kilocode_change` markers are needed**. - -## Background (verified) - -- The prompt editor is `PromptEditorTextField` → `SessionEditorTextField` → platform - `com.intellij.ui.EditorTextField`, hosted in the chat tool window (`PromptPanel`). -- The mention/slash completion + highlight logic lives in - `KiloPromptCompletionProvider`. Highlights are applied to the editor by - `PromptPanel.syncHighlights()` via `markupModel` range highlighters. -- The CLI resolves *configured* references from prompt text; arbitrary file mentions are - surfaced client-side. This change is **highlight + insertion only** — it does not change - what gets attached/sent (`mentionParts`/`mentionFileParts` stay as-is). - ---- - -## Issue 1 — Undo/redo (Cmd-Z) does nothing in the prompt - -### Root cause - -The platform `$Undo`/`$Redo` actions (`UndoRedoAction`) resolve the target editor from -`PlatformCoreDataKeys.FILE_EDITOR`. When focus is in the prompt editor, the focused -component is `EditorComponentImpl`, whose `uiDataSnapshot` provides `CommonDataKeys.EDITOR` -but **not** `FILE_EDITOR`. With a null `FileEditor`, `UndoDocumentUtil.getDocRefs(null)` -returns an empty list, so the undo manager has no document references and reports "nothing -to undo". - -JetBrains' own `EditorTextField`-based inputs fix this by exposing a `FileEditor` wrapper. -See `platform/collaboration-tools/.../CodeReviewCommentTextFieldFactory.kt:114` -(`sink[PlatformCoreDataKeys.FILE_EDITOR] = TextEditorProvider.getInstance().getTextEditor(editor)`, -commented "required for undo/redo"). - -### Change - -File: `session/ui/editor/SessionEditorTextField.kt` - -In `uiDataSnapshot`, additionally expose a `TextEditor` wrapper of the current editor: - -```kotlin -override fun uiDataSnapshot(sink: DataSink) { - super.uiDataSnapshot(sink) - ctx?.let { sink.set(PromptDataKeys.SEND, it) } - getEditor(false)?.let { editor -> - sink.set( - PlatformCoreDataKeys.FILE_EDITOR, - TextEditorProvider.getInstance().getTextEditor(editor), - ) - } -} -``` - -New imports: `com.intellij.openapi.actionSystem.PlatformCoreDataKeys`, -`com.intellij.openapi.fileEditor.impl.text.TextEditorProvider`. - -Notes: -- `TextEditorProvider.getTextEditor(editor)` caches the wrapper in the editor's user data, - so this is cheap and stable across snapshots. -- This is the same wrapper platform `BasicUiDataRule` would synthesize; setting it - explicitly guarantees undo/redo regardless of data-rule timing, and matches the - collaboration-tools precedent. -- Applies to both prompt and the question custom-answer editor (both use - `SessionEditorTextField`); both benefit from working undo. - ---- - -## Issue 2 — Completing a mention mid-token leaves garbage - -### Reproduction - -Input `@backend/deploy⟨caret⟩-dev.sh`, accept the `backend/deploy-dev.sh` suggestion → -result is `@backend/deploy-dev.sh -dev.sh` (leftover suffix). - -### Root cause - -`KiloPromptCompletionProvider.replace()` (lines ~210-220) computes the replaced region end -as `ctx.tailOffset` — the end of the platform-inserted lookup string — instead of the end -of the whitespace-delimited token under the caret. The original suffix after the caret -(`-dev.sh`) is therefore left behind. - -### Change - -File: `session/ui/prompt/KiloPromptCompletionProvider.kt` - -Compute `end` as the end of the full whitespace-delimited token (scan right from the token -start to the next whitespace), mirroring the existing left-scan used for `start` and the -boundary logic in `token()`: - -```kotlin -private fun replace(ctx: InsertionContext, value: String, trim: Boolean, path: String? = null) { - val text = ctx.document.text - val offset = ctx.startOffset.coerceAtMost(text.length) - val start = (offset - 1 downTo 0).firstOrNull { text[it].isWhitespace() }?.plus(1) ?: 0 - val end = (start until text.length).firstOrNull { text[it].isWhitespace() } ?: text.length - val next = text.getOrNull(end) - val insert = if (trim && next?.isWhitespace() == true) value.trimEnd() else value - ctx.document.replaceString(start, end, insert) - ctx.editor.caretModel.moveToOffset(start + insert.length) - path?.let { paths.add(it); exists[it] = true } // exists seeding added in Issue 3 -} -``` - -This fixes file mentions (`file`), special mentions (`special`/`@git-changes`), and server -slash commands (`server`), which all route through `replace`. End-of-token (normal) -completion behaves exactly as before. - -Apply the same token-end computation to the "No matches" placeholder insert handler -(`info`, lines ~158-166) so accepting it mid-token also restores the prefix cleanly instead -of duplicating the suffix. Extract a tiny shared helper for the token end (e.g. -`tokenEnd(text, start)`) used by both `replace` and `info`. - -Behavior holds for `\n` (Enter) and Tab acceptance — both run the same insert handler. - ---- - -## Issue 3 — A mention whose file is not found is not highlighted (stays plain text) - -Chosen approach (confirmed with user): **accurate** — validate each `@mention` against the -workspace via the backend and show a red error highlight only when the file truly does not -resolve. The token currently under the caret (being typed) is never flagged. - -### Current behavior - -`highlights()` only emits `MENTION` for completion-tracked `paths` (+ `git-changes`). -Every other `@token` (hand-typed or edited) renders as ordinary text, with no signal that -it will not resolve. - -### Design - -Add asynchronous, cached existence validation using the existing -`KiloWorkspaceService.files(directory, path)` RPC (returns matches or empty list; runs -off-EDT — the fake asserts `assertNotEdt("files")`). - -#### `KiloPromptCompletionProvider.kt` - -- Constructor: add `private val scope: CoroutineScope` (for launching validation). -- New state: - - `private val exists = Collections.synchronizedMap(mutableMapOf())` - - `private val pending = Collections.synchronizedSet(mutableSetOf())` -- `clearMentions()`: also clear `exists` and `pending`. -- `replace(...)`: when a completed path is accepted, seed `exists[path] = true` (known-good, - highlights instantly with no RPC) — see Issue 2 snippet. -- New `HighlightKind.INVALID` enum value. -- New private `mentionSpans(text): List` — whitespace-delimited tokens that start with - `@` and have a non-empty value (returns `start`, `end`, `value`). Replaces the current - substring/longest-first scan with direct token scanning (inherently non-overlapping). -- `highlights(text: String, caret: Int = -1): List` (add `caret` param, default - `-1` keeps existing call sites/tests working): - - Command highlight: unchanged. - - For each mention span (`underCaret = caret in span.start..span.end`): - - `value == "git-changes"` → `MENTION` - - `value in paths || exists[value] == true` → `MENTION` - - `underCaret` → skip (no highlight; being edited — avoids red flashing while typing) - - `exists[value] == false` → `INVALID` - - else (unknown / validation pending) → skip -- New `fun validate(text: String, caret: Int, onResolved: () -> Unit)`: - - For each mention span where `value != "git-changes"`, `value !in paths`, not `underCaret`, - `value !in exists`, `value !in pending`: - - `pending.add(value)` - - `scope.launch { val ok = runCatching { service.files(workspace.directory, value).isNotEmpty() }.getOrDefault(false); exists[value] = ok; pending.remove(value); onResolved() }` - - `onResolved` is only invoked when an RPC actually completes (no launch → no callback → - no refresh loop). EDT marshalling is the caller's responsibility. - -#### `PromptPanel.kt` - -- `syncHighlights()`: - - Read `val caret = ed.caretModel.offset`. - - Call `provider.validate(ed.document.text, caret) { ApplicationManager.getApplication().invokeLater { if (!project.isDisposed) refreshHighlights() } }`. - - Pass `caret` to `provider.highlights(ed.document.text, caret)`. - - Re-running `syncHighlights()` from the refresh callback is safe: all values are now - cached or pending, so `validate` launches nothing and the callback chain terminates. -- Add error attributes key + mapping: - - companion: `private val INVALID_KEY = CodeInsightColors.WRONG_REFERENCES_ATTRIBUTES` - (red "unresolved reference" attributes — semantically a not-found file reference). - - `key(kind)`: add `INVALID -> INVALID_KEY` branch. -- New import: `com.intellij.openapi.editor.colors.CodeInsightColors`. - -Notes / non-goals: -- Validation is best-effort and cached; files created/deleted after a check may show a stale - state until re-typed. Acceptable for an input affordance. -- Attachment semantics (`mentionParts` / `mentionFileParts`, the tracked `paths` set used at - send time) are **unchanged**. This issue is purely the pre-send highlight. - ---- - -## Tests - -All tests use `BasePlatformTestCase` (real Application + EDT), per package conventions. -Update the two `completion()` / provider constructions to pass the new `scope` argument: -- `KiloPromptCompletionProviderTest.setUp` (provider construction) -- `PromptPanelTest.completion()` helper -- `SessionUi` line ~336 passes `scope = cs`. - -`FakeWorkspaceRpcApi`: add a `var fileResolver: ((String) -> List)? = null` -and make `files()` return `fileResolver?.invoke(path) ?: fileMatches`, so a single test can -make one path resolve and another not. (Kilo test file — no markers.) - -### `KiloPromptCompletionProviderTest.kt` - -- **Mid-token clean insert (new):** configure `@backend/deploy⟨caret⟩-dev.sh`, search returns - `backend/deploy-dev.sh`, accept via `myFixture.type('\n')`, assert document is - `"@backend/deploy-dev.sh "` and `mentionPaths()` contains `backend/deploy-dev.sh`. -- **Mid-token clean insert with trailing content (new):** `@backend/deploy⟨caret⟩-dev.sh tail` - → `"@backend/deploy-dev.sh tail"` (trailing space trimmed against existing whitespace). -- **Not-found mention → INVALID (new):** `fileResolver` returns empty for `unknownPath`; call - `validate("see @unknownPath", caret = -1) { ... }`, `waitFor` until resolved, then assert - `highlights("see @unknownPath", -1)` contains `Highlight(4, 16, INVALID)`. -- **Existing hand-typed mention → MENTION (new):** `fileResolver` returns a match for - `src/x.kt`; after `validate`/`waitFor`, `highlights("see @src/x.kt")` → `MENTION`. -- **Mention under caret is not flagged (new):** with `exists` empty, `highlights("@nope", caret = 5)` - is empty (skipped while being typed). -- **Update** `test highlights ignore untracked mentions`: keep asserting the *synchronous* - result is empty (pending), and add the async resolution assertion above. Rename to reflect - "pending → resolves to error". -- Existing tracked/`git-changes`/longest-first highlight tests keep passing unchanged. - -### `PromptPanelTest.kt` - -- **Undo/redo (new):** build `PromptPanel` with completion, `realize`, get - `field.getEditor(false)`. Insert text inside a `WriteCommandAction`/`CommandProcessor` - command, then resolve `PlatformCoreDataKeys.FILE_EDITOR` from the editor content - component's data context (`DataManager.getInstance().getDataContext(...)`), assert it is - non-null and wraps the prompt document, and that - `UndoManager.getInstance(project).undo(fileEditor)` reverts the text (and `redo` reapplies). - This exercises the real `uiDataSnapshot` contribution end-to-end. -- **Error highlight (new):** set `fileResolver`/`fileMatches` so `@missing` resolves to empty; - set `field.text = "@missing"`, pump events + `waitFor`, assert a highlighter spans - `@missing` with `CodeInsightColors.WRONG_REFERENCES_ATTRIBUTES`. -- **Update** `test prompt editor highlights validated commands and mentions` (currently - asserts `@unknown` has no highlight): make it deterministic under the new async validation — - either drive `@unknown` to resolve-not-found and assert the INVALID span, or keep it pending - and assert no synchronous highlight. Keep the `/new` + `@git-changes` assertions. -- Existing clear/bounded highlighter tests: confirm still ≤ 2 for `/new @git-changes`. - ---- - -## Verification - -From `packages/kilo-jetbrains/`: - -- `./gradlew typecheck` (or `bun run typecheck`) -- `./gradlew test` (targeted: the two prompt test classes above) - -Requires Java 21 (only diagnose Java if Gradle fails with a Java error). Also do a manual -`./gradlew runIde` smoke check: Cmd-Z/Cmd-Shift-Z in the prompt, mid-token completion, and a -not-found `@mention` turning red. - -## Touched files - -- `frontend/.../session/ui/editor/SessionEditorTextField.kt` — expose `FILE_EDITOR` (undo). -- `frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt` — token-end insert fix; - async existence validation + `INVALID` highlight kind; `scope` ctor param. -- `frontend/.../session/ui/prompt/PromptPanel.kt` — pass caret, drive validation, map - `INVALID` to error attributes. -- `frontend/.../session/SessionUi.kt` — pass `scope = cs` to the provider. -- `frontend/src/test/.../session/ui/prompt/KiloPromptCompletionProviderTest.kt` — tests. -- `frontend/src/test/.../session/ui/PromptPanelTest.kt` — tests. -- `frontend/src/test/.../testing/FakeWorkspaceRpcApi.kt` — `fileResolver` hook for tests. diff --git a/.kilo/plans/jetbrains-prompt-mention-slash-highlighting.md b/.kilo/plans/jetbrains-prompt-mention-slash-highlighting.md deleted file mode 100644 index fa1dc26863e..00000000000 --- a/.kilo/plans/jetbrains-prompt-mention-slash-highlighting.md +++ /dev/null @@ -1,214 +0,0 @@ -# JetBrains prompt: syntax-highlight @mentions and /commands - -## Goal - -Visually distinguish, inside the JetBrains chat prompt editor, the two token kinds the -completion system already understands: - -- `@mention` tokens (file/folder paths, plus the special `@terminal` / `@git-changes`) -- `/command` tokens (client slash actions + server commands), only at the very start of the prompt - -Highlighting is **semantic / validated** (the chosen mode): only mentions that resolve to a -real tracked path or special mention, and only `/commands` that match a known command, get -colored. Bare `@foo` / `/foo` that aren't real are left as plain text — same rule the VS Code -webview uses (`buildHighlightSegments` only paints tracked paths). - -## Current state (verified) - -The completion feature is already implemented and committed (`3150da9986` + -`7fd80d0af9` + `0c2a7c1c5b`). Relevant pieces: - -- `KiloPromptCompletionProvider` (`frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt`) - is a `TextCompletionProvider` that: - - owns the token grammar in `token(text, offset)` (`:158-165`): SLASH = leading `/` with the - text before it blank and no space; MENTION = `@` with no space. - - tracks inserted file-mention paths in a synchronized `paths` set; exposes `mentionPaths()` - (`:41`) and `clearMentions()` (`:43`). - - knows client action names via `clientNames()` (`:48`) and reads server commands from - `workspace.state.value.commands` (`:75-77`). - - inserts `@terminal` / `@git-changes` via `special()` (`:127-131`) — note these are **not** - added to `paths`. -- The editor is a real `EditorEx` behind `EditorTextField`: - `PromptPanel.editor` = `PromptEditorTextField(project, this, completion)` → - `SessionEditorTextField` (`PlainTextFileType`, completion-backed document). - The live editor is reachable via `editor.getEditor(false): EditorEx?` (lazy, non-null once shown). -- `PromptPanel` already has the hooks we need: - - a `DocumentListener.documentChanged` (`:222-229`) that runs on the EDT on every edit. - - `applyStyle(style)` (`:330-339`) re-applies the editor color scheme on theme change. - - `clear()` (`:342-348`) resets text + mentions. - - `completion: KiloPromptCompletionProvider?` field (`:91`) — null for the question - custom-answer editor, so highlighting naturally scopes to the prompt only. -- There is a proven in-repo highlighting precedent: `MdViewHybrid.applyTerm/applyShell` - (`ui/md/hybrid/MdViewHybrid.kt:641-690`) + `MdShellHighlight` (`ui/md/hybrid/MdShellHighlight.kt`) - apply ranges via `editor.markupModel.addRangeHighlighter(TextAttributesKey, start, end, - HighlighterLayer.SYNTAX + 1, HighlighterTargetArea.EXACT_RANGE)`. - -There is **no** existing highlighting in the prompt editor today — confirmed no `markupModel`, -`RangeHighlighter`, or `TextAttributes` usage in `prompt/` or `editor/`. - -## Options for how to highlight (with recommendation) - -### Option A — MarkupModel range highlighters, recomputed on edit (RECOMMENDED) - -Recompute the validated token ranges on each document change and apply them to the live -editor's `markupModel` with theme-aware `TextAttributesKey`s, mirroring `MdViewHybrid`. - -- Pros: matches the existing in-repo pattern; entirely kilo-owned frontend code (no shared - files, no upstream merge surface, no extension-point registration); theme-aware; trivially - supports the **semantic** rule because the validation data (tracked paths, known commands) - is right there in the completion provider; recompute cost is negligible for short prompt text. -- Cons: highlights are decorative only (no PSI); we recompute the whole (small) buffer per edit. - -### Option B — Custom lexer-based `EditorHighlighter` via `EditorEx.setHighlighter` - -Write a small `Lexer` + `SyntaxHighlighter` for `@`/`/` tokens and set it on the editor in -`addSettingsProvider`. The platform then maintains incremental highlighting. - -- Pros: platform-managed incremental highlighting; integrates with the editor color scheme. -- Cons: a hand-written `Lexer` is more code; crucially a lexer only sees **characters**, not the - tracked-paths set or the loaded command list, so it cannot do the **semantic / validated** - highlighting the user asked for (it would color any `@word`/`/word`). It also has to coexist - with the completion-backed document setup. Heavier for a worse fit. - -### Option C — Full custom `Language` + `FileType` + `Annotator` (PSI) - -Register a dedicated language with a parser and an `Annotator` for semantic coloring. - -- Pros: the "proper" platform way; PSI unlocks future features. -- Cons: large effort and registration surface for two token kinds; conflicts with the existing - plain-text + completion document; overkill. - -**Recommendation: Option A.** It is the smallest change, reuses a tested repo pattern, keeps -everything in kilo-owned files, and is the only option that cleanly expresses semantic -validation. The rest of this plan implements Option A. - -## Design (Option A) - -### 1. Compute highlight ranges in the completion provider - -The provider already owns the grammar and the validation data, so the range computation belongs -there (single source of truth, no duplicated parsing). - -Add to `KiloPromptCompletionProvider`: - -```kotlin -enum class HighlightKind { MENTION, COMMAND } -data class Highlight(val start: Int, val end: Int, val kind: HighlightKind) - -/** Validated highlight ranges for the whole prompt text. EDT-safe, pure over [text]. */ -fun highlights(text: String): List -``` - -Rules inside `highlights(text)`: - -- **Command** (at most one, at the start): if `text` starts with `/`, take the run up to the - first whitespace as `name`; if `name` is in `clientNames()` or in - `workspace.state.value.commands.map { it.name }`, emit `Highlight(0, 1 + name.length, COMMAND)`. - Mirrors the SLASH branch of `token()` and `SessionUi.serverCommand`. -- **Mentions** (anywhere): for each candidate set member — the tracked `mentionPaths()` plus the - literals `terminal` and `git-changes` — find every `@` occurrence whose end is at a - whitespace or end-of-text, and emit `Highlight(idx, idx + 1 + value.length, MENTION)`. Match - **longest value first** so `@src/a.tsx` isn't shadowed by `@src/a.ts` (same precaution as - webview `syncMentionedPaths`/`findMentionRange`). Skip overlaps already covered. - -This keeps `@terminal`/`@git-changes` highlighted even though they aren't in `paths`, by checking -the two literals explicitly. - -### 2. Apply ranges to the live editor in `PromptPanel` - -- Add a field to track our own highlighters so we never disturb anything else on the editor: - `private val highlighters = mutableListOf()`. -- Add `@RequiresEdt private fun syncHighlights()`: - - `val provider = completion ?: return` (no provider → question editor → no highlighting). - - `val ed = editor.getEditor(false) ?: return` (editor not realized yet). - - Remove our previous highlighters: `highlighters.forEach(ed.markupModel::removeHighlighter); highlighters.clear()`. - - For each `provider.highlights(ed.document.text)`, clamp to `document.textLength`, then - `ed.markupModel.addRangeHighlighter(keyFor(kind), start, end, HighlighterLayer.SYNTAX + 1, - HighlighterTargetArea.EXACT_RANGE)` and store the returned highlighter. - - `keyFor(MENTION)` / `keyFor(COMMAND)` map to `TextAttributesKey`s (see step 3). -- Call `syncHighlights()` from: - - the existing `documentChanged` listener (`:222-229`), after `triggerCompletion(e)` — covers - typing, completion inserts (which mutate the document and update `paths`), paste, and clear. - - the `addSettingsProvider` block (after the editor is configured) so the first realization of - the editor paints existing draft text. - - `applyStyle(...)` is not strictly required because `TextAttributesKey` highlighters recolor - with the scheme automatically and the editor instance is retained, but add a - `syncHighlights()` there too for safety on Look-and-Feel/scheme changes. - - when the server command list arrives: the `commands` list can load after the user has typed - `/foo`. Re-run `syncHighlights()` when workspace state updates. `PromptPanel` doesn't observe - workspace state directly today; the simplest wiring is to expose a `PromptPanel.refreshHighlights()` - (EDT) and call it from `SessionUi` where it already reacts to `SessionControllerEvent.WorkspaceReady` - (`SessionUi.kt:377`). Keep this a no-op-cheap call. - -### 3. Colors — theme-aware `TextAttributesKey` - -Per the JetBrains UI guidelines, prefer existing platform keys (as `MdShellHighlight` does with -`DefaultLanguageHighlighterColors.*`). Use: - -- `MENTION` → `DefaultLanguageHighlighterColors.METADATA` (renders like `@annotation` accents — - a natural fit for `@mentions`). -- `COMMAND` → `DefaultLanguageHighlighterColors.KEYWORD` (distinct, keyword-like). - -These require **no** registration and recolor with the active editor scheme automatically. - -Optional enhancement (not required for first cut): register two custom keys -(`KILO_PROMPT_MENTION`, `KILO_PROMPT_COMMAND`) with the above as fallbacks plus a color-settings -page, so users can recolor them. Defer unless we want user-configurable colors. - -### 4. Edge cases - -- Token at caret while typing: highlight only validated tokens, so a half-typed `@de` stays plain - until completion inserts a real path (which adds it to `paths` and triggers a recompute). This - matches the webview's tracked-path behavior and avoids flicker. -- Overlapping/duplicate paths: longest-first matching + skip-covered avoids double highlighting. -- Empty/`null` editor: guard with `getEditor(false)?` (already the pattern used at `:335`,`:467`). -- Performance: prompt text is short; full rescan per keystroke is fine. No debounce needed; if it - ever matters, gate on "text contains '@' or starts with '/'". - -## Files to change (all kilo-owned — no shared/upstream files, no `kilocode_change` markers needed) - -- `frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt` - - add `HighlightKind`, `Highlight`, and `highlights(text)`. -- `frontend/.../session/ui/prompt/PromptPanel.kt` - - add `highlighters` list, `syncHighlights()`, call sites (document listener, - `addSettingsProvider`, `applyStyle`), and a public `refreshHighlights()` for the - command-loaded case. -- `frontend/.../session/SessionUi.kt` - - call `prompt.refreshHighlights()` from the `WorkspaceReady` handler (`:377`). - -No new bundle strings, RPC, DTOs, backend, or extension-point registration. - -## Testing - -Follow the existing JetBrains pattern (`BasePlatformTestCase`, no mocks). There is already -`KiloPromptCompletionProviderTest` using `myFixture.configureByText` + `TextCompletionUtil.installProvider`. - -1. Provider unit tests for `highlights(text)` (pure function, no editor needed): - - `/new ...` at start with `new` a known client action → one COMMAND range over `/new`. - - `/bogus ...` → no COMMAND range. - - `/foo` not at start (e.g. `hi /foo`) → no range. - - `@src/a.ts` present in `paths` → MENTION range; `@src/a.tsx` longest-first not shadowed. - - `@terminal` / `@git-changes` literals → MENTION ranges without being in `paths`. - - `@unknownPath` not tracked → no range. -2. `PromptPanelTest` (real EDT) — type text, drain EDT, assert the editor's markup model contains - the expected number of highlighters with the expected ranges/keys; assert `clear()` removes - them; assert no highlighter growth across repeated edits (bounded count), per the - stress/leak-test guidance for editor-backed UI. - -## Verification - -From `packages/kilo-jetbrains/` (requires Java 21 — check `java -version` first): - -- `./gradlew typecheck` -- `./gradlew test` (or the smallest relevant test task for the frontend module) - -Optionally `./gradlew runIde` to eyeball mention/command coloring in the prompt. - -## Out of scope / notes - -- Completion, file search, `@terminal`/`@git-changes` attachments, server-command execution, and - send-time file parts already exist — this plan only adds visual highlighting on top. -- `@terminal` content is currently a backend stub (`KiloWorkspaceRpcApiImpl.terminalOutput` returns - `null`) and `searchFiles` never advertises `terminal=true`; that's a separate completion-content - gap, independent of highlighting. The literal `@terminal` will still be highlighted if typed. -- Keep all logic in the kilo-owned `frontend` files above to preserve the small upstream diff. diff --git a/.kilo/plans/jetbrains-root-mention-completions.md b/.kilo/plans/jetbrains-root-mention-completions.md deleted file mode 100644 index a8031098ad4..00000000000 --- a/.kilo/plans/jetbrains-root-mention-completions.md +++ /dev/null @@ -1,65 +0,0 @@ -# JetBrains Root Mention Completions - -## Goal - -When a JetBrains user types `@` in the prompt with no query, show useful file/folder suggestions from the workspace root instead of only `@git-changes`, matching the VS Code behavior more closely. - -## Current Findings - -- Frontend completion is implemented in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProvider.kt`. -- `mention(prefix, result)` calls `search(prefix)` for all mention completions. With plain `@`, `prefix == ""`. -- The backend RPC is `KiloWorkspaceRpcApiImpl.searchFiles(...)` in `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt`. -- Backend `search(project, base, query, limit)` currently returns `emptyList()` when `query.trim().isBlank()`, so the frontend only has special mentions such as `@git-changes`. -- VS Code file mentions request both file and folder search and merge file/folder/open-editor results in `packages/kilo-vscode/src/kilo-provider/file-search.ts`; for the JetBrains fix, the smallest useful parity step is root-directory prepopulation for blank queries. - -## Implementation Plan - -1. Keep the existing fuzzy search path unchanged for non-empty mention queries. - - Leave the current `GotoFileModel` / `ChooseByName*` code in `KiloWorkspaceRpcApiImpl.search(...)` intact for typed queries such as `@main` or `@src/foo`. - - Only branch when `query.trim().isBlank()`. - -2. Add a blank-query root listing path in the backend. - - In `KiloWorkspaceRpcApiImpl.search(...)`, replace `if (text.isBlank()) return emptyList()` with a call to a new private helper, for example `roots(project, base, limit)`. - - Resolve `base` through `LocalFileSystem.getInstance().refreshAndFindFileByNioFile(base)`. - - Read immediate children only, not a recursive tree, to keep `@` completion fast and predictable. - - Convert each child through existing `fileDto(base, vf)` so returned paths remain relative and normalized the same way as fuzzy results. - -3. Filter root entries to avoid noisy or invalid suggestions. - - Exclude `.git` explicitly. - - Use IntelliJ project indexing APIs such as `ProjectFileIndex.getInstance(project)` to skip excluded files/directories when available. - - Preserve directories by returning `WorkspaceFileDto(..., directory = true)` via `fileDto` so the existing frontend folder icon path continues to work. - -4. Sort root suggestions for a useful default list. - - Put directories before files, matching common file picker behavior. - - Sort each group case-insensitively by name. - - Respect the existing `limit` after sorting. - - Do not pin or special-case repository-specific names unless later UX feedback asks for it. - -5. Keep special mention behavior unchanged. - - `@git-changes` should still appear when `search.git` is true and the prefix matches, including blank prefix. - - The frontend should continue to add special mentions before file suggestions, so git changes remains at the top when available. - -6. Add focused frontend completion coverage. - - Update `KiloPromptCompletionProviderTest` with a test for `@` where the fake RPC returns root entries plus `git = true`. - - Assert the lookup contains `git-changes` and returned file/folder paths. - - Assert the RPC saw a blank query (`listOf("")`) so the behavior is explicitly covered. - - If needed, extend the existing `file(...)` helper to create directory DTOs for icon/type coverage, but keep the test focused on visible completion values. - -7. Add backend coverage if practical in the existing test harness. - - First look for an existing IntelliJ project fixture pattern in backend tests. - - If lightweight backend fixture setup is practical, add a test around `searchFiles(directory, "", limit)` that creates root children and verifies immediate relative entries only. - - If backend fixture setup is heavy or brittle, rely on the frontend completion test plus `typecheck`, and note the backend behavior is exercised manually in sandbox. - -8. Verification. - - Run the smallest relevant JetBrains checks from `packages/kilo-jetbrains/`. - - Start with the targeted frontend test class, e.g. `./gradlew :frontend:test --tests ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest` if the module task supports it. - - Run `./gradlew typecheck` from `packages/kilo-jetbrains/` after tests pass. - - Do not run Java preflight unless Gradle reports a Java version or missing-Java failure. - -## Non-Goals - -- Do not change VS Code mention behavior. -- Do not introduce recursive blank-query search; initial `@` should show root-level entries only. -- Do not change how selected file mentions are serialized into prompt parts. -- Do not change rendering of mentioned files or synthetic file content; that is covered by the separate `jetbrains-file-mention-parity.md` plan. -- Do not modify shared OpenCode files; this work stays inside the Kilo JetBrains package. diff --git a/.kilo/plans/jetbrains-slash-actions.md b/.kilo/plans/jetbrains-slash-actions.md deleted file mode 100644 index c3baf20bbf4..00000000000 --- a/.kilo/plans/jetbrains-slash-actions.md +++ /dev/null @@ -1,63 +0,0 @@ -# JetBrains Slash Actions Organization Plan - -## Goal - -Organize JetBrains built-in slash actions so names and metadata are centralized instead of scattered across `SessionUi` and tests. - -## Current Findings - -- Built-in client slash actions are currently defined inline in `frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt` inside `slashActions()`. -- `SlashAction` is currently a nested data class inside `KiloPromptCompletionProvider`. -- Tests instantiate `KiloPromptCompletionProvider.SlashAction("new", ...)` directly in: - - `frontend/src/test/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProviderTest.kt` - - `frontend/src/test/kotlin/ai/kilocode/client/session/ui/PromptPanelTest.kt` -- I found no `git-review` literal in `packages/kilo-jetbrains`. The prompt package does contain hardcoded `git-changes`, but that is an `@mention` resource, not a slash action. - -## Implementation Plan - -1. Add a top-level `SlashAction` class in the prompt package: - - Path: `frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/SlashAction.kt` - - Package: `ai.kilocode.client.session.ui.prompt` - - Move the current data shape out of `KiloPromptCompletionProvider`. - -2. Model built-in actions as named specs inside `SlashAction`: - - Add an inner `Spec` data class or equivalent to hold `name`, `descriptionKey`, and `hints`. - - Add named constants for each built-in action: - - `NEW` - - `SESSIONS` - - `MODELS` - - `AGENTS` - - `VARIANT` - - `COMPACT` - - `SETTINGS` - - `HELP` - - Add an ordered `ALL` list containing all specs. - -3. Keep execution binding in `SessionUi`: - - Replace inline string literals like `"new"`, `"sessions"`, etc. with the named `SlashAction` specs. - - Build executable `SlashAction` instances by binding each spec to its handler lambda. - - Keep the current behavior and ordering unchanged. - -4. Update `KiloPromptCompletionProvider` to use the top-level class: - - Remove the nested `data class SlashAction`. - - Constructor remains `actions: List`. - - Existing provider logic can continue to use `action.name`, `action.description`, `action.hints`, and `action.action`. - -5. Update tests to refer to centralized names: - - Replace `KiloPromptCompletionProvider.SlashAction` with `SlashAction`. - - Replace built-in hardcoded `"new"` references with `SlashAction.NEW.name` or helper-bound `SlashAction.NEW` where practical. - - Leave synthetic non-built-in test commands like `"next"` as custom test actions unless a test-only helper is clearer. - -6. Keep localization unchanged: - - Continue using existing `prompt.slash.*` keys in `KiloBundle.properties`. - - `SlashAction` specs can store the message key; `SessionUi` can resolve descriptions when binding specs. - -7. Verification: - - Run JetBrains typecheck from `packages/kilo-jetbrains`: `./gradlew typecheck`. - - Run targeted frontend tests if available through Gradle filtering for `KiloPromptCompletionProviderTest` and `PromptPanelTest`; otherwise run the smallest JetBrains test task that covers frontend tests. - -## Non-Goals - -- Do not change server/config command handling through `CommandDto`. -- Do not change `@git-changes` mention behavior unless explicitly requested; it is not a slash action. -- Do not add new built-in slash actions or change aliases/descriptions. diff --git a/.kilo/plans/jetbrains-slash-aliases.md b/.kilo/plans/jetbrains-slash-aliases.md deleted file mode 100644 index 71160287fb4..00000000000 --- a/.kilo/plans/jetbrains-slash-aliases.md +++ /dev/null @@ -1,75 +0,0 @@ -# Add JetBrains Slash Command Aliases - -## Goal -Add the VS Code slash-command alias parity that JetBrains is missing, and make those aliases execute the same client-side actions when typed and sent directly. - -## Current Findings -- VS Code aliases from `packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts`: - - `/clear` -> `/new` - - `/continue`, `/history`, `/resume` -> `/sessions` - - `/variants`, `/reasoning`, `/thinking` -> `/variant` - - `/smol`, `/condense` -> `/compact` -- JetBrains aliases currently in `SlashAction.kt`: - - `/history`, `/resume` -> `/sessions` - - `/modes` -> `/agents` - - `/reasoning` -> `/variant` - - `/smol` -> `/compact` -- Missing JetBrains aliases: - - `/clear` - - `/continue` - - `/variants` - - `/thinking` - - `/condense` -- `KiloPromptCompletionProvider` already uses `hints` as lookup strings and search matches, so adding missing hints improves autocomplete discovery. -- Direct send handling currently uses `serverCommand(text)` and only excludes canonical client names via `clientNames()`. It does not resolve client aliases, so manually sending `/clear` would fall through to a normal prompt unless selected from autocomplete. - -## Implementation Plan -1. Update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/SlashAction.kt`: - - Add `clear` to `NEW.hints`. - - Add `continue` to `SESSIONS.hints` while preserving `history` and `resume`. - - Add `variants` and `thinking` to `VARIANT.hints` while preserving `reasoning`. - - Add `condense` to `COMPACT.hints` while preserving `smol`. - -2. Update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProvider.kt`: - - Add a small client-action resolver, for example `fun clientAction(text: String): SlashAction?`. - - Parse only leading slash commands the same way `serverCommand(text)` does. - - Match the parsed name against each action's canonical `name` and `hints`. - - Return `null` for blank names and non-leading slash text. - - Update `clientNames()` or add a separate `clientTokens()` helper so `serverCommand(text)` also treats aliases as client-owned and does not route an alias to a server command with the same name. - - Update `highlights(text)` to highlight aliases as valid commands, not just canonical client command names. - -3. Update `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt`: - - In `sendPrompt`, after clearing the prompt and before server-command dispatch, call the new client-action resolver. - - If a client action is found, execute `action.action()` and return without calling `controller.prompt(...)` or `controller.command(...)`. - - Keep scroll follow behavior consistent with the current send flow. - - This will also make directly typed canonical commands such as `/new` execute client actions, which aligns with the alias behavior and avoids sending client commands as ordinary prompts. - -4. Add or update tests in `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/ui/prompt/KiloPromptCompletionProviderTest.kt`: - - Construct test actions with representative hints, including `clear` and `continue`. - - Assert slash completion finds a command by alias, e.g. `/cle` shows/selects `new`. - - Assert `clientAction("/clear")` resolves to the `new` action. - - Assert `clientAction("/continue")` resolves to the `sessions` action. - - Assert `clientAction("hi /clear")` is `null`. - - Assert `serverCommand("/clear")` is `null` even if a server command named `clear` exists. - - Assert `highlights("/clear")` marks the alias as `COMMAND`. - -5. Add a focused `SessionUi` behavior test if an existing harness makes it straightforward: - - Verify sending `/clear` triggers `manager.newSession()` and does not call `controller.prompt`. - - If the harness would require heavy UI plumbing, rely on provider resolver tests plus typecheck and avoid brittle UI tests. - -6. Add a changeset: - - Package: `@kilocode/kilo-jetbrains` - - Bump: `patch` - - Suggested text: `Support VS Code slash-command aliases in the JetBrains prompt.` - -## Verification -Run the smallest relevant checks from `packages/kilo-jetbrains/`: -- `./gradlew test --tests ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProviderTest` -- `bun run typecheck` - -If the focused Gradle test filter is not accepted by the repo setup, run `./gradlew test` from `packages/kilo-jetbrains/` instead. - -## Non-Goals -- Do not add `/export` or `/remote` in this change. -- Do not change server-side CLI slash commands. -- Do not introduce new RPC, backend services, or UI surfaces. diff --git a/.kilo/plans/jetbrains-slash-mention-completion.md b/.kilo/plans/jetbrains-slash-mention-completion.md deleted file mode 100644 index 78c2cfb1c42..00000000000 --- a/.kilo/plans/jetbrains-slash-mention-completion.md +++ /dev/null @@ -1,363 +0,0 @@ -# JetBrains: slash commands + file mentions via native IntelliJ completion - -## Goal - -Reach VS Code parity for the prompt input in the JetBrains plugin: - -- `/` slash commands (built-in client actions **and** server/custom commands a.k.a. workflows) -- `@` file/folder mentions - -Render both with **IntelliJ's native completion lookup popup** (not a custom Swing popup), since the -prompt is already a real `EditorTextField` over `PlainTextFileType`. - -File-mention search must use **JetBrains' own index API** (`FilenameIndex`). When the project index is -not built yet (dumb mode), the **mention completion only** must show a clear "indexing" message — the -rest of the plugin (including slash commands) must not wait or be blocked. - -## Key facts established during research - -- Prompt input: `session/ui/prompt/PromptPanel.kt` → `PromptEditorTextField` → `SessionEditorTextField : EditorTextField(project, PlainTextFileType.INSTANCE)`. No slash/mention handling today. -- Platform registers `TextCompletionContributor` for `language="TEXT"` (`LangExtensions.xml:1243`). It is a **no-op unless** a `TextCompletionProvider` is installed on that specific document's PSI file (`TextCompletionUtil.COMPLETING_TEXT_FIELD_KEY`). So enabling completion on the Kilo prompt does **not** affect other plain-text editors. -- Install path: either subclass `TextFieldWithCompletion` (which wires the provider + autopopup via `DocumentWithCompletionCreator`), or call `TextCompletionUtil.installProvider(psiFile, provider, autoPopup=true)` on the prompt document's PSI file. -- Autopopup: `AutoPopupController.getInstance(project).scheduleAutoPopup(editor)` (public, `analysis-impl`). For invocationCount 0 the contributor only fills when `AUTO_POPUP_KEY=true`. -- `TextCompletionUtil.getProvider` calls `DumbService.isUsableInCurrentContext(provider)`: a provider that is **not** `DumbAware` is skipped during indexing → no completion at all. Make the provider `DumbAware` so slash commands keep working while indexing. -- Slash-command data already exists: `KiloWorkspaceStateDto.commands: List`, exposed via `Workspace.state` / `KiloWorkspaceService`. -- File search: existing `KiloWorkspaceRpcApi.files(directory, path)` is a **literal path resolver**, not fuzzy search. Need a new fuzzy-search RPC. -- Split-mode constraint: completion runs in the **frontend** process which has no project index; `FilenameIndex` lives on the **backend**. File search therefore must go over RPC, and the dumb-mode flag is a **backend** condition surfaced in the RPC result. -- Server commands run via CLI `POST /session/{id}/command` (SDK `session.command`). JetBrains has no command-execution path yet (`KiloSessionRpcApi` has `prompt` but no `command`). -- Prompt parts: `PromptPartDto(type, text?, mime?, url?, filename?)`. File mention → `type="file", url="file://abs"` (reference, not base64; base64 is only used for pasted media in `PromptAttachment.part()`). -- `DumbService.isDumb` (`platform/core-api/.../DumbService.kt:66`), `FilenameIndex.processAllFileNames(...)` + `FilenameIndex.getVirtualFilesByName(name, scope)`, `NameUtil.buildMatcher("*"+query)` → `MinusculeMatcher`. -- Backend project resolution helper already exists: `KiloWorkspaceRpcApiImpl.project(path: Path): Project?` and `file(...)`/`clean(...)`. - -## Architecture decision - -One frontend `TextCompletionProvider` handles **both** `/` and `@`. Slash data is local (workspace -state). Mention data comes from a new backend `searchFiles` RPC backed by `FilenameIndex`, with an -`indexing` flag. The provider is `DumbAware` so it always runs; mention branch renders an indexing -notice when the backend reports the index isn't ready. Slash never depends on the index. - -``` -PromptPanel editor (frontend, PlainText) - └─ DocumentListener: on '/' or '@' typed → AutoPopupController.scheduleAutoPopup(editor) - └─ TextCompletionContributor (platform, language=TEXT) - └─ KiloPromptCompletionProvider (DumbAware) - ├─ getPrefix → detect SLASH vs MENTION vs none(null) - ├─ SLASH → client actions + Workspace.state.commands (no index) - └─ MENTION → KiloWorkspaceService.searchFiles(dir,q) ──RPC──▶ backend - FileSearchResultDto(indexing, files) - indexing=true → show "Indexing… file mentions soon" - else → file/folder lookup elements -``` - ---- - -## Phase 0 — Wire native completion into the prompt editor - -Files: `session/ui/editor/SessionEditorTextField.kt`, `session/ui/prompt/PromptEditorTextField.kt`, -`session/ui/prompt/PromptPanel.kt`, `session/SessionUi.kt`. - -1. Add a `TextCompletionProvider?` parameter to `SessionEditorTextField` (default null so the - question custom-answer editor stays plain). When non-null, install it on the document's PSI file. - Prefer the documented path: build the document via - `LanguageTextField.createDocument(value, PlainTextLanguage.INSTANCE, project, TextCompletionUtil.DocumentWithCompletionCreator(provider, autoPopup=true))` and pass it to the `EditorTextField` constructor; or call `TextCompletionUtil.installProvider(PsiDocumentManager.getInstance(project).getPsiFile(document), provider, true)` after construction. Keep the existing `uiDataSnapshot`/`PromptDataKeys.SEND` behavior. -2. `PromptEditorTextField` forwards the provider to `SessionEditorTextField`. -3. `PromptPanel` constructor gains a `completion: TextCompletionProvider` param (built in `SessionUi`, - see Phase 1/2). Keep all existing `addSettingsProvider` styling/soft-wrap/height logic — it still - applies because `TextFieldWithCompletion`/`EditorTextField` is the same component family. -4. In `PromptPanel`'s existing `DocumentListener.documentChanged`, after a single-char insertion of - `/` (only when it is the first non-space char of the document) or `@`, call - `AutoPopupController.getInstance(project).scheduleAutoPopup(editorComponent)`. Guard with - `project.isDisposed`. -5. `SessionUi.kt:328` constructs `PromptPanel` — pass the provider here, where `project`, `workspace`, - `controller`, and `app` are in scope. - -Acceptance: typing `/` or `@` in the prompt opens the native lookup; other plain-text editors are -unaffected. - ---- - -## Phase 1 — Slash commands - -New file: `session/ui/prompt/PromptSlashCompletion.kt` (helper that produces `LookupElement`s for the -slash branch), consumed by the shared provider in Phase 2's `fillCompletionVariants`. - -### 1a. Client-action commands (mirror `useSlashCommand.ts`) - -Define a small list of built-ins, each: name, description, hints (aliases), and an `action: () -> Unit`. -Map to existing JetBrains capabilities: - -| Command | Action target (existing) | -|---|---| -| `new` | start new session (SessionManager / new-session flow) | -| `sessions` (`history`,`resume`) | open history (`HistoryPanel`/`SessionManager`) | -| `models` | open `PromptPanel.model` (`ModelPicker`) | -| `agents` (`modes`) | open `PromptPanel.mode` (`ModePicker`) | -| `variant` (`reasoning`) | open `PromptPanel.reasoning` (`ReasoningPicker`) | -| `compact` (`smol`) | `controller.compact(...)` | -| `settings` | open Kilo settings | -| `help` | open `https://kilo.ai/docs` | - -- Each picker likely needs a public `open()`/`showPopup()` hook if it doesn't already expose one; add a - minimal method on the picker rather than reaching into internals. -- `export`/`remote` from VS Code are **dropped** for JetBrains (no equivalent capability today). Do not - invent features. -- Lookup element: `LookupElementBuilder.create(name).withPresentableText("/"+name) - .withTailText(" "+description, true).withLookupStrings(hints) - .withIcon(...)`. `InsertHandler`: clear the document (`context.document.setText("")`) and run - `action()` on the EDT via `ApplicationManager.getApplication().invokeLater`. Mark with a badge tail - ("custom"/"skill"/"mcp") analogous to VS Code where source info is available. - -### 1b. Server/custom commands (workflows) - -- Source: `workspace.state.value.commands` (`List` already populated). Filter out names - that collide with client actions. Build lookup elements; `InsertHandler` inserts `"/"+name+" "` and - positions the caret after it (do **not** clear / run an action). -- Execution at submit time (mirrors VS Code `prompt-input-utils` + `sendCommand`): - - In `PromptPanel.submit`/`SessionUi.sendPrompt`, detect a leading `/` where `` is a known - server command; split into `name` + `args` (rest of the text). If matched, route to a new - controller command path instead of a normal prompt. - - Add `SessionController.command(name: String, args: String, files: List)` mirroring - `prompt(...)`: lazily create session if needed, then call a new session RPC. - - New RPC `KiloSessionRpcApi.command(id, directory, name, args, model: ModelSelectionDto?)` → - backend `KiloSessionRpcApiImpl.command(...)` → CLI `POST /session/{id}/command` - (mirror `chat.prompt`; reuse the existing CLI HTTP client used by chat). Add a `CommandDto`-free - request payload as needed. - - Optimistic display + event flow reuse the existing prompt pipeline. - -Acceptance: `/` shows built-ins + workflows; selecting a built-in runs its action and clears input; -selecting a workflow inserts `/name `; submitting `/name args` runs the workflow. - ---- - -## Phase 2 — File mentions via `FilenameIndex`, with dumb-mode handling - -### 2a. Shared DTO + RPC - -File: `shared/.../rpc/dto/WorkspaceFileDto.kt` (add a result wrapper) or a new -`FileSearchResultDto.kt`: - -```kotlin -@Serializable -data class FileSearchResultDto( - val indexing: Boolean = false, - val files: List = emptyList(), - val terminal: Boolean = false, // an active terminal exists → offer @terminal - val git: Boolean = false, // project under git → offer @git-changes -) -``` - -File: `shared/.../rpc/KiloWorkspaceRpcApi.kt` — add: - -```kotlin -/** Fuzzy file/folder search via the IDE index. indexing=true means the index isn't ready yet. */ -suspend fun searchFiles(directory: String, query: String, limit: Int = 50): FileSearchResultDto -``` - -### 2b. Backend impl (JetBrains API) - -File: `backend/.../rpc/KiloWorkspaceRpcApiImpl.kt` — add `searchFiles`: - -```kotlin -override suspend fun searchFiles(directory: String, query: String, limit: Int): FileSearchResultDto { - val base = file(clean(directory) ?: directory) ?: return FileSearchResultDto() - val project = project(base) ?: return FileSearchResultDto() - if (DumbService.getInstance(project).isDumb) return FileSearchResultDto(indexing = true) - return try { - readAction { search(project, base, query, limit) } // com.intellij.openapi.application.readAction - } catch (e: IndexNotReadyException) { - // indexing started mid-search — surface the same not-ready state, do not block - FileSearchResultDto(indexing = true) - } -} -``` - -`search(project, base, query, limit)` (read action): -- `val matcher = NameUtil.buildMatcher("*").build()` when query blank, else `NameUtil.buildMatcher("*$query").build()` (`MinusculeMatcher`, case-insensitive substring/camel match). -- Collect candidate names: `FilenameIndex.processAllFileNames({ name -> if (matcher.matches(name)) names.add(name); names.size < CAP }, GlobalSearchScope.projectScope(project), null)` (cap candidate names, e.g. 2000, to bound work). -- For each matched name: `FilenameIndex.getVirtualFilesByName(name, GlobalSearchScope.projectScope(project))` → for each `vf`, compute path relative to `base` (skip if not under `base`), build `WorkspaceFileDto(path = relative, name = vf.name, directory = vf.isDirectory)`. -- Sort by `matcher.matchingDegree(name)` desc, then shorter path; take `limit`. -- Return `FileSearchResultDto(indexing = false, files = …)`. - -Notes: -- Do **not** call `DumbService.waitForSmartMode` / `runReadActionInSmartMode` — those block. We explicitly want the non-blocking `indexing=true` short-circuit. -- Perf escape hatch (only if `processAllFileNames` proves too heavy on huge projects): switch to - `GotoFileModel` / `ChooseByNameModelEx` which the IDE's "Go to File" uses. Keep `FilenameIndex` as the - default; note this alternative in code comments only if needed. -- Imports: `com.intellij.psi.search.FilenameIndex`, `com.intellij.psi.search.GlobalSearchScope`, - `com.intellij.openapi.project.DumbService`, `com.intellij.openapi.project.IndexNotReadyException`, - `com.intellij.psi.codeStyle.NameUtil`, `com.intellij.openapi.application.readAction`. - -### 2c. Frontend service - -File: `frontend/.../app/KiloWorkspaceService.kt` — add: - -```kotlin -suspend fun searchFiles(directory: String, query: String, limit: Int = 50): FileSearchResultDto = - try { call { searchFiles(directory, query, limit) } } - catch (e: Exception) { LOG.warn("file search failed dir=$directory q=$query", e); FileSearchResultDto() } -``` - -### 2d. Mention branch in the provider - -In `fillCompletionVariants` MENTION branch (runs on the completion **background** thread, off EDT): -- `val result = runBlockingCancellable { service.searchFiles(directory, prefix, 50) }` - (`com.intellij.openapi.progress.runBlockingCancellable`). Background-thread blocking is acceptable here; - never do this on the EDT. -- If `result.indexing`: - - `resultSet.addLookupAdvertisement(KiloBundle.message("prompt.mention.indexing"))` - - add one non-inserting info element: - `LookupElementBuilder.create("").withPresentableText(message).withIcon(AllIcons.General.Information)` - with an `InsertHandler` that no-ops (or removes itself). Optionally - `resultSet.restartCompletionWhenNothingMatches()` and `DumbService.getInstance(project).runWhenSmartMode { scheduleAutoPopup }` (frontend project) so the popup refreshes once indexing finishes. -- Else build a `LookupElement` per file/folder: present text `@`+path, tail = parent dir, file-type icon - (`FileTypeManager`/`AllIcons.Nodes.Folder`), `withLookupString(path)`; `InsertHandler` replaces the - `@query` token with `@path ` (see Phase 3). - -Directory used for `searchFiles` = `workspace.directory` (available in `SessionUi`); pass it into the -provider at construction. - -Acceptance: `@` shows fuzzy file/folder matches from the IDE index; during initial indexing the -mention popup shows an "indexing" notice while slash commands and the rest of the UI work normally. - -### 2e. Special mentions: `@terminal` and `@git-changes` - -Mirror VS Code (`terminal-context-utils.ts`, `git-changes-context-utils.ts`). Each is a single -synthetic mention that, on submit, embeds gathered text as a data-URL `file` part: -`PromptPartDto(type="file", mime="text/plain", url="data:text/plain;charset=utf-8,", filename=...)` -(`terminal-output.txt` / `git-changes.txt`). The `@terminal` / `@git-changes` tokens stay in the text. - -Completion candidates (in the MENTION branch, **before** file results, filtered by prefix like VS Code's -`getTerminalMentionResult`/`getGitChangesMentionResult`): -- `@terminal` — show only when a terminal is available (terminal tool window / `org.jetbrains.plugins.terminal` present and has a session). -- `@git-changes` — show only when the project root is under VCS/git. -These two require **no index**, so offer them even while `indexing=true` (do not gate behind dumb mode). - -Backend content RPCs (new on `KiloWorkspaceRpcApi`, since content depends on IDE/project state): -- `suspend fun terminalOutput(directory: String): String?` — read the active terminal widget scrollback. - Use `org.jetbrains.plugins.terminal.TerminalView.getInstance(project)` → active - `ShellTerminalWidget`/content; best-effort `getText()` of the terminal buffer. Return `null` if the - terminal plugin/tool window is unavailable so the frontend can hide the mention. Guard the - terminal-plugin dependency so the plugin still loads if it's absent. -- `suspend fun gitChanges(directory: String): String?` — current uncommitted changes as a unified diff. - Prefer VCS API (`ChangeListManager.getInstance(project).allChanges` → render patch) or shell - `git diff` (+ `git diff --staged`) via the existing CLI/process util scoped to `directory`. Return - `null`/empty when not a git repo. - -Availability flags for the completion popup: expose lightweight checks -(`terminalAvailable(directory)` / `gitAvailable(directory)`) — either as separate RPCs or folded into -`FileSearchResultDto` (e.g. add `terminal: Boolean`, `git: Boolean` flags returned by `searchFiles`). -Prefer folding into `searchFiles` to avoid extra round-trips; the provider then knows which special -mentions to offer alongside file results (and during indexing, `searchFiles` can still return the flags -with `indexing=true` and empty `files`). - -Submit-time assembly (Phase 3): when `@terminal` / `@git-changes` is present in the text, call the -corresponding backend RPC, encode the returned text, and append the data-URL `file` part. Skip if the -RPC returns null/empty. - -Acceptance: `@` offers `@terminal` (when a terminal exists) and `@git-changes` (when under git); both are -offered even while indexing; submitting attaches the gathered terminal/diff text as a file part. - ---- - -## Phase 3 — Mention insert + attachment building on submit - -- Track inserted mention paths. Simplest robust approach: on submit, re-derive mentions from the text - by scanning for `@` tokens whose `` matches a path we offered/inserted. Mirror VS Code's - `buildFileAttachments`: keep a `Set` of inserted paths in the provider/panel; the mention - `InsertHandler` adds the path to that set. -- `InsertHandler` for a file: compute the `@query` token range before the caret, replace with - `@ ` (trailing space unless next char is whitespace), and register ``. -- In `PromptPanel.submit`, for each tracked path still present as `@` in the text, append a - `PromptPartDto(type="file", mime="text/plain", url="file://", filename=)` where - ` = directory + "/" + path` (or the path if already absolute). Keep the `@path` text in the - message (matches VS Code). -- If the text contains `@terminal` / `@git-changes`, call `workspace.terminalOutput(dir)` / - `workspace.gitChanges(dir)`; when non-empty, append a data-URL `file` part (Phase 2e). These RPCs run - off the EDT inside the existing pooled-thread submit path (`PromptPanel.submit` already does - `executeOnPooledThread`). -- Optional polish (defer): atomic backspace deletion of a whole `@path`, arrow-key skip, selection - snapping — the editor already handles most caret behavior; only add if needed. - -Acceptance: submitting a prompt with `@src/Foo.kt` sends a `file` part referencing that file plus the -text. - ---- - -## i18n / resources - -- Add bundle keys: `prompt.mention.indexing`, slash command descriptions, badges - (`prompt.slash.badge.custom/skill/mcp`) in the frontend `*.properties` (mirror VS Code `en.ts` - keys). Keep all user-visible strings in properties. - -## Testing (extend `SessionControllerTestBase` / `BasePlatformTestCase`) - -- `KiloPromptCompletionProvider` prefix detection: SLASH only at text start, MENTION after whitespace, - null otherwise (pure unit test on `getPrefix`). -- Slash: client actions present + invoke their action and clear text; server commands inserted as - `/name `; submit routes `/name args` to `controller.command`. -- Mentions: with a real indexed test project, `searchFiles` returns expected relative paths; build - lookup elements; insert handler produces `@path ` and a `file` part on submit. -- **Dumb-mode**: drive `searchFiles` while `DumbService` is dumb (or stub the backend RPC to return - `indexing=true` via `FakeSessionRpcApi`-style fake for the workspace RPC) and assert the mention - branch yields the indexing advertisement/info element and **no** file elements, while the slash - branch still returns commands. Use `DumbModeTestUtils` / `BasePlatformTestCase` dumb helpers for the - backend search unit test. -- Native completion smoke test: install provider on an `EditorTextField`, type `/` and `@`, assert the - platform builds the expected lookup items (use `CompletionAutoPopupTestCase`/`myFixture` patterns where - feasible; otherwise test the provider directly via a synthetic `CompletionParameters`). -- No new EDT/threading violations; `searchFiles` runs in a read action; RPC never called on EDT. - -## File-change checklist - -Shared: -- `shared/.../rpc/dto/` add `FileSearchResultDto` (with `indexing`/`terminal`/`git` flags). -- `shared/.../rpc/KiloWorkspaceRpcApi.kt` add `searchFiles`, `terminalOutput`, `gitChanges`. -- `shared/.../rpc/KiloSessionRpcApi.kt` add `command` (Phase 1b). - -Backend: -- `backend/.../rpc/KiloWorkspaceRpcApiImpl.kt` add `searchFiles` (+ `search` helper), `terminalOutput` - (terminal plugin, guarded), `gitChanges` (VCS API or `git diff`), imports. -- `backend/.../rpc/KiloSessionRpcApiImpl.kt` add `command` → CLI `/session/{id}/command` (Phase 1b). -- `backend/build.gradle.kts`: add an **optional** dependency on `org.jetbrains.plugins.terminal` for - terminal scrollback; keep it soft so the plugin loads without it (reflective/`PluginManager` guard or - `` style). Confirm against AGENTS "bundle third-party libs" rule — platform plugin - deps are declared via the IntelliJ Platform Gradle plugin, not bundled. - -Frontend: -- `frontend/.../app/KiloWorkspaceService.kt` add `searchFiles`, `terminalOutput`, `gitChanges`. -- `frontend/.../session/ui/editor/SessionEditorTextField.kt` accept + install provider. -- `frontend/.../session/ui/prompt/PromptEditorTextField.kt` forward provider. -- `frontend/.../session/ui/prompt/PromptPanel.kt` accept provider param; autopopup trigger in doc - listener; mention→file-part assembly in `submit`; server-command submit routing. -- `frontend/.../session/ui/prompt/KiloPromptCompletionProvider.kt` (new): `TextCompletionProvider, DumbAware`. -- `frontend/.../session/ui/prompt/PromptSlashCompletion.kt` (new, optional split): slash lookup builders. -- `frontend/.../session/SessionUi.kt` construct provider with `project`/`workspace`/`controller`/`app` - and pass to `PromptPanel`; wire client-action targets (picker `open()` hooks). -- `frontend/.../session/controller/SessionController.kt` add `command(name,args,files)` (Phase 1b). -- Picker components (`ModePicker`/`ModelPicker`/`ReasoningPicker`) expose a public `open()` if missing. -- Frontend `*.properties` add bundle keys. - -## Scope decisions (confirmed) - -1. **Server-command execution (Phase 1b): included now** — new `KiloSessionRpcApi.command` + CLI - `/session/{id}/command` wiring is part of this plan. -2. **Special mentions: include `@terminal` + `@git-changes`** (Phase 2e), gated on availability. -3. **`/export` and `/remote`: dropped** on JetBrains (no equivalent capability). - -## Suggested execution order - -Phase 0 → 1a (client actions) → 2a–2d (files + dumb-mode) → 3 (file attachments) → 1b (workflow -execution) → 2e (terminal/git-changes mentions). Each phase is independently testable. - -## Risks - -- `FilenameIndex.processAllFileNames` cost on very large projects — mitigated by candidate cap + the - `GotoFileModel` escape hatch. -- `runBlockingCancellable` inside completion fill: safe on the background completion thread, but keep - the RPC fast and cancellable; never run on EDT. -- Provider must be `DumbAware` or completion is fully suppressed during indexing. -- `@terminal` scrollback depends on the optional terminal plugin and its (semi-internal) widget API — - keep it best-effort and warn if it relies on an experimental/internal IntelliJ API; hide the mention - when unavailable rather than failing. -- `gitChanges` via shelling `git diff` must use the project-spawn process util (Windows `windowsHide`) - and be scoped to `directory`; large diffs should be size-capped before embedding as a data URL. diff --git a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt index b2f2c746923..ec37e0c6a60 100644 --- a/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt +++ b/packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/rpc/KiloWorkspaceRpcApiImpl.kt @@ -394,8 +394,11 @@ class KiloWorkspaceRpcApiImpl : KiloWorkspaceRpcApi { return WorkspaceFileDto(rel, vf.name, vf.isDirectory) } - private fun gitAvailable(base: Path): Boolean = gitCache.getOrPut(base.toString()) { - Files.exists(base.resolve(".git")) || git(base, "rev-parse", "--is-inside-work-tree").trim() == "true" + private fun gitAvailable(base: Path): Boolean { + if (Files.exists(base.resolve(".git"))) return true + return gitCache.getOrPut(base.toString()) { + git(base, "rev-parse", "--is-inside-work-tree").trim() == "true" + } } private fun git(base: Path, vararg args: String): String { diff --git a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/WorkspacePathScopingTest.kt b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/WorkspacePathScopingTest.kt index 4746cda4811..96b3f6776a6 100644 --- a/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/WorkspacePathScopingTest.kt +++ b/packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/rpc/WorkspacePathScopingTest.kt @@ -6,45 +6,50 @@ import kotlin.test.assertEquals import kotlin.test.assertNull class WorkspacePathScopingTest { - private val base = Path.of("/home/user/project") + // Derive an absolute, OS-portable base from the real home dir so the test runs identically on + // Windows, macOS, and Linux (no hardcoded POSIX "/home/..." literals). + private val base: Path = Path.of(System.getProperty("user.home")).resolve("kilo-scope-test").normalize() + + private fun at(vararg segments: String): Path = segments.fold(base) { acc, s -> acc.resolve(s) } @Test fun `in-base file returns forward-slash relative path`() { - assertEquals("src/A.kt", relativeWithinBase(base, Path.of("/home/user/project/src/A.kt"))) + assertEquals("src/A.kt", relativeWithinBase(base, at("src", "A.kt"))) } @Test fun `nested file returns nested relative path`() { - assertEquals("a/b/c.kt", relativeWithinBase(base, Path.of("/home/user/project/a/b/c.kt"))) + assertEquals("a/b/c.kt", relativeWithinBase(base, at("a", "b", "c.kt"))) } @Test fun `base itself is rejected as blank`() { - assertNull(relativeWithinBase(base, Path.of("/home/user/project"))) + assertNull(relativeWithinBase(base, base)) } @Test fun `sibling directory outside base is rejected`() { - assertNull(relativeWithinBase(base, Path.of("/home/user/other/A.kt"))) + assertNull(relativeWithinBase(base, base.resolveSibling("other").resolve("A.kt"))) } @Test fun `parent directory is rejected`() { - assertNull(relativeWithinBase(base, Path.of("/home/user"))) + assertNull(relativeWithinBase(base, base.parent)) } @Test fun `traversal that escapes base is rejected after normalization`() { - assertNull(relativeWithinBase(base, Path.of("/home/user/project/../secret/A.kt"))) + assertNull(relativeWithinBase(base, base.resolve("..").resolve("secret").resolve("A.kt"))) } @Test fun `traversal that stays inside base is kept after normalization`() { - assertEquals("src/A.kt", relativeWithinBase(base, Path.of("/home/user/project/x/../src/A.kt"))) + assertEquals("src/A.kt", relativeWithinBase(base, base.resolve("x").resolve("..").resolve("src").resolve("A.kt"))) } @Test fun `prefix sibling is not treated as inside base`() { - assertNull(relativeWithinBase(base, Path.of("/home/user/project-2/A.kt"))) + val sibling = base.resolveSibling(base.fileName.toString() + "-2") + assertNull(relativeWithinBase(base, sibling.resolve("A.kt"))) } }