Skip to content

ADFA-3653: Expand IdeEditorService plugin API with buffer reads, mutations, and file-change events#1194

Merged
Daniel-ADFA merged 6 commits into
stagefrom
ADFA-3653-fix
Apr 22, 2026
Merged

ADFA-3653: Expand IdeEditorService plugin API with buffer reads, mutations, and file-change events#1194
Daniel-ADFA merged 6 commits into
stagefrom
ADFA-3653-fix

Conversation

@Daniel-ADFA
Copy link
Copy Markdown
Contributor

Screen.Recording.2026-04-16.at.23.24.51.mov

@Daniel-ADFA Daniel-ADFA requested a review from a team April 16, 2026 22:29
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

Warning

Rate limit exceeded

@Daniel-ADFA has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 40 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 40 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fabd4afe-2ddd-476d-a335-dbda684f0fc9

📥 Commits

Reviewing files that changed from the base of the PR and between 1e38012 and 12383ca.

📒 Files selected for processing (3)
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
  • plugin-api/build.gradle.kts
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
📝 Walkthrough

Walkthrough

Adds a process-wide editor event dispatcher, an EditorProvider implementation that exposes rich editor state/mutations to plugins, plugin-api types/methods for editor access, and plugin-manager wiring to register/swap an active editor provider at runtime.

Changes

Cohort / File(s) Summary
Activity & Events
app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt, app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt
Activity registers/unregisters an EditorProviderImpl with PluginManager; _displayedFile changes now notify EditorEvents. Added EditorEvents singleton for last-active-file tracking, deduplicated file-change notifications, and thread-safe listener management.
Editor Provider Implementation
app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
New EditorProviderImpl implementing IdeEditorServiceImpl.EditorProvider: forwards file-change events, exposes file/selection/cursor/language queries, open/modified checks, open/save/open-at, many line/range edit mutations, main-thread execution helpers, and dispose() to unregister listeners.
Plugin API & Build
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt, plugin-api/build.gradle.kts
Plugin API extended with CursorPosition, SelectionRange, FileChangeListener, many new editor query/mutation APIs and listener registration; build config updated to compileSdk 36 and JVM target 17.
Plugin Manager & Service Impl
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt, plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt
PluginManager gains setEditorProvider(...) and a delegating provider that buffers/replays callbacks. IdeEditorServiceImpl split read/write permissions, added many provider methods (read/write), listener lifecycle, permission/validator checks, and dispose() behavior.

Sequence Diagram

sequenceDiagram
    participant Activity as EditorHandlerActivity
    participant Events as EditorEvents
    participant Provider as EditorProviderImpl
    participant PluginMgr as PluginManager
    participant Plugin as Plugin Code

    Activity->>Provider: instantiate EditorProviderImpl(this)
    Activity->>PluginMgr: setEditorProvider(provider)
    PluginMgr->>Provider: bind delegating provider / replay buffered callbacks
    Provider->>Events: register internal listener

    Plugin->>PluginMgr: addFileChangeListener(cb)
    PluginMgr->>Provider: addFileChangeCallback(cb)
    Events->>Provider: notifyFileChanged(file)
    Provider->>Plugin: invoke plugin callback(file)

    Activity->>Events: notifyFileChanged(null|file)
    PluginMgr->>Provider: setEditorProvider(null)
    Provider->>Events: remove listener
    Provider->>Provider: dispose()
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • jatezzz
  • itsaky-adfa

Poem

🐰
I hopped into tabs and stitched the seams,
Files now hum with plugin dreams,
Callbacks prance from view to thread,
Cursors twinkle, lines are spread,
I nibble bugs and clap my paws with beams! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: expansion of IdeEditorService plugin API with new buffer reads, mutations, and file-change events functionality.
Description check ✅ Passed The description references a linked asset showing PR details that corresponds to the API expansion objective, making it substantively related to the changeset despite being minimal.

✏️ 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 ADFA-3653-fix

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: 6

🧹 Nitpick comments (6)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (2)

88-101: Orphaned/duplicate KDoc blocks.

Two adjacent KDoc comments are stacked on pendingFileChangeCallbacks. The first one (“Stable provider handed to every plugin's [IdeEditorServiceImpl]…”) clearly documents delegatingEditorProvider and should be moved directly above line 101. As written, the first block is attached to the wrong symbol and Dokka will likely drop it.

✏️ Proposed fix
-    /**
-     * Stable provider handed to every plugin's [IdeEditorServiceImpl]. Each call delegates to
-     * the currently-set [editorProvider] (or returns a no-op default when none is wired). This
-     * lets the editor activity register/unregister a real provider over its lifecycle without
-     * having to rebuild already-loaded plugin services.
-     */
     /**
      * Callbacks added by plugins before any real provider was set. We hold them here and
      * replay onto a real provider when one is registered, so listener registration doesn't
      * silently drop on the floor during the early-boot window.
      */
     private val pendingFileChangeCallbacks = java.util.concurrent.CopyOnWriteArraySet<(File?) -> Unit>()

+    /**
+     * Stable provider handed to every plugin's [IdeEditorServiceImpl]. Each call delegates to
+     * the currently-set [editorProvider] (or returns a no-op default when none is wired). This
+     * lets the editor activity register/unregister a real provider over its lifecycle without
+     * having to rebuild already-loaded plugin services.
+     */
     private val delegatingEditorProvider = object : IdeEditorServiceImpl.EditorProvider {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`
around lines 88 - 101, The first KDoc block describing the "Stable provider
handed to every plugin's [IdeEditorServiceImpl]" is misplaced above
pendingFileChangeCallbacks; move that KDoc so it sits immediately above the
declaration of delegatingEditorProvider (the object implementing
IdeEditorServiceImpl.EditorProvider) and leave the second KDoc above
pendingFileChangeCallbacks which documents the callback set, ensuring each
comment is attached to the correct symbol (pendingFileChangeCallbacks and
delegatingEditorProvider) so Dokka picks them up correctly.

140-147: Double-registration via addFileChangeCallback.

When addFileChangeCallback is invoked while a provider is already attached, the callback is added to pendingFileChangeCallbacks and forwarded to current(). A subsequent setEditorProvider(newProvider) then replays all pending callbacks onto the new provider — including ones that were already added when the original provider was live. That's fine on the new provider (since it's a fresh target), but on provider swap setEditorProvider also does previous.removeFileChangeCallback(cb) which should cleanly detach them from the old one. Worth verifying that EditorProviderImpl.removeFileChangeCallback is idempotent when called with an unknown callback (it is — CopyOnWriteArrayList.remove is a no-op). No functional bug, but the "pending" name is now slightly misleading since it also contains already-attached callbacks; consider renaming to registeredFileChangeCallbacks for clarity.

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

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`
around lines 140 - 147, The field pendingFileChangeCallbacks is misleading
because addFileChangeCallback both registers and forwards callbacks to
current(), causing the list to contain callbacks already attached to an active
provider; rename pendingFileChangeCallbacks to registeredFileChangeCallbacks and
update all usages (addFileChangeCallback, removeFileChangeCallback,
setEditorProvider, and any replay logic) to use the new name so semantics are
clearer, and ensure EditorProviderImpl.removeFileChangeCallback usage remains
unchanged (it's idempotent) while updating variable references accordingly.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt (1)

282-282: Empty readPermissions/writePermissions would short-circuit the gate.

hasAll returns true when the required set is empty (Set.all {} vacuously true). If a caller ever constructs IdeEditorServiceImpl with an empty readPermissions/writePermissions (e.g. a test or misconfigured wiring), all permission checks silently pass. The defaults in the constructor prevent this today, but a defensive require(readPermissions.isNotEmpty()) (or an explicit comment documenting the intent) would prevent foot-guns later.

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

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt`
at line 282, The hasAll function currently returns true for an empty required
set (vacuous truth), so if readPermissions or writePermissions are ever
constructed empty permission checks will incorrectly pass; update the
IdeEditorServiceImpl constructor to enforce non-empty permission sets by adding
a defensive require(readPermissions.isNotEmpty()) and
require(writePermissions.isNotEmpty()) (or equivalent assertions) so hasAll
cannot be called with empty requirement sets, and optionally add a short comment
next to hasAll documenting that it expects non-empty required sets.
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt (2)

34-47: Clarify inclusivity convention for SelectionRange.

The kdoc says "Inclusive of start, exclusive of end (matches the underlying editor)". In practice, callers will produce ranges from cursor positions where end is at rightLine/rightColumn — please confirm whether (endLine, endColumn) is interpreted as a zero-width position just past the last selected character (exclusive), and document explicitly what endColumn == 0 means (i.e., a selection that ends at the start of endLine including the newline of endLine-1). Plugin authors building edit operations on replaceRange will need this to be unambiguous; right now EditorProviderImpl.replaceRange passes these through to sora's Content.replace(startLine, startCol, endLine, endCol, ...) which has its own (inclusive-start, exclusive-end) semantics — please add a sentence pinning the contract so plugin authors don't have to reverse-engineer sora.

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

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt`
around lines 34 - 47, Update the KDoc for SelectionRange to explicitly state
that (endLine,endColumn) is an exclusive end position (i.e., a zero-width
position immediately after the last selected character), that endColumn == 0
means the selection ends at the start of endLine and therefore includes the
newline character of endLine-1 when applicable, and that this contract
(inclusive start, exclusive end) matches the behavior used by
EditorProviderImpl.replaceRange which forwards values to sora's
Content.replace(startLine,startCol,endLine,endCol,...); edit the KDoc on the
SelectionRange data class (and optionally reference CursorPosition) to pin this
contract so plugin authors do not need to reverse-engineer sora.

57-119: Public API surface — document threading contract.

The mutation methods (openFile, saveCurrentFile, insertTextAtCursor, replaceSelection, appendToLine, …) are backed by EditorProviderImpl.onMain { … } which blocks the caller on a CountDownLatch when invoked off the main thread. Plugin authors calling these from a UI thread, a coroutine dispatcher, or under a lock need to know:

  1. These calls are synchronous and will block until the main thread has processed them.
  2. saveCurrentFile() is not synchronous in the current impl — it dispatches to lifecycleScope.launch and returns true immediately (see EditorProviderImpl.saveCurrentFile), so the Boolean does not reflect whether the save actually succeeded. That's inconsistent with the others and deserves a doc note here.

Consider adding a KDoc block to the interface summarising thread/async semantics so plugin authors don't guess.

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

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt`
around lines 57 - 119, Add a KDoc to the IdeEditorService interface that clearly
documents the threading/async contract: state that read methods are safe for
callers but mutation methods (list the mutation methods by name: openFile,
openFileAt, saveCurrentFile, insertTextAtCursor, replaceSelection, appendToLine,
prependToLine, replaceLine, insertLineBefore, deleteLine, replaceRange) are
dispatched to the main editor thread via EditorProviderImpl.onMain and therefore
block the caller by waiting on a CountDownLatch when invoked off the main
thread; explicitly call out the inconsistency that saveCurrentFile does not
block in the current implementation (see EditorProviderImpl.saveCurrentFile
which uses lifecycleScope.launch and returns true immediately) so its Boolean
return value does not indicate completion, and advise plugin authors to treat
saveCurrentFile as asynchronous until the implementation is changed.
app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt (1)

236-245: deleteLine semantics for final line.

When deleting the last line in the buffer (line == lineCount - 1 and line > 0), the code deletes from (line - 1, colCount(line-1)) to (line, colCount(line)). That removes the trailing newline that terminated line - 1 plus the text on line, which is conventional. But when the buffer has a single line (line == 0 && lineCount == 1), the else branch clears the line content in place (leaving an empty single line). That's a reasonable behaviour but worth documenting since deleteLine returning true on a 1-line buffer leaves a non-empty result (the empty line) — different from "file now contains nothing at all". A plugin author may expect the buffer to shrink to 0 lines.

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

In `@app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt` around
lines 236 - 245, The deleteLine implementation (deleteLine calling lineEdit)
behaves differently for the final line vs. a single-line buffer: when deleting
the only line it clears its contents but leaves a single empty line rather than
producing a zero-line buffer; update the code and docs to make this explicit and
consistent — either change the else branch in deleteLine (the lambda passed to
lineEdit) so that when line == 0 && lineCount == 1 you remove the line from the
buffer (shrink to 0 lines) instead of deleting its contents in-place, or add
KDoc above deleteLine explaining the current semantics (that deleting the sole
line yields an empty single line) and add a unit test; reference the deleteLine
method and the lineEdit call when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt`:
- Around line 66-71: notifyFileClosed currently clears lastActiveFile but leaves
lastNotifiedPath stale, which can cause future reopen events to be deduplicated;
update the notifyFileClosed function to also reset lastNotifiedPath (set it to
null or empty as your dedupe logic expects) whenever you clear lastActiveFile so
that listeners will fire correctly even if notifyFileChanged is not called
(refer to notifyFileClosed, lastActiveFile, lastNotifiedPath and the
notifyFileChanged/_displayedFile observer in EditorHandlerActivity to locate the
related logic).

In `@app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt`:
- Around line 174-185: openFile and openFileAt currently dispatch asynchronous
opens (activity.openFileAsync, activity.openFileAndSelect) and optimistically
return true; change them to call and await the actual suspend open operation on
the main dispatcher and return its real boolean result instead of always true.
Specifically, in EditorProviderImpl.openFile call the activity's suspend
openFile (or the suspend variant of openFileAsync) via
withContext(Dispatchers.Main) and return that boolean; similarly in openFileAt
call and await activity.openFileAndSelect (suspend) with the computed Range and
return its result; ensure you still null-check activity() and preserve
Position/Range construction, and handle delegateFileOpen behavior by returning
the underlying result rather than assuming success.
- Around line 298-316: The onMain helper currently calls latch.await() with no
timeout which can block forever; change it to use latch.await(timeout,
TimeUnit.SECONDS) (or another configured TimeUnit) and handle the boolean
result: if await returns false, set or throw a TimeoutException (or wrap it in
an appropriate runtime exception) so callers get a clear failure instead of
hanging. Update the logic around errorRef/resultRef to surface that timeout
(e.g., set errorRef to the TimeoutException when await times out, then follow
the existing errorRef.get()?.let { throw it } path) and pick a sensible timeout
constant; keep the existing finally/countDown behavior and retain the `@Suppress`
unchecked cast handling for resultRef.
- Around line 187-195: saveCurrentFile() currently schedules the save
asynchronously (uses activity.lifecycleScope.launch) and returns true
immediately, so the Boolean misleads callers; change
EditorProviderImpl.saveCurrentFile to perform the save synchronously on the main
thread so the return value reflects completion: replace the
lifecycleScope.launch path with an onMain { ... } call and invoke
activity.saveResult(index, SaveResult()) inside it while blocking until it
completes (e.g., wrap the save call in runBlocking or switch saveResult to a
synchronous API) so callers of saveCurrentFile() can rely on the returned
Boolean; reference: EditorProviderImpl.saveCurrentFile, activity(),
activity.saveResult(...), and current lifecycleScope.launch usage.

In `@plugin-api/build.gradle.kts`:
- Around line 9-13: The plugin-api change raises compileSdk/minSdk and requires
updating the dependent plugins to match: update the minSdk property in
markdown-preview-plugin, keystore-generator-plugin, and apk-viewer-plugin to at
least 28 (e.g., change minSdk = 26 to minSdk = 28) in each plugin's
build.gradle.kts so manifest merging won't fail; ensure any module-level
defaultConfig/minSdk declarations or productFlavor overrides in those plugins
are adjusted likewise and run a build to verify no further manifest/AGP
conflicts remain.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt`:
- Around line 311-328: The current isFileAccessAllowedDefault uses
canonicalPath.startsWith(it) which allows sibling matches; change it to compare
against canonicalized allowlist entries and ensure the match is an exact path
segment by canonicalizing each defaultAllowedPaths entry (use
File(path).canonicalPath) and then check either canonicalPath == allowedEntry or
canonicalPath.startsWith(allowedEntry + File.separator); update
defaultAllowedPaths to produce canonical paths lazily and make
isFileAccessAllowedDefault iterate those canonical entries when validating.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt`:
- Around line 236-245: The deleteLine implementation (deleteLine calling
lineEdit) behaves differently for the final line vs. a single-line buffer: when
deleting the only line it clears its contents but leaves a single empty line
rather than producing a zero-line buffer; update the code and docs to make this
explicit and consistent — either change the else branch in deleteLine (the
lambda passed to lineEdit) so that when line == 0 && lineCount == 1 you remove
the line from the buffer (shrink to 0 lines) instead of deleting its contents
in-place, or add KDoc above deleteLine explaining the current semantics (that
deleting the sole line yields an empty single line) and add a unit test;
reference the deleteLine method and the lineEdit call when making the change.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt`:
- Around line 34-47: Update the KDoc for SelectionRange to explicitly state that
(endLine,endColumn) is an exclusive end position (i.e., a zero-width position
immediately after the last selected character), that endColumn == 0 means the
selection ends at the start of endLine and therefore includes the newline
character of endLine-1 when applicable, and that this contract (inclusive start,
exclusive end) matches the behavior used by EditorProviderImpl.replaceRange
which forwards values to sora's
Content.replace(startLine,startCol,endLine,endCol,...); edit the KDoc on the
SelectionRange data class (and optionally reference CursorPosition) to pin this
contract so plugin authors do not need to reverse-engineer sora.
- Around line 57-119: Add a KDoc to the IdeEditorService interface that clearly
documents the threading/async contract: state that read methods are safe for
callers but mutation methods (list the mutation methods by name: openFile,
openFileAt, saveCurrentFile, insertTextAtCursor, replaceSelection, appendToLine,
prependToLine, replaceLine, insertLineBefore, deleteLine, replaceRange) are
dispatched to the main editor thread via EditorProviderImpl.onMain and therefore
block the caller by waiting on a CountDownLatch when invoked off the main
thread; explicitly call out the inconsistency that saveCurrentFile does not
block in the current implementation (see EditorProviderImpl.saveCurrentFile
which uses lifecycleScope.launch and returns true immediately) so its Boolean
return value does not indicate completion, and advise plugin authors to treat
saveCurrentFile as asynchronous until the implementation is changed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 88-101: The first KDoc block describing the "Stable provider
handed to every plugin's [IdeEditorServiceImpl]" is misplaced above
pendingFileChangeCallbacks; move that KDoc so it sits immediately above the
declaration of delegatingEditorProvider (the object implementing
IdeEditorServiceImpl.EditorProvider) and leave the second KDoc above
pendingFileChangeCallbacks which documents the callback set, ensuring each
comment is attached to the correct symbol (pendingFileChangeCallbacks and
delegatingEditorProvider) so Dokka picks them up correctly.
- Around line 140-147: The field pendingFileChangeCallbacks is misleading
because addFileChangeCallback both registers and forwards callbacks to
current(), causing the list to contain callbacks already attached to an active
provider; rename pendingFileChangeCallbacks to registeredFileChangeCallbacks and
update all usages (addFileChangeCallback, removeFileChangeCallback,
setEditorProvider, and any replay logic) to use the new name so semantics are
clearer, and ensure EditorProviderImpl.removeFileChangeCallback usage remains
unchanged (it's idempotent) while updating variable references accordingly.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt`:
- Line 282: The hasAll function currently returns true for an empty required set
(vacuous truth), so if readPermissions or writePermissions are ever constructed
empty permission checks will incorrectly pass; update the IdeEditorServiceImpl
constructor to enforce non-empty permission sets by adding a defensive
require(readPermissions.isNotEmpty()) and require(writePermissions.isNotEmpty())
(or equivalent assertions) so hasAll cannot be called with empty requirement
sets, and optionally add a short comment next to hasAll documenting that it
expects non-empty required sets.
🪄 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: c85b567c-3f1b-4a7c-8797-1b65cbb4f43d

📥 Commits

Reviewing files that changed from the base of the PR and between 8c1620d and 4a71964.

📒 Files selected for processing (7)
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
  • app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt
  • app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
  • plugin-api/build.gradle.kts
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt

Comment thread app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt
Comment thread app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
Comment thread app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
Comment thread app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt Outdated
Comment thread plugin-api/build.gradle.kts
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: 2

🧹 Nitpick comments (3)
app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt (1)

54-59: Silent listener failures hurt diagnosability.

catch (_: Exception) {} swallows every listener error with no log line. When a plugin's FileChangeListener misbehaves, there'll be no trace at all — not even in debug. Consider a Log.w(TAG, "listener failed", e) so plugin authors (and your own CI) can see it.

🔧 Suggested change
         fileChangeListeners.forEach { listener ->
             try {
                 listener(file)
-            } catch (_: Exception) {
+            } catch (e: Exception) {
+                Log.w(TAG, "file-change listener threw", e)
             }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt` around lines 54
- 59, The silent catch in the fileChangeListeners.forEach block (in
EditorEvents.kt) hides plugin exceptions; replace the empty catch with logging
the Throwable so failures are visible: catch the exception (e) from
listener(file) and call Log.w(TAG, "FileChangeListener failed for file: " +
file, e) (or use your processLogger) so the error and context are recorded, but
still do not rethrow to preserve existing behavior.
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt (1)

49-55: Document the threading contract of FileChangeListener.

Plugin authors will want to know which thread onFileChanged fires on before deciding whether it's safe to touch UI / do I/O in the callback. Given EditorEvents.notifyFileChanged is invoked from EditorHandlerActivity's _displayedFile observer, this is effectively main-thread — worth stating so consumers don't defensively post or block.

🔧 Suggested KDoc
 /**
  * Notified when the user switches between open files. The new active file is passed,
- * or null if all files are closed.
+ * or null if all files are closed.
+ *
+ * Callbacks are delivered on the IDE's main thread. Do not perform blocking I/O here;
+ * dispatch to a background scope if needed.
  */
 fun interface FileChangeListener {
     fun onFileChanged(file: File?)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt`
around lines 49 - 55, Add KDoc to the FileChangeListener interface clarifying
its threading contract: state that FileChangeListener.onFileChanged is invoked
on the main (UI) thread because EditorEvents.notifyFileChanged is called from
EditorHandlerActivity's _displayedFile observer; instruct plugin authors that
they must dispatch to background threads for heavy I/O or use main-thread
constructs for UI work as appropriate. Mention the specific symbols
FileChangeListener, onFileChanged, EditorEvents.notifyFileChanged, and
EditorHandlerActivity._displayedFile in the doc so readers can trace why the
callback is main-thread.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt (1)

76-79: Consider exposing dispose() on the IdeEditorService interface to clarify lifecycle requirements.

dispose() is currently only accessible on the concrete IdeEditorServiceImpl type, not through the interface contract. While PluginManager does correctly call dispose() during plugin unload via an explicit type check, this pattern is fragile—external code cannot reliably dispose instances without knowing the concrete type. Adding dispose() to the interface (or implementing AutoCloseable) would make the cleanup contract explicit and prevent accidental listener leaks if instances are ever passed outside the plugin manager context.

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

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt`
around lines 76 - 79, Add the cleanup API to the interface so callers can
dispose without knowing the concrete type: add a dispose() method to the
IdeEditorService interface (or have the interface extend AutoCloseable and
implement close() in IdeEditorServiceImpl) and move the current cleanup logic
from IdeEditorServiceImpl.dispose() (removing
editorProvider.removeFileChangeCallback(internalFileChangeCallback) and
fileChangeListeners.clear()) to the interface implementation; update
PluginManager to call the interface method instead of type-checking the concrete
class. Ensure method signatures and documentation reflect the lifecycle
requirement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt`:
- Around line 233-236: insertLineBefore currently calls insert(line, 0, payload)
but allows line == content.lineCount via lineEdit(..., existing = false), which
causes Content.insert to throw for end-of-buffer indices; update
insertLineBefore to special-case the end-of-buffer: if line ==
content.lineCount, call insert at the end of the last line (use the last line's
length as the column) or use an append/insert-at-end API instead, otherwise keep
the existing insert(line, 0, payload) path; locate this logic in
insertLineBefore and adjust the branch that invokes insert via lineEdit to avoid
passing lineCount into Content.insert.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt`:
- Around line 325-333: The /tmp entry in defaultAllowedPaths is incorrect:
replace the hardcoded singular "/tmp/CodeOnTheGoProject" with a path that uses
the Environment.PROJECTS_FOLDER value (the local variable projects) so it
matches the other entries; update the list inside the defaultAllowedPaths lazy
block to build the /tmp entry using "/tmp/$projects" (then let the existing map
{ runCatching { File(it).canonicalPath }... } handle canonicalization) so
canonicalized allowed paths align with actual project directories.

---

Nitpick comments:
In `@app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt`:
- Around line 54-59: The silent catch in the fileChangeListeners.forEach block
(in EditorEvents.kt) hides plugin exceptions; replace the empty catch with
logging the Throwable so failures are visible: catch the exception (e) from
listener(file) and call Log.w(TAG, "FileChangeListener failed for file: " +
file, e) (or use your processLogger) so the error and context are recorded, but
still do not rethrow to preserve existing behavior.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt`:
- Around line 49-55: Add KDoc to the FileChangeListener interface clarifying its
threading contract: state that FileChangeListener.onFileChanged is invoked on
the main (UI) thread because EditorEvents.notifyFileChanged is called from
EditorHandlerActivity's _displayedFile observer; instruct plugin authors that
they must dispatch to background threads for heavy I/O or use main-thread
constructs for UI work as appropriate. Mention the specific symbols
FileChangeListener, onFileChanged, EditorEvents.notifyFileChanged, and
EditorHandlerActivity._displayedFile in the doc so readers can trace why the
callback is main-thread.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt`:
- Around line 76-79: Add the cleanup API to the interface so callers can dispose
without knowing the concrete type: add a dispose() method to the
IdeEditorService interface (or have the interface extend AutoCloseable and
implement close() in IdeEditorServiceImpl) and move the current cleanup logic
from IdeEditorServiceImpl.dispose() (removing
editorProvider.removeFileChangeCallback(internalFileChangeCallback) and
fileChangeListeners.clear()) to the interface implementation; update
PluginManager to call the interface method instead of type-checking the concrete
class. Ensure method signatures and documentation reflect the lifecycle
requirement.
🪄 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: 3a69dc82-ed4d-4790-9882-5df889d242de

📥 Commits

Reviewing files that changed from the base of the PR and between 4a71964 and a275eeb.

📒 Files selected for processing (4)
  • app/src/main/java/com/itsaky/androidide/app/EditorEvents.kt
  • app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeServices.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeEditorServiceImpl.kt

Comment thread app/src/main/java/com/itsaky/androidide/app/EditorProviderImpl.kt
@Daniel-ADFA Daniel-ADFA merged commit 5c77f69 into stage Apr 22, 2026
2 checks passed
@Daniel-ADFA Daniel-ADFA deleted the ADFA-3653-fix branch April 22, 2026 19:28
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.

2 participants