Skip to content

ADFA-3753: handle file and build events to update kt source file index#1214

Merged
itsaky-adfa merged 17 commits into
stagefrom
fix/ADFA-3753
May 5, 2026
Merged

ADFA-3753: handle file and build events to update kt source file index#1214
itsaky-adfa merged 17 commits into
stagefrom
fix/ADFA-3753

Conversation

@itsaky-adfa
Copy link
Copy Markdown
Contributor

@itsaky-adfa itsaky-adfa commented Apr 19, 2026

See ADFA-3753 for more details.

@itsaky-adfa itsaky-adfa requested a review from a team April 19, 2026 18:36
@itsaky-adfa itsaky-adfa self-assigned this Apr 19, 2026
Base automatically changed from fix/ADFA-3729 to stage April 21, 2026 18:27
Copy link
Copy Markdown
Contributor

@dara-abijo-adfa dara-abijo-adfa left a comment

Choose a reason for hiding this comment

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

Can this PR be broken down into smaller ones?

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

EventBus subscribers are added to KotlinLanguageServer to handle file lifecycle events and build completion, triggering Kotlin source refresh and incremental indexing. CompilationKind is refactored from enum to sealed interface with file-extension matching. File lifecycle handlers propagate through CompilationEnvironment, KtSymbolIndex, and worker classes to maintain incremental source indices. Module search scope caching and a new ResolutionScopeProvider service support analysis invalidation. Analysis logic switches to session-based analyzeMaybeDangling approach.

Changes

Kotlin Source Indexing via File Lifecycle Events

Layer / File(s) Summary
Type System & Data Shapes
lsp/kotlin/.../CompilationKind.kt
CompilationKind transforms from enum class with Default/Script to sealed interface with data object singletons. Adds fileExtensions property and acceptsFile(Path): Boolean method for file-kind matching.
Compilation Core
lsp/kotlin/.../Compiler.kt, lsp/kotlin/.../CompilationEnvironment.kt
Compiler and CompilationEnvironment constructors now take kind: CompilationKind parameter. Compiler.compilationKindFor(file) uses acceptsFile to determine kind and throws for unsupported files. Compiler.refreshSources() added to trigger indexing refresh.
File Lifecycle Handlers in Environment
lsp/kotlin/.../CompilationEnvironment.kt
New notifyElementModifiedForPath helper and suspend methods onFileCreated, onFileRemoved, onFileMoved update Kotlin source metadata, invalidate module search scopes via ResolutionScopeProvider, and trigger LL FIR session invalidation. refreshSources() delegates to ktSymbolIndex.refreshSources().
Incremental Indexing API & Commands
lsp/kotlin/.../index/IndexCommand.kt, lsp/kotlin/.../index/KtSymbolIndex.kt
IndexCommand gains RemoveFromIndex variant. KtSymbolIndex adds kind parameter, new suspend methods (submitForIndexing, removeFromIndex, onFileMoved), and refreshSources() orchestration. Index jobs are refactored with startIndexing()/startScanning() helpers.
Index Workers
lsp/kotlin/.../index/ScanningWorker.kt, lsp/kotlin/.../index/IndexWorker.kt
ScanningWorker now requires kind: CompilationKind, filters discovered files by kind.acceptsFile(), and uses coroutineScope with isActive instead of AtomicBoolean. Entry point changed from start() to suspend fun scan(). IndexWorker loop uses isActive and handles new RemoveFromIndex command by deleting records from fileIndex/sourceIndex.
Module Search Scope Caching
lsp/kotlin/.../modules/AbstractKtModule.kt
Replaces lazy baseSearchScope with explicit thread-safe searchScopeLock and cached _baseSearchScope/_contentScope. Adds getOrCreateBaseSearchScope() to compute and cache scopes, invalidateSearchScope() to clear both, and overrides contentScope and toString().
Resolution Scope Service
lsp/kotlin/.../services/ResolutionScopeProvider.kt, lsp/kotlin/.../services/ProjectStructureProvider.kt
New ResolutionScopeProvider extends KaBaseResolutionScopeProvider with debug logging and invalidate(module)/invalidateAll() cache-clearing methods. ProjectStructureProvider adds getInstance(project) factory.
Analysis & Indexing Logic
lsp/kotlin/.../modules/KtFileExts.kt, lsp/kotlin/.../SourceFileIndexer.kt, lsp/kotlin/.../DeclarationsProvider.kt
New analyzeMaybeDangling helper detects dangling files and routes to analyzeCopy with PREFER_SELF, else analyze. SourceFileIndexer.indexSourceFile wraps whole file in single analyzeMaybeDangling(ktFile) session instead of per-declaration analysis. DeclarationsProvider.ktFilesForPackage uses mapNotNull to safely resolve virtual files.
EventBus Subscribers
lsp/kotlin/.../KotlinLanguageServer.kt
New subscribers onBuildCompleted, onFileCreated, onFileDeleted, onFileRenamed react to build and file lifecycle events. Each route to compiler.compilationEnvironmentFor(path) and call appropriate lifecycle handler (onFileCreated, onFileRemoved, or onFileMoved). Renames handle Kotlin-ness of old/new paths, calling onFileMoved when both are Kotlin and same environment, else remove+create separately.
Service Registration & Utilities
lsp/kotlin/.../resources/META-INF/kt-lsp/kt-lsp.xml, lsp/kotlin/.../utils/VirtualFileExts.kt, subprojects/kotlin-analysis-api/build.gradle.kts
XML registers new ResolutionScopeProvider as KaResolutionScopeProvider override. VirtualFileExts adds Path.toVirtualFileOrNull() extension. Build script updates Kotlin analysis-api dependency tag and SHA256 checksum.

Sequence Diagram

sequenceDiagram
    actor FileSystem as File System
    participant LSP as KotlinLanguageServer
    participant EventBus as EventBus
    participant Compiler as Compiler
    participant Env as CompilationEnvironment
    participant Index as KtSymbolIndex
    participant ModuleScope as AbstractKtModule<br/>Search Scope
    
    FileSystem->>EventBus: FileCreationEvent / FileRenameEvent
    EventBus->>LSP: onFileCreated / onFileRenamed
    LSP->>Compiler: compilationEnvironmentFor(path)
    Compiler->>Env: onFileCreated / onFileMoved
    Env->>Env: notifyElementModifiedForPath()
    Env->>ModuleScope: invalidateSearchScope()
    Env->>Index: submitForIndexing(path)
    Index->>Index: scan + index commands
    
    FileSystem->>EventBus: BuildCompletedEvent
    EventBus->>LSP: onBuildCompleted
    LSP->>Compiler: refreshSources()
    Compiler->>Env: refreshSources()
    Env->>Index: refreshSources()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related issues

  • Crash in Kotlin LSP #1253: The changes to SourceFileIndexer wrapping file visitation in analyzeMaybeDangling and using a single KaSession address the KaInaccessibleLifetimeOwnerAccessException crash evident in the reported stack trace, directly resolving the underlying lifetime-access issue.

Possibly related PRs

  • ADFA-3739: collect and report Kotlin syntax errors #1191: Both PRs modify KotlinDiagnosticProvider analysis logic; this PR switches diagnostics collection to analyzeMaybeDangling while the other adds PSI error collection and refactors diagnostic helpers, making them complementary changes to the same provider.
  • ADFA-3365: include Kotlin analysis api as dependency #1098: The retrieved PR adds Kotlin analysis API as a project dependency, which directly enables and supports the new Ka* APIs and services (e.g., KaSession, KaResolutionScopeProvider) introduced throughout this PR.
  • ADFA-2417: Add LSP support for Kotlin #907: Both PRs modify the core Kotlin LSP components (KotlinLanguageServer, CompilationEnvironment, KtSymbolIndex, ScanningWorker, CompilationKind) that were originally introduced in PR #907, indicating overlapping architectural changes to the same foundation.

Suggested reviewers

  • dara-abijo-adfa
  • jatezzz

🐰 A file was born, deleted, and renamed with glee,
The indexing keeps pace, incrementally free,
Sessions now dangling but handled with care,
Scopes cached and cleared—a refresh in the air!
EventBus whispers, the Kotlin source shines,
LSP's heartbeat in perfect design.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: handling file and build events to update the Kotlin source file index, which is clearly demonstrated by the EventBus subscribers and lifecycle event handlers added throughout the changeset.
Description check ✅ Passed The description references the ADFA-3753 issue ticket, which is related to the changeset's purpose of handling file and build events to update the source index, making it relevant to the changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ADFA-3753

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/Compiler.kt (1)

72-82: ⚠️ Potential issue | 🔴 Critical

.kts files will crash the new EventBus handlers.

DocumentUtils.isKotlinFile(file) returns true for both .kt and .kts, so for a .kts path compilationEnvironmentFor(file) falls through to compilationEnvironmentFor(CompilationKind.Script) which throws UnsupportedOperationException at Line 81. The new subscribers in KotlinLanguageServer (onFileCreated, onFileDeleted, onFileRenamed) all invoke compiler?.compilationEnvironmentFor(path) for any Kotlin path, so creating/deleting/renaming a .kts file will crash inside scope.launch { ... } and silently skip index maintenance (and log a coroutine exception).

Either return null for kinds that don't yet have an environment, or have the callers guard against Script:

🛠️ Suggested fix
 	fun compilationEnvironmentFor(compilationKind: CompilationKind): CompilationEnvironment =
 		when (compilationKind) {
 			CompilationKind.Default -> defaultCompilationEnv
-			CompilationKind.Script -> throw UnsupportedOperationException("Not supported yet")
+			// Script environment is not yet implemented; callers treat a null env
+			// as "no indexing/analysis for this file kind".
+			CompilationKind.Script -> defaultCompilationEnv // or: null, with signature change
 		}

If you prefer to keep the explicit failure, please guard every caller (including the new rename handler that looks up both fromEnv and toEnv) against CompilationKind.Script.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/Compiler.kt`
around lines 72 - 82, The crash happens because compilationEnvironmentFor(file:
Path) delegates .kts files to compilationEnvironmentFor(CompilationKind.Script)
which throws UnsupportedOperationException; change the behavior so
compilationEnvironmentFor(compilationKind: CompilationKind) returns null for
CompilationKind.Script (instead of throwing) and propagate null back to callers,
or alternatively update callers in KotlinLanguageServer (onFileCreated,
onFileDeleted, onFileRenamed) to guard against CompilationKind.Script by
checking for null/unsupported before using the returned CompilationEnvironment;
locate the two overloads compilationEnvironmentFor(file: Path) and
compilationEnvironmentFor(compilationKind: CompilationKind) and update the
Script branch to return null (or add null checks in the three
KotlinLanguageServer subscriber methods) so .kts files do not trigger an
exception.
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt (1)

183-188: ⚠️ Potential issue | 🔴 Critical

Cancel scanningJob before joining to prevent potential hang — submitting Stop does not cancel the scanning coroutine.

The current implementation has a race condition: ScanningWorker.scan() continuously submits commands via indexWorker.submitCommand(), which calls scanChannel.send(). This suspend function blocks when the channel is full. After close() submits IndexCommand.Stop, the IndexWorker stops consuming from the queue. If ScanningWorker is mid-submission when this happens, scanChannel.send() will suspend indefinitely. The isActive checks in ScanningWorker only trigger on explicit coroutine cancellation, not on the Stop command. Therefore, scanningJob?.join() will hang waiting for a coroutine that is blocked trying to send to a full, no-longer-consumed channel.

Use scanningJob?.cancelAndJoin() to explicitly cancel the scanning coroutine before joining, or re-introduce a direct scanningWorker.stop() method that sets an internal flag checked during iteration.

Suggested change
 suspend fun close() {
+	scanningJob?.cancelAndJoin()
 	indexWorker.submitCommand(IndexCommand.Stop)
-
-	scanningJob?.join()
 	indexingJob?.join()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 183 - 188, close() can hang because submitting IndexCommand.Stop
doesn't cancel a scanning coroutine blocked in
IndexWorker.submitCommand/scanChannel.send(); explicitly cancel the scanning job
before joining. Update close() to call scanningJob?.cancelAndJoin() (or call a
new ScanningWorker.stop() that sets a cancellation flag and then cancelAndJoin)
prior to submitting IndexCommand.Stop and before indexingJob?.join(), ensuring
ScanningWorker.scan() responds to cancellation and
IndexWorker.submitCommand/scanChannel.send() cannot leave the coroutine
permanently suspended.
🧹 Nitpick comments (2)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/ScanningWorker.kt (1)

29-34: Potentially noisy warning for legitimately-filtered files.

When the Default-kind ScanningWorker walks source roots that contain .kts (build scripts, tests, Gradle files) or other non-.kt files, each one will emit a WARN log through this branch. Filtering by kind is expected behavior, so logging at debug (or only warning when toNioPathOrNull() itself returns null) would avoid polluting warnings with routine filtering:

-			.filter {
-				it.toNioPathOrNull()?.let { path -> kind.acceptsFile(path) } ?: run {
-					logger.warn("rejecting {} from kt source index", it)
-					false
-				}
-			}
+			.filter {
+				val path = it.toNioPathOrNull()
+				if (path == null) {
+					logger.warn("Could not convert {} to nio path; skipping", it)
+					return@filter false
+				}
+				kind.acceptsFile(path)
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/ScanningWorker.kt`
around lines 29 - 34, The logger.warn inside ScanningWorker's filtering lambda
is noisy for routinely filtered files; change the branch so that when
toNioPathOrNull() returns a Path and kind.acceptsFile(path) is false you log at
debug (or not at all), but if toNioPathOrNull() returns null (indicating an
unexpected/unparseable VirtualFile) keep or escalate to logger.warn including
the file id; update the filter using the same toNioPathOrNull()/kind.acceptsFile
check to only warn on null and use logger.debug for normal rejects to avoid
spamming WARNs.
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt (1)

320-399: Consider ThreadMode.ASYNC for consistency with the other subscribers.

All the pre-existing document-lifecycle subscribers in this class (onDocumentOpen, onDocumentChange, onDocumentClose, onDocumentSaved) are annotated with @Subscribe(threadMode = ThreadMode.ASYNC). The new onBuildCompleted, onFileCreated, onFileDeleted, and onFileRenamed handlers use the default POSTING mode, so their body (including DocumentUtils.isKotlinFile, event.file.toPath(), and the scope.launch dispatch itself) runs synchronously on whichever thread posts the event — typically the main thread for file/build events. Matching the existing convention keeps index maintenance off the posting thread:

-	`@Subscribe`
+	`@Subscribe`(threadMode = ThreadMode.ASYNC)
 	`@Suppress`("unused")
 	fun onBuildCompleted(event: BuildCompletedEvent) {

(Repeat for onFileCreated, onFileDeleted, onFileRenamed.)

Also worth flagging: because of the UnsupportedOperationException thrown by Compiler.compilationEnvironmentFor(CompilationKind.Script), these handlers will crash inside scope.launch for any .kts path (see the comment on Compiler.kt). Once that root cause is addressed, the handlers themselves look correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`
around lines 320 - 399, Update the four handlers (onBuildCompleted,
onFileCreated, onFileDeleted, onFileRenamed) to use `@Subscribe`(threadMode =
ThreadMode.ASYNC) and move any work that runs on the posting thread (calls to
event.file.toPath(), DocumentUtils.isKotlinFile, and any index operations)
inside the coroutine started by scope.launch so the posting thread does minimal
work; also wrap calls to compiler?.compilationEnvironmentFor(...) in a try/catch
(or null-check) to guard against the UnsupportedOperationException from
Compiler.compilationEnvironmentFor(CompilationKind.Script) to avoid crashing
when a .kts path is encountered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt`:
- Around line 64-68: The RemoveFromIndex branch computing filePath uses
cmd.vf.path fallback which differs from the insertion normalization in
SourceFileIndexer (which uses (virtualFile.toNioPathOrNull() ?:
backingFilePath)!!.pathString), causing mismatched keys on some platforms;
change the removal key computation in the IndexCommand.RemoveFromIndex handler
to use the same normalization used for insertion (i.e., prefer toNioPathOrNull()
and fall back to the same backingFilePath-derived Path then .pathString) so
fileIndex.remove and sourceIndex.removeBySource target the exact stored key, and
also remove or guard the use of !! so you handle the rare case both conversions
fail (e.g., skip removal or log an error) to avoid an NPE.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 115-120: The warning message in getVirtualFileOrWarn is hardcoded
to "cannot submit {} for indexing" though the function is used by
submitForIndexing, removeFromIndex, and onFileMoved; change getVirtualFileOrWarn
to accept a context/action string parameter (e.g., "submit for indexing",
"remove from index", "reindex after move") and update all callers
(submitForIndexing, removeFromIndex, onFileMoved) to pass the appropriate action
so the logger.warn prints the correct, contextual message when
path.toVirtualFileOrNull() returns null.
- Around line 130-144: removeFromIndex and onFileMoved silently return when
getVirtualFileOrWarn(path) fails; change the removal flow to use the path rather
than requiring a VirtualFile. Update the IndexCommand.RemoveFromIndex variant to
accept a java.nio.file.Path (or add an overload RemoveFromIndex(Path)) and
modify callers: in removeFromIndex submit IndexCommand.RemoveFromIndex(path)
instead of resolving VF, and in onFileMoved always submit RemoveFromIndex(from)
(by path) then submit ScanSourceFile(toVf) / IndexSourceFile(toVf) only after
resolving the target VF; also adjust indexWorker command handling to remove
entries by path from sourceIndex/fileIndex accordingly.
- Around line 86-113: syncIndexInBackground/refreshSources can start new
coroutines while previous ones are still doing pre-queue producer work and the
completing job can overwrite the shared job reference; fix by making indexingJob
and scanningJob atomic (e.g., AtomicReference<Job?>), when cancelling call
cancel() and then await completion (join or invokeOnCompletion/await) before
launching a replacement, and inside startIndexing/startScanning capture the
launched Job into a local val and only clear the shared AtomicReference via
compareAndSet(currentJob, null) when that local job completes; additionally move
any producer-side work that mutates shared state (e.g.,
sourceIndex.setActiveSources(), module iteration before enqueueing) into the
serialized worker or protect it with a mutex so two scans cannot concurrently
modify sourceIndex while one is still cancelling.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`:
- Around line 385-398: The code incorrectly uses reference equality of
CompilationEnvironment (via compiler.compilationEnvironmentFor) to decide
same-kind moves, which can mis-handle .kt ↔ .kts renames and also can throw;
change the logic to determine the compilation kind for fromPath and toPath
(e.g., via a safe compilationKindFor(path) helper or by querying
CompilationEnvironment.getKind) and compare the CompilationKind values instead
of env instances; if kinds are equal and non-null call
ktSymbolIndex.onFileMoved(fromPath, toPath), otherwise ensure you
removeFromIndex(fromPath) on the old kind and submitForIndexing(toPath) on the
new kind; also guard calls to compiler.compilationEnvironmentFor (or the helper)
with try/catch so UnsupportedOperationException results in treating that side as
a distinct/unsupported kind (fallthrough to remove+submit).

---

Outside diff comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/Compiler.kt`:
- Around line 72-82: The crash happens because compilationEnvironmentFor(file:
Path) delegates .kts files to compilationEnvironmentFor(CompilationKind.Script)
which throws UnsupportedOperationException; change the behavior so
compilationEnvironmentFor(compilationKind: CompilationKind) returns null for
CompilationKind.Script (instead of throwing) and propagate null back to callers,
or alternatively update callers in KotlinLanguageServer (onFileCreated,
onFileDeleted, onFileRenamed) to guard against CompilationKind.Script by
checking for null/unsupported before using the returned CompilationEnvironment;
locate the two overloads compilationEnvironmentFor(file: Path) and
compilationEnvironmentFor(compilationKind: CompilationKind) and update the
Script branch to return null (or add null checks in the three
KotlinLanguageServer subscriber methods) so .kts files do not trigger an
exception.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 183-188: close() can hang because submitting IndexCommand.Stop
doesn't cancel a scanning coroutine blocked in
IndexWorker.submitCommand/scanChannel.send(); explicitly cancel the scanning job
before joining. Update close() to call scanningJob?.cancelAndJoin() (or call a
new ScanningWorker.stop() that sets a cancellation flag and then cancelAndJoin)
prior to submitting IndexCommand.Stop and before indexingJob?.join(), ensuring
ScanningWorker.scan() responds to cancellation and
IndexWorker.submitCommand/scanChannel.send() cannot leave the coroutine
permanently suspended.

---

Nitpick comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/ScanningWorker.kt`:
- Around line 29-34: The logger.warn inside ScanningWorker's filtering lambda is
noisy for routinely filtered files; change the branch so that when
toNioPathOrNull() returns a Path and kind.acceptsFile(path) is false you log at
debug (or not at all), but if toNioPathOrNull() returns null (indicating an
unexpected/unparseable VirtualFile) keep or escalate to logger.warn including
the file id; update the filter using the same toNioPathOrNull()/kind.acceptsFile
check to only warn on null and use logger.debug for normal rejects to avoid
spamming WARNs.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`:
- Around line 320-399: Update the four handlers (onBuildCompleted,
onFileCreated, onFileDeleted, onFileRenamed) to use `@Subscribe`(threadMode =
ThreadMode.ASYNC) and move any work that runs on the posting thread (calls to
event.file.toPath(), DocumentUtils.isKotlinFile, and any index operations)
inside the coroutine started by scope.launch so the posting thread does minimal
work; also wrap calls to compiler?.compilationEnvironmentFor(...) in a try/catch
(or null-check) to guard against the UnsupportedOperationException from
Compiler.compilationEnvironmentFor(CompilationKind.Script) to avoid crashing
when a .kts path is encountered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ad4d50d3-c66c-4668-a757-130024025058

📥 Commits

Reviewing files that changed from the base of the PR and between 363e581 and 2bffbaf.

📒 Files selected for processing (10)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationKind.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/Compiler.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexCommand.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/ScanningWorker.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/DeclarationsProvider.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/utils/VirtualFileExts.kt
💤 Files with no reviewable changes (1)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/DeclarationsProvider.kt

Comment thread lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt Outdated
itsaky added 2 commits April 24, 2026 01:19
Signed-off-by: Akash Yadav <itsaky01@gmail.com>
Signed-off-by: Akash Yadav <itsaky01@gmail.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt (1)

108-113: ⚠️ Potential issue | 🟠 Major

refreshSources() can no-op after a prior cancellation leaves a stale indexingJob reference.

indexingJob ?: startIndexing() only starts indexing when the reference is null, but startIndexing() only nulls it via the normal-completion path at Line 97. When a previous syncIndexInBackground() called indexingJob?.cancel(), the coroutine typically propagates CancellationException out of indexWorker.start(), so Line 97 is skipped and the reference stays non-null. A subsequent build-triggered refreshSources() will then see a non-null (but cancelled) job and never restart indexing — silently dropping all subsequent scan/index commands.

Safer: either clear the reference in a finally in startIndexing(), or check indexingJob?.isActive == true here.

🛠️ Proposed fix
-	private fun startIndexing() {
-		indexingJob = scope.launch {
-			indexWorker.start()
-			indexingJob = null
-		}
-	}
-
-	private fun startScanning() {
-		scanningJob = scope.launch {
-			scanningWorker.scan()
-			scanningJob = null
-		}
-	}
+	private fun startIndexing() {
+		indexingJob = scope.launch {
+			try {
+				indexWorker.start()
+			} finally {
+				indexingJob = null
+			}
+		}
+	}
+
+	private fun startScanning() {
+		scanningJob = scope.launch {
+			try {
+				scanningWorker.scan()
+			} finally {
+				scanningJob = null
+			}
+		}
+	}

 	fun refreshSources() {
-		indexingJob ?: startIndexing()
+		if (indexingJob?.isActive != true) startIndexing()

 		scanningJob?.cancel()
 		startScanning()
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 108 - 113, refreshSources() can see a non-null but cancelled
indexingJob and skip restarting; change its startup guard to check activity
(e.g. call startIndexing() when indexingJob?.isActive != true) instead of only
nullity, and also make startIndexing() robust by clearing the shared indexingJob
reference in a finally block (set indexingJob = null) so a cancelled coroutine
cannot leave a stale non-active reference; refer to refreshSources(),
startIndexing(), indexingJob, syncIndexInBackground(), and indexWorker.start()
when locating where to apply these changes.
🧹 Nitpick comments (2)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt (2)

122-124: Minor: indentation mixes spaces with the file's tab convention.

Lines 123 and 136 use leading spaces while the surrounding code uses tabs. Likely an accidental paste — a quick reformat keeps the file consistent.

Also applies to: 135-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 122 - 124, Lines in the suspend function submitForIndexing (around
the getVirtualFileOrWarn call) mix spaces with the file's tab indentation;
update those lines (including the ones at 123 and 135-137) to use the project's
tab-based indentation style so the file is consistent, then run the file
reformat/save to ensure no other mixed-indentation remains (look for
submitForIndexing and getVirtualFileOrWarn to locate the area).

23-23: Remove unused imports from FIR internals.

org.jetbrains.kotlin.fir.resolve.toArrayOfFactoryName (line 23) and org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileManager (line 21) are not referenced in this file and appear to be auto-import artifacts. Remove both to avoid unnecessary dependencies on FIR internals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
at line 23, Remove the unused FIR/internal imports in KtSymbolIndex.kt: delete
the import lines for org.jetbrains.kotlin.fir.resolve.toArrayOfFactoryName and
org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileManager in the top of
the file (they are not referenced anywhere in the KtSymbolIndex class or its
methods), then run a quick compile or search to confirm no remaining references
to those symbols (e.g., check usages around the KtSymbolIndex class and its
methods) and commit the cleaned import list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 135-144: onFileMoved currently returns early when
getVirtualFileOrWarn(to, ...) yields null and therefore skips enqueuing the
path-based removal; always submit IndexCommand.RemoveFromIndex(from)
unconditionally before the VFS lookup, and only gate submitting
IndexCommand.ScanSourceFile(toVf) and IndexCommand.IndexSourceFile(toVf) on toVf
being non-null (i.e., call getVirtualFileOrWarn, but do not return early —
submit RemoveFromIndex(from) first, then if toVf != null submit the
ScanSourceFile and IndexSourceFile commands to indexWorker).

---

Duplicate comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 108-113: refreshSources() can see a non-null but cancelled
indexingJob and skip restarting; change its startup guard to check activity
(e.g. call startIndexing() when indexingJob?.isActive != true) instead of only
nullity, and also make startIndexing() robust by clearing the shared indexingJob
reference in a finally block (set indexingJob = null) so a cancelled coroutine
cannot leave a stale non-active reference; refer to refreshSources(),
startIndexing(), indexingJob, syncIndexInBackground(), and indexWorker.start()
when locating where to apply these changes.

---

Nitpick comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 122-124: Lines in the suspend function submitForIndexing (around
the getVirtualFileOrWarn call) mix spaces with the file's tab indentation;
update those lines (including the ones at 123 and 135-137) to use the project's
tab-based indentation style so the file is consistent, then run the file
reformat/save to ensure no other mixed-indentation remains (look for
submitForIndexing and getVirtualFileOrWarn to locate the area).
- Line 23: Remove the unused FIR/internal imports in KtSymbolIndex.kt: delete
the import lines for org.jetbrains.kotlin.fir.resolve.toArrayOfFactoryName and
org.jetbrains.kotlin.com.intellij.openapi.vfs.VirtualFileManager in the top of
the file (they are not referenced anywhere in the KtSymbolIndex class or its
methods), then run a quick compile or search to confirm no remaining references
to those symbols (e.g., check usages around the KtSymbolIndex class and its
methods) and commit the cleaned import list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2d2d8cc-2718-424f-8f43-df1292d669be

📥 Commits

Reviewing files that changed from the base of the PR and between 2bffbaf and 622a577.

📒 Files selected for processing (3)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexCommand.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt

Signed-off-by: Akash Yadav <itsaky01@gmail.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt (1)

193-198: ⚠️ Potential issue | 🟠 Major

close() can hang waiting on scanningJob.

Stop is only handled by IndexWorker (via the index queue), so scanningWorker.scan() is never signalled to stop and scanningJob?.join() will block until scanning naturally completes. If close() is invoked while a long scan is in progress, shutdown stalls. Cancel the scanning job explicitly before joining (or join indexing first, since indexing depends on scanning's output, then cancel+join scanning).

♻️ Proposed fix
 	suspend fun close() {
 		indexWorker.submitCommand(IndexCommand.Stop)
 
-		scanningJob?.join()
 		indexingJob?.join()
+		scanningJob?.cancelAndJoin()
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 193 - 198, close() can hang because IndexCommand.Stop is handled
only by IndexWorker and does not signal scanningWorker.scan(), so
scanningJob?.join() may block; fix by ensuring scanningJob is cancelled before
waiting: first wait for indexingJob (indexingJob?.join()) since indexing
consumes scanning output, then call scanningJob?.cancel() and finally
scanningJob?.join() to avoid a stuck shutdown, updating the close()
implementation that submits IndexCommand.Stop to IndexWorker to perform this
ordering and cancellation using the scanningJob and indexingJob symbols.
♻️ Duplicate comments (2)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt (2)

146-154: ⚠️ Potential issue | 🟠 Major

onFileMoved still skips removing the old entry when the destination VFS lookup fails.

When getVirtualFileOrWarn(to) returns null (e.g., the destination isn't yet visible to the VFS at event-handling time), the early return at Line 147 bypasses RemoveFromIndex(from) on Line 150. The index then keeps stale entries for the old path even though the move definitely happened — exactly what the path-based RemoveFromIndex was introduced to prevent. Removal doesn't depend on the VFS, so it should be enqueued unconditionally and only the scan/index steps gated on toVf.

🛠️ Proposed fix
 	suspend fun onFileMoved(from: Path, to: Path) {
-		val toVf = getVirtualFileOrWarn(to) ?: return
+		indexWorker.submitCommand(IndexCommand.RemoveFromIndex(from))
 
+		val toVf = getVirtualFileOrWarn(to) ?: return
 		indexWorker.apply {
-			submitCommand(IndexCommand.RemoveFromIndex(from))
 			submitCommand(IndexCommand.ScanSourceFile(toVf))
 			submitCommand(IndexCommand.IndexSourceFile(toVf))
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 146 - 154, In onFileMoved, always enqueue the path-based removal so
stale entries are cleared: call
indexWorker.submitCommand(IndexCommand.RemoveFromIndex(from)) unconditionally
(before or regardless of getVirtualFileOrWarn(to) result), then only if
getVirtualFileOrWarn(to) returns a non-null toVf submit the ScanSourceFile(toVf)
and IndexSourceFile(toVf) commands; adjust the logic around
getVirtualFileOrWarn, IndexCommand.RemoveFromIndex, IndexCommand.ScanSourceFile,
IndexCommand.IndexSourceFile and indexWorker.submitCommand to enforce this
ordering.

86-125: ⚠️ Potential issue | 🟠 Major

Cancel-then-relaunch without awaiting completion still allows two workers to consume the shared queue concurrently.

Job.cancel() is asynchronous, so syncIndexInBackground() and refreshSources() can launch a fresh indexWorker.start() / scanningWorker.scan() while the previous one is still inside its coroutineScope { while (isActive) … } loop. Both coroutines then read from the same WorkerQueue and mutate the shared fileIndex / sourceIndex concurrently — including the locally constructed modifiedFileIndexer debouncer and scanCount/sourceIndexCount counters in IndexWorker. The job-self-check in the finally block fixes the ref-overwrite, but not the overlap.

Additionally, refreshSources() uses indexingJob ?: startIndexing(). A cancelled-but-not-yet-completed indexingJob is still non-null, so the truthy check skips restart and may leave no indexer running until the dangling coroutine finishes its finally block.

Suggest awaiting cancellation (cancelAndJoin()) before relaunching:

♻️ Sketch
-	fun syncIndexInBackground() {
-		indexingJob?.cancel()
-		startIndexing()
-
-		scanningJob?.cancel()
-		startScanning()
-	}
+	fun syncIndexInBackground() {
+		scope.launch {
+			indexingJob?.cancelAndJoin()
+			startIndexing()
+
+			scanningJob?.cancelAndJoin()
+			startScanning()
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 86 - 125, syncIndexInBackground() and refreshSources() currently
call indexingJob?.cancel() / scanningJob?.cancel() and immediately relaunch
workers, allowing the old coroutine to still run and cause concurrent access;
change both to await cancellation before starting new work by calling
cancelAndJoin() on existing indexingJob and scanningJob (e.g.,
indexingJob?.cancelAndJoin(); scanningJob?.cancelAndJoin()) and only then call
startIndexing() / startScanning(); also update refreshSources() so it restarts
indexing when indexingJob is null or not active/completed (check job.isActive or
use cancelAndJoin semantics) to avoid skipping a restart when a job is cancelled
but not yet finished, and handle CancellationException/Interrupted exceptions
from cancelAndJoin appropriately to preserve finally blocks in
startIndexing()/startScanning().
🧹 Nitpick comments (1)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt (1)

62-136: Consider guarding each command dispatch against unexpected exceptions.

A failure in any branch (e.g., a null backingFilePath triggering NPE on Line 100, a transient PSI error in IndexSourceFile, or an IO error in indexSourceFile(...)) propagates out of take() and terminates the entire worker loop. After that, no further IndexCommand (including RemoveFromIndex, Stop, modified-file events) will ever be processed for this KtSymbolIndex, leaving the index permanently stale until the language server is restarted.

Consider wrapping each iteration's when (...) in a try/catch (rethrowing CancellationException) and logging+continuing on other failures so a single bad command does not silently kill background indexing.

♻️ Sketch
 		while (isActive) {
-			when (val cmd = queue.take()) {
+			val cmd = queue.take()
+			try {
+				when (cmd) {
 					is IndexCommand.RemoveFromIndex -> {
 						val filePath = cmd.path.pathString
 						fileIndex.remove(filePath)
 						sourceIndex.removeBySource(filePath)
 					}
 					...
 					IndexCommand.Stop -> break
+				}
+			} catch (ce: kotlinx.coroutines.CancellationException) {
+				throw ce
+			} catch (t: Throwable) {
+				logger.error("Failed to process index command {}", cmd, t)
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt`
around lines 62 - 136, The worker loop in IndexWorker.kt currently processes
commands from queue.take() without catching unexpected exceptions, so a single
failure (e.g., NPE in IndexCommand.IndexModifiedFile via
cmd.ktFile.backingFilePath, IO/PSI errors in indexSourceFile, etc.) can
terminate the loop and leave the index stale; wrap the body that handles the
when (val cmd = queue.take()) { ... } in a try/catch that rethrows
CancellationException but catches Throwable for other failures, log the
exception along with the command type (use logger.error with cmd::class or the
IndexCommand variant), and continue to the next iteration so RemoveFromIndex,
Stop and future commands are still processed; ensure IndexCommand.Stop is still
honored (i.e., when Stop is received, break the loop after handling) and avoid
swallowing errors from cancellation by rethrowing CancellationException.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Line 23: The import org.jetbrains.kotlin.fir.resolve.toArrayOfFactoryName in
KtSymbolIndex.kt is accidental and unused; remove that import statement from the
file (delete the line importing toArrayOfFactoryName), ensure there are no
references to toArrayOfFactoryName elsewhere in KtSymbolIndex (or any accidental
FIR symbol usage), and run a build/typecheck to confirm nothing else relied on
it.

---

Outside diff comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 193-198: close() can hang because IndexCommand.Stop is handled
only by IndexWorker and does not signal scanningWorker.scan(), so
scanningJob?.join() may block; fix by ensuring scanningJob is cancelled before
waiting: first wait for indexingJob (indexingJob?.join()) since indexing
consumes scanning output, then call scanningJob?.cancel() and finally
scanningJob?.join() to avoid a stuck shutdown, updating the close()
implementation that submits IndexCommand.Stop to IndexWorker to perform this
ordering and cancellation using the scanningJob and indexingJob symbols.

---

Duplicate comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 146-154: In onFileMoved, always enqueue the path-based removal so
stale entries are cleared: call
indexWorker.submitCommand(IndexCommand.RemoveFromIndex(from)) unconditionally
(before or regardless of getVirtualFileOrWarn(to) result), then only if
getVirtualFileOrWarn(to) returns a non-null toVf submit the ScanSourceFile(toVf)
and IndexSourceFile(toVf) commands; adjust the logic around
getVirtualFileOrWarn, IndexCommand.RemoveFromIndex, IndexCommand.ScanSourceFile,
IndexCommand.IndexSourceFile and indexWorker.submitCommand to enforce this
ordering.
- Around line 86-125: syncIndexInBackground() and refreshSources() currently
call indexingJob?.cancel() / scanningJob?.cancel() and immediately relaunch
workers, allowing the old coroutine to still run and cause concurrent access;
change both to await cancellation before starting new work by calling
cancelAndJoin() on existing indexingJob and scanningJob (e.g.,
indexingJob?.cancelAndJoin(); scanningJob?.cancelAndJoin()) and only then call
startIndexing() / startScanning(); also update refreshSources() so it restarts
indexing when indexingJob is null or not active/completed (check job.isActive or
use cancelAndJoin semantics) to avoid skipping a restart when a job is cancelled
but not yet finished, and handle CancellationException/Interrupted exceptions
from cancelAndJoin appropriately to preserve finally blocks in
startIndexing()/startScanning().

---

Nitpick comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt`:
- Around line 62-136: The worker loop in IndexWorker.kt currently processes
commands from queue.take() without catching unexpected exceptions, so a single
failure (e.g., NPE in IndexCommand.IndexModifiedFile via
cmd.ktFile.backingFilePath, IO/PSI errors in indexSourceFile, etc.) can
terminate the loop and leave the index stale; wrap the body that handles the
when (val cmd = queue.take()) { ... } in a try/catch that rethrows
CancellationException but catches Throwable for other failures, log the
exception along with the command type (use logger.error with cmd::class or the
IndexCommand variant), and continue to the next iteration so RemoveFromIndex,
Stop and future commands are still processed; ensure IndexCommand.Stop is still
honored (i.e., when Stop is received, break the loop after handling) and avoid
swallowing errors from cancellation by rethrowing CancellationException.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41fc43b1-42db-4ffb-85cc-e0bc12c64c6e

📥 Commits

Reviewing files that changed from the base of the PR and between 622a577 and 671f6f5.

📒 Files selected for processing (5)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexCommand.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/ScanningWorker.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/ScanningWorker.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt

Signed-off-by: Akash Yadav <itsaky01@gmail.com>
Signed-off-by: Akash Yadav <itsaky01@gmail.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt (1)

84-123: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Job replacement still races with in-flight scan/index work.

Job.cancel() on Lines 85, 88, and 121 is asynchronous, so both syncIndexInBackground() and refreshSources() can launch a replacement job before the previous one has actually finished. That still leaves a window where two scans overlap, and Line 119 also treats a cancelled-but-not-complete indexingJob as “running” because it only checks for null.

#!/bin/bash
set -euo pipefail

echo "=== KtSymbolIndex lifecycle ==="
sed -n '84,123p' lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt

echo
echo "=== ScanningWorker pre-queue work ==="
fd ScanningWorker.kt lsp/kotlin --type f -x rg -n -C3 'fun\s+scan\s*\(|setActiveSources|submitCommand' {}

echo
echo "=== IndexWorker start loop ==="
fd IndexWorker.kt lsp/kotlin --type f -x rg -n -C3 'fun\s+start\s*\(|submitCommand|take|poll|Stop' {}

The expected result is that scan() still does producer-side work before enqueuing commands, which means cancel() alone is not enough to prevent overlap; the relaunch path needs join/serialization rather than a null check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`
around lines 84 - 123, syncIndexInBackground() and refreshSources() race because
cancel() is asynchronous and you can start new indexing/scanning before the
previous coroutine has completed; change the launch-restart logic to wait for
the old job to finish (serialize restarts) by calling indexingJob?.cancel()
followed by indexingJob?.join() (and likewise for scanningJob) before starting a
new job, and keep the existing finally block that nulls indexingJob/scanningJob
but rely on equality comparison to the stored job instance (indexingJob /
scanningJob) rather than coroutineContext identity to avoid races when replacing
jobs.
🧹 Nitpick comments (2)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt (1)

35-41: ⚡ Quick win

Redundant synchronization: @Synchronized plus synchronized(searchScopeLock) is double-locking.

The @Synchronized annotation already synchronizes on this, while the body synchronizes on searchScopeLock. Either remove the annotation or remove the inner synchronized block for consistency with the rest of the class.

♻️ Proposed fix
-	`@Synchronized`
 	fun invalidateSearchScope() {
 		synchronized(searchScopeLock) {
 			_baseSearchScope = null
 			_contentScope = null
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt`
around lines 35 - 41, The method invalidateSearchScope is double-locking via
`@Synchronized` and an inner synchronized(searchScopeLock) block; remove the
`@Synchronized` annotation and keep the synchronized(searchScopeLock) block so the
method consistently synchronizes on searchScopeLock (update the declaration of
invalidateSearchScope to no longer have `@Synchronized`).
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt (1)

326-330: 💤 Low value

Consider specifying threadMode for onBuildCompleted or offloading refreshSources() to a coroutine.

Unlike the document event handlers that use ThreadMode.ASYNC, onBuildCompleted uses the default ThreadMode.POSTING, meaning it runs on the poster's thread. If compiler?.refreshSources() performs I/O or significant computation, this could block the calling thread.

♻️ Suggested approach
-	`@Subscribe`
+	`@Subscribe`(threadMode = ThreadMode.ASYNC)
 	`@Suppress`("unused")
 	fun onBuildCompleted(event: BuildCompletedEvent) {
 		compiler?.refreshSources()
 	}

Or launch into a coroutine to match the pattern used by file event handlers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`
around lines 326 - 330, The onBuildCompleted subscriber currently runs on the
poster's thread (default ThreadMode.POSTING) and may block; change the
`@Subscribe` to specify ThreadMode.ASYNC or instead offload work by launching a
coroutine and calling compiler?.refreshSources() on a background dispatcher
(e.g., CoroutineScope(Dispatchers.IO).launch { compiler?.refreshSources() }) so
refreshSources() does not block the poster thread; update the onBuildCompleted
method accordingly and ensure any coroutine scope used is appropriate for the
component lifecycle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt`:
- Around line 404-410: refreshSources() currently invalidates
ResolutionScopeProvider and ktSymbolIndex but doesn't clear module-level search
scopes, causing stale AbstractKtModule.searchScope caches to persist after
build-triggered refreshes; update refreshSources() to iterate the project's
Kotlin modules (use the same module collection used elsewhere in
CompilationEnvironment or reference AbstractKtModule instances) and call
invalidateSearchScope() on each module (or invoke the existing module-level
invalidation helper) before or after ktSymbolIndex.refreshSources() so module
search scopes are cleared during full refreshes.
- Around line 453-461: The delete/move handlers don't update the in-memory
open-file maps so stale keys remain; update onFileDeleted and onFileMoved to
also reconcile open-file state by calling the appropriate ktSymbolIndex methods
that manage open-buffer registrations and notify ProjectStructureProvider (e.g.,
invoke the same logic used when files are closed/opened) — specifically, after
invalidateOn(...) and ktSymbolIndex.removeFromIndex(path) in onFileDeleted call
the ktSymbolIndex method that unregisters open buffers and inform
ProjectStructureProvider to remove the path from its in-memory registration;
likewise in onFileMoved, after invalidateOn(fromPath)/invalidateOn(toPath) and
ktSymbolIndex.onFileMoved(fromPath,toPath), update ktSymbolIndex's open-file key
(move the buffer from fromPath to toPath) and notify ProjectStructureProvider so
the open buffer is registered under toPath and the stale fromPath key is cleared
(mirror the onFileClosed/onFileOpened reconciliation logic).

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt`:
- Around line 59-79: The equals/hashCode currently include mutable/derived
cached fields causing races and side effects; change equals and hashCode to use
only stable identity fields (e.g., id and directRegularDependencies) and remove
references to _baseSearchScope, _contentScope, searchScopeLock,
baseContentScope, contentScope, and direct* friend/dependsOn collections; ensure
neither equals nor hashCode calls getters that can populate caches (like
getOrCreateBaseSearchScope()) and do not read cache fields without acquiring
searchScopeLock or calling invalidateSearchScope(); update
AbstractKtModule.equals and AbstractKtModule.hashCode to compute equality/hash
solely from immutable identifiers and stable dependency sets.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt`:
- Around line 127-152: KtLibraryModule's hashCode() is out of sync with
equals(): it includes applicationEnvironment, baseContentScope, libraryName,
binaryRoots, binaryVirtualFiles, and targetPlatform (and accesses
baseContentScope which triggers lazy computation) while equals() only compares
super.equals, id, contentRoots, isSdk, jvmTarget, and librarySources. Fix
hashCode() in class KtLibraryModule so it only uses the same fields equals()
checks (call super.hashCode(), then incorporate isSdk, id, contentRoots,
jvmTarget, and librarySources in the same nullable-safe way) and remove any
access to baseContentScope or other extra properties (applicationEnvironment,
libraryName, binaryRoots, binaryVirtualFiles, targetPlatform) to avoid side
effects and maintain symmetry with equals().

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt`:
- Around line 107-124: The equals/hashCode contract is broken: hashCode()
includes module, contentRoots, name, languageVersionSettings and targetPlatform
which equals() does not check (equals currently uses super.equals(other),
project and versions). Fix by making the two consistent—either remove those
extra fields from hashCode or add corresponding comparisons into equals; I
recommend updating equals(other: Any?) to, after the super.equals(other) check,
also compare module, contentRoots, name, languageVersionSettings and
targetPlatform (e.g., return super.equals(other) && project == other.project &&
versions == other.versions && module == other.module && contentRoots ==
other.contentRoots && name == other.name && languageVersionSettings ==
other.languageVersionSettings && targetPlatform == other.targetPlatform) so
every field used in hashCode() is also used in equals().

---

Duplicate comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt`:
- Around line 84-123: syncIndexInBackground() and refreshSources() race because
cancel() is asynchronous and you can start new indexing/scanning before the
previous coroutine has completed; change the launch-restart logic to wait for
the old job to finish (serialize restarts) by calling indexingJob?.cancel()
followed by indexingJob?.join() (and likewise for scanningJob) before starting a
new job, and keep the existing finally block that nulls indexingJob/scanningJob
but rely on equality comparison to the stored job instance (indexingJob /
scanningJob) rather than coroutineContext identity to avoid races when replacing
jobs.

---

Nitpick comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt`:
- Around line 35-41: The method invalidateSearchScope is double-locking via
`@Synchronized` and an inner synchronized(searchScopeLock) block; remove the
`@Synchronized` annotation and keep the synchronized(searchScopeLock) block so the
method consistently synchronizes on searchScopeLock (update the declaration of
invalidateSearchScope to no longer have `@Synchronized`).

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`:
- Around line 326-330: The onBuildCompleted subscriber currently runs on the
poster's thread (default ThreadMode.POSTING) and may block; change the
`@Subscribe` to specify ThreadMode.ASYNC or instead offload work by launching a
coroutine and calling compiler?.refreshSources() on a background dispatcher
(e.g., CoroutineScope(Dispatchers.IO).launch { compiler?.refreshSources() }) so
refreshSources() does not block the poster thread; update the onBuildCompleted
method accordingly and ensure any coroutine scope used is appropriate for the
component lifecycle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4542f6ac-82a8-40fe-97ff-ca88e7edbde1

📥 Commits

Reviewing files that changed from the base of the PR and between 671f6f5 and e8d3020.

📒 Files selected for processing (12)
  • lsp/jvm-symbol-index/src/main/kotlin/org/appdevforall/codeonthego/indexing/jvm/KtFileMetadataIndex.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtLibraryModule.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ResolutionScopeProvider.kt
  • lsp/kotlin/src/main/resources/META-INF/kt-lsp/kt-lsp.xml
  • subprojects/kotlin-analysis-api/build.gradle.kts

…deleted

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt (2)

410-414: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Invalidate analysis caches during full refresh.

A build-triggered refresh currently updates only ktSymbolIndex. The resolution-scope cache and each AbstractKtModule search-scope cache survive, so post-build analysis can keep using pre-refresh file sets.

Suggested fix
 fun refreshSources() {
+	ResolutionScopeProvider.getInstance(project).invalidateAll()
+	modules.asFlatSequence().forEach { module ->
+		(module as? AbstractKtModule)?.invalidateSearchScope()
+	}
 	ktSymbolIndex.refreshSources()
 	// TODO: Should also update/notify Java file services about possibly changed Java files
 	//       But that's a bit problematic right now, scheduled for later
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt`
around lines 410 - 414, refreshSources() currently only updates ktSymbolIndex,
leaving the resolution-scope cache and each AbstractKtModule search-scope cache
intact so post-build analysis can use stale data; modify refreshSources() to
also invalidate the global resolution-scope cache and call the appropriate
invalidate/clear on every AbstractKtModule search-scope (or invoke their
provided reset/refresh methods) so all analysis caches are cleared during a full
refresh; locate refreshSources(), ktSymbolIndex, and the AbstractKtModule
search-scope/cache implementations to add calls that clear or rebuild those
caches (ensure thread-safety consistent with existing refresh logic).

493-504: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove/move should also clear open-buffer state.

onFileRemoved() only mutates the index. If the file is open, pending diagnostics, the ktSymbolIndex open-file entry, and ProjectStructureProvider's in-memory registration stay keyed to the deleted path; onFileMoved() inherits the same stale state by delegating to remove/create.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt`
around lines 493 - 504, onFileRemoved currently only updates the symbol index
and leaves open-buffer state, diagnostics, and ProjectStructureProvider
registrations stale; update onFileRemoved(Path) to also clear any editor buffer
state and pending diagnostics for that path, remove any open-file entry from
ktSymbolIndex's open-file map, and unregister the path from
ProjectStructureProvider's in-memory registration (use the same identifiers used
when opening files). For onFileMoved(fromPath,toPath), instead of simply
delegating to onFileRemoved/from onFileCreated, preserve or transfer open-buffer
state and diagnostics if the file was open: remove registrations keyed by
fromPath, move or re-key the buffer/diagnostic entries and ktSymbolIndex
open-file entry to toPath, and update ProjectStructureProvider to re-register
under toPath so no stale references remain.
lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt (1)

381-389: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use kind equality for same-kind renames, not env instance equality.

After resolving fromKind/toKind, Line 386 still decides "same-kind move" via fromEnv == toEnv. That keeps the .kt.kts rename path fragile, and env resolution at Lines 383-384 can still throw because CompilationKind.Script has no environment yet.

Suggested fix
-			val fromEnv = fromKind?.let { compiler?.compilationEnvironmentFor(it) }
-			val toEnv = toKind?.let { compiler?.compilationEnvironmentFor(it) }
+			val fromEnv = runCatching {
+				fromKind?.let { compiler?.compilationEnvironmentFor(it) }
+			}.getOrNull()
+			val toEnv = runCatching {
+				toKind?.let { compiler?.compilationEnvironmentFor(it) }
+			}.getOrNull()
 
-			if (fromKind != null && fromEnv == toEnv && toEnv != null) {
+			if (fromKind != null && fromKind == toKind && toEnv != null) {
 				// file was renamed within the same compilation environment
 				toEnv.onFileMoved(fromPath, toPath)
 				return@launch
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`
around lines 381 - 389, The current rename check compares environment instances
(fromEnv == toEnv) which can be null or unavailable for some CompilationKind
(e.g., Script); change the logic to first compare kinds using fromKind and
toKind (e.g., if (fromKind != null && fromKind == toKind) { ... }) and only then
obtain the compilation environment via compilationEnvironmentFor to call
onFileMoved; ensure you guard the onFileMoved call by checking the resolved env
is non-null (use fromKind/toKind, compilationEnvironmentFor, fromEnv/toEnv, and
onFileMoved identifiers) so same-kind renames work even when one kind has no
pre-existing env.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt`:
- Around line 443-457: The early return when path.toVirtualFileOrNull() is null
prevents the module/global fallback from running; instead, change the flow so
that virtualFile absence only skips the PsiManager/KtFile lookup and
KaSourceModificationService call, but still allows calling
ProjectStructureProvider.getInstance(project).findModuleForSourceId(path.pathString)
as the fallback: keep structureProvider assignment, set ktFile only when
virtualFile != null (using PsiManager.getInstance(project).findFile(virtualFile)
as? KtFile), call
KaSourceModificationService.getInstance(project).handleElementModification(ktFile,
typeProvider(ktFile)) only when ktFile != null, and then compute module using
the existing conditional that uses structureProvider.getModule(ktFile, null)
when ktFile != null else
structureProvider.findModuleForSourceId(path.pathString) and cast to
AbstractKtModule.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt`:
- Line 52: The unguarded "!!" when computing filePath can throw an unhelpful NPE
if both virtualFile?.toNioPathOrNull() and backingFilePath are null; in
SourceFileIndexer (the assignment to filePath used when converting KtFile to
metadata) replace the "!!" with checkNotNull (or requireNotNull) around the
entire expression and supply a clear message that includes which KtFile (or its
name/psi element) is missing both path sources so the error is descriptive;
ensure you reference the same expression virtualFile?.toNioPathOrNull() ?:
backingFilePath and keep the resulting .pathString access after the null-check.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt`:
- Around line 46-47: The contentScope getter dereferences _contentScope outside
the searchScopeLock because getOrCreateBaseSearchScope() releases the lock
before accessing _contentScope; to fix, ensure the read of _contentScope happens
while holding searchScopeLock (e.g., acquire searchScopeLock around both
getOrCreateBaseSearchScope() call and the subsequent `_contentScope!!` read or
refactor getOrCreateBaseSearchScope() to return the already-locked
_contentScope), so that invalidateSearchScope() cannot null out _contentScope
between those operations; update the contentScope property to perform the
null-check and return inside the lock using the existing searchScopeLock,
referencing contentScope, getOrCreateBaseSearchScope(), _contentScope,
searchScopeLock, and invalidateSearchScope().

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`:
- Around line 327-337: The handlers onFileCreated (and the corresponding
onFileDeleted) call compiler.compilationEnvironmentFor(path) for all Kotlin
files, but that function throws for script files (CompilationKind.Script); guard
the call by detecting and skipping Kotlin script files (e.g., check
path.extension == "kts" or a DocumentUtils.isKotlinScript(path) helper) before
invoking compilationEnvironmentFor(path), and apply the same guard in the delete
handler so .kts events are skipped/logged instead of aborting the coroutine.

---

Duplicate comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt`:
- Around line 410-414: refreshSources() currently only updates ktSymbolIndex,
leaving the resolution-scope cache and each AbstractKtModule search-scope cache
intact so post-build analysis can use stale data; modify refreshSources() to
also invalidate the global resolution-scope cache and call the appropriate
invalidate/clear on every AbstractKtModule search-scope (or invoke their
provided reset/refresh methods) so all analysis caches are cleared during a full
refresh; locate refreshSources(), ktSymbolIndex, and the AbstractKtModule
search-scope/cache implementations to add calls that clear or rebuild those
caches (ensure thread-safety consistent with existing refresh logic).
- Around line 493-504: onFileRemoved currently only updates the symbol index and
leaves open-buffer state, diagnostics, and ProjectStructureProvider
registrations stale; update onFileRemoved(Path) to also clear any editor buffer
state and pending diagnostics for that path, remove any open-file entry from
ktSymbolIndex's open-file map, and unregister the path from
ProjectStructureProvider's in-memory registration (use the same identifiers used
when opening files). For onFileMoved(fromPath,toPath), instead of simply
delegating to onFileRemoved/from onFileCreated, preserve or transfer open-buffer
state and diagnostics if the file was open: remove registrations keyed by
fromPath, move or re-key the buffer/diagnostic entries and ktSymbolIndex
open-file entry to toPath, and update ProjectStructureProvider to re-register
under toPath so no stale references remain.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt`:
- Around line 381-389: The current rename check compares environment instances
(fromEnv == toEnv) which can be null or unavailable for some CompilationKind
(e.g., Script); change the logic to first compare kinds using fromKind and
toKind (e.g., if (fromKind != null && fromKind == toKind) { ... }) and only then
obtain the compilation environment via compilationEnvironmentFor to call
onFileMoved; ensure you guard the onFileMoved call by checking the resolved env
is non-null (use fromKind/toKind, compilationEnvironmentFor, fromEnv/toEnv, and
onFileMoved identifiers) so same-kind renames work even when one kind has no
pre-existing env.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cd677913-0042-4ea8-8927-646ffeaab27c

📥 Commits

Reviewing files that changed from the base of the PR and between e8d3020 and 01128c0.

📒 Files selected for processing (13)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinLanguageServer.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/Compiler.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/SourceFileIndexer.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/AbstractKtModule.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtFileExts.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/DeclarationsProvider.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ResolutionScopeProvider.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/diagnostic/KotlinDiagnosticProvider.kt
  • lsp/kotlin/src/main/resources/META-INF/kt-lsp/kt-lsp.xml
  • subprojects/kotlin-analysis-api/build.gradle.kts
✅ Files skipped from review due to trivial changes (3)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/modules/KtSourceModule.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/Compiler.kt
  • subprojects/kotlin-analysis-api/build.gradle.kts
🚧 Files skipped from review as they are similar to previous changes (2)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/services/ProjectStructureProvider.kt
  • lsp/kotlin/src/main/resources/META-INF/kt-lsp/kt-lsp.xml

Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
@itsaky-adfa itsaky-adfa requested a review from jatezzz May 5, 2026 10:39
@itsaky-adfa itsaky-adfa merged commit 5d4c601 into stage May 5, 2026
2 checks passed
@itsaky-adfa itsaky-adfa deleted the fix/ADFA-3753 branch May 5, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants