Skip to content

ADFA-3580: (feat) Plugin Build Actions & Custom Scripts System#1150

Open
Daniel-ADFA wants to merge 7 commits intostagefrom
ADFA-3580
Open

ADFA-3580: (feat) Plugin Build Actions & Custom Scripts System#1150
Daniel-ADFA wants to merge 7 commits intostagefrom
ADFA-3580

Conversation

@Daniel-ADFA
Copy link
Copy Markdown
Contributor

Adds first-class API for plugins to declare build actions (shell commands and Gradle tasks), execute them with streaming output to the Build Output panel, and control toolbar action visibility.

Screen.Recording.2026-04-03.at.01.13.52.mov

  Adds first-class API for plugins to declare build actions (shell commands
  and Gradle tasks), execute them with streaming output to the Build Output
  panel, and control toolbar action visibility.

  Includes custom scripts support via .codeonthego/scripts.json — plugins
  can auto-detect project type (Node.js, Python, Rust, Go, Make, Ruby)
  and bootstrap user-editable run scripts on first project open.

  New plugin-api interfaces: BuildActionExtension, IdeCommandService,
  CommandExecution. New plugin-manager implementations:
  IdeCommandServiceImpl (ProcessBuilder-based with Termux env injection),
  PluginBuildActionManager (singleton orchestrator). New app integration:
  PluginBuildActionItem
  Adds first-class API for plugins to declare build actions (shell commands
  and Gradle tasks), execute them with streaming output to the Build Output
  panel, and control toolbar action visibility.

  Includes custom scripts support via .codeonthego/scripts.json — plugins
  can auto-detect project type (Node.js, Python, Rust, Go, Make, Ruby)
  and bootstrap user-editable run scripts on first project open.

  New plugin-api interfaces: BuildActionExtension, IdeCommandService,
  CommandExecution. New plugin-manager implementations:
  IdeCommandServiceImpl (ProcessBuilder-based with Termux env injection),
  PluginBuildActionManager (singleton orchestrator). New app integration:
  PluginBuildActionItem
@Daniel-ADFA Daniel-ADFA requested a review from a team April 3, 2026 00:15
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 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

Adds a plugin build-action system: new plugin API/types and command execution interfaces, an IDE-side command service implementation and manager, manifest build-action parsing, editor toolbar integration for plugin build actions (run/cancel UI and output streaming), plugin context/service wiring, and safer plugin drawable resolution.

Changes

Cohort / File(s) Summary
Editor action entrypoints
app/src/main/java/com/itsaky/androidide/actions/EditorActivityAction.kt
prepare() now returns immediately after markInvisible() when required Context is missing.
Editor toolbar & registration
app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt, app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt
Toolbar building filters out plugin-hidden IDs and action.visible; registration now includes plugin-provided build actions and preserves plugin.build.* items on clear; logging on plugin UI registration failures changed to Log.w(...).
Toolbar action item
app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt
New PluginBuildActionItem implements run/cancel UI, resolves icon/label, starts/cancels executions via manager, streams output to build output UI, and updates UI state.
Plugin API surface
plugin-api/build.gradle.kts, plugin-api/src/main/kotlin/.../BuildActionExtension.kt, plugin-api/src/main/kotlin/.../IdeCommandService.kt
Adds coroutines api dep; introduces BuildActionExtension, ToolbarActionIds, PluginBuildAction, CommandSpec, CommandOutput, CommandResult, BuildActionCategory, and IdeCommandService/CommandExecution interfaces.
Plugin manager — build actions
plugin-manager/src/main/kotlin/.../build/PluginBuildActionManager.kt
New singleton to register/merge extension and manifest build actions, compute hidden toolbar IDs, execute/cancel actions via IdeCommandService, track active executions, and forward plugin lifecycle callbacks.
Plugin manager — core & manifest
plugin-manager/src/main/kotlin/.../core/PluginManager.kt, plugin-manager/src/main/kotlin/.../loaders/PluginManifest.kt
Plugin activation now registers build actions and provisions IdeCommandService; unload cleans up executions and cancels commands; added getLoadedPlugin(); manifest parsing adds build_actions and normalizes ManifestBuildAction.
Command service implementation
plugin-manager/src/main/kotlin/.../services/IdeCommandServiceImpl.kt
New IdeCommandServiceImpl enforcing permissions/concurrency, constructing processes for ShellCommand/GradleTask, injecting environment, streaming stdout/stderr via coroutines, enforcing timeouts, and exposing cancel/all APIs.
Drawable resolution
plugin-manager/src/main/kotlin/.../ui/PluginDrawableResolver.kt
Safer drawable loading: explicit null checks, runCatching resolution, broader exception handling, and fallback to host drawable with logging.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Editor as Editor Activity
    participant Item as PluginBuildActionItem
    participant Manager as PluginBuildActionManager
    participant Service as IdeCommandService
    participant OS as OS Process

    User->>Editor: click plugin build action
    Editor->>Item: execAction(data)
    Item->>Manager: isActionRunning(pluginId,actionId)?
    Manager-->>Item: running? / not running
    alt not running
      Item->>Manager: executeAction(pluginId,actionId,service)
      Manager->>Service: executeCommand(CommandSpec, timeout)
      Service->>OS: spawn process (shell/gradle)
      Service-->>Manager: CommandExecution (stream + await)
      Manager-->>Item: CommandExecution
      Item->>Editor: mark build in-progress / open output
      loop stream output
        Service->>Item: emit StdOut/StdErr
        Item->>Editor: append lines to UI (main)
      end
      OS-->>Service: exit code
      Service->>Manager: completion result
      Manager->>Manager: notifyActionCompleted -> onActionCompleted
      Manager-->>Item: update UI (stopped)
    else running
      Item->>Manager: cancelAction(pluginId,actionId)
      Manager->>Service: cancelCommand(executionId)
      Manager-->>Item: cancelled
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • itsaky-adfa
  • jomen-adfa
  • dara-abijo-adfa

"I hop and I tap on a keyboard delight,
Plugins hum and processes race through the night,
Lines of output tumble, glowing and bright,
I nibble bugs and cheer when builds take flight,
A rabbit's wink—your actions run just right! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% 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 describes the main feature being added: a plugin build actions and custom scripts system, which is the primary focus of all changes.
Description check ✅ Passed The description is directly related to the changeset, explaining that the PR adds a first-class API for plugins to declare and execute build actions with streaming output and toolbar visibility control.

✏️ 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-3580

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

🧹 Nitpick comments (8)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginDrawableResolver.kt (1)

18-19: Intermediate variable is unnecessary.

The local drawable variable adds no value here—consider returning directly for conciseness.

♻️ Suggested simplification
             try {
-                val drawable = ContextCompat.getDrawable(pluginContext, resId)
-                return drawable
+                return ContextCompat.getDrawable(pluginContext, resId)
             } catch (_: Resources.NotFoundException) {
🤖 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/ui/PluginDrawableResolver.kt`
around lines 18 - 19, In PluginDrawableResolver (the method that currently does
"val drawable = ContextCompat.getDrawable(pluginContext, resId); return
drawable"), remove the unnecessary local variable and return the result of
ContextCompat.getDrawable(pluginContext, resId) directly to simplify the code
and improve conciseness; update the method body to a single direct return of
ContextCompat.getDrawable(pluginContext, resId).
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeCommandService.kt (1)

8-20: Consider adding KDoc for the public plugin API.

This is a new public API surface for plugin developers. Adding documentation would improve discoverability and usage:

📝 Proposed documentation additions
+/**
+ * Service for executing shell commands and Gradle tasks from plugins.
+ */
 interface IdeCommandService {
+    /**
+     * Starts command execution with the given specification.
+     * `@param` spec The command to execute (shell command or Gradle task)
+     * `@param` timeoutMs Maximum execution time in milliseconds (default: 10 minutes)
+     * `@return` A CommandExecution handle for monitoring and controlling the execution
+     */
     fun executeCommand(spec: CommandSpec, timeoutMs: Long = 600_000): CommandExecution
     fun isCommandRunning(executionId: String): Boolean
     fun cancelCommand(executionId: String): Boolean
     fun getRunningCommandCount(): Int
 }

+/**
+ * Handle for a running command execution.
+ */
 interface CommandExecution {
+    /** Unique identifier for this execution */
     val executionId: String
+    /** Flow emitting stdout, stderr, and exit code as they become available */
     val output: Flow<CommandOutput>
+    /** Suspends until the command completes and returns the result */
     suspend fun await(): CommandResult
+    /** Requests cancellation of the running command */
     fun cancel()
 }
🤖 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/IdeCommandService.kt`
around lines 8 - 20, Add KDoc comments describing the public API surface:
document the IdeCommandService interface and each method (executeCommand,
isCommandRunning, cancelCommand, getRunningCommandCount) and the
CommandExecution interface and its members (executionId, output, await, cancel)
to explain purpose, parameters, return types, default values (e.g., timeoutMs
default 600_000), possible exceptions/behaviour (cancellation semantics,
lifecycle of output Flow), and usage examples or thread-safety notes as
appropriate to help plugin authors discover and use these APIs.
app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt (1)

135-140: Clarify the relationship between buildService and plugin action state.

resetProgressIfIdle checks buildService?.isBuildInProgress but plugin build actions don't appear to update buildService state. This could lead to inconsistent progress indicators if a Gradle build runs concurrently with a plugin action. Consider either:

  • Having plugin actions update buildService.isBuildInProgress
  • Or tracking plugin action progress separately with clear documentation
🤖 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/actions/build/PluginBuildActionItem.kt`
around lines 135 - 140, resetProgressIfIdle currently only checks
buildService?.isBuildInProgress which is never updated by plugin actions; either
update the plugin action lifecycle to flip buildService.isBuildInProgress when a
plugin build starts/ends (set buildService?.isBuildInProgress = true at action
start and false at completion in the PluginBuildActionItem action handlers) or
add a separate flag (e.g.,
EditorHandlerActivity.editorViewModel.isPluginActionInProgress) and change
resetProgressIfIdle to consider both buildService?.isBuildInProgress and
isPluginActionInProgress so UI progress reflects both Gradle builds and plugin
actions consistently.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt (1)

61-75: Consider adding @SerializedName annotations for consistency.

The ManifestBuildAction data class has inconsistent serialization annotations. Some fields use @SerializedName (e.g., gradle_task, working_directory, timeout_ms) while others rely on Gson's default field name matching. For maintainability and clarity about the expected JSON schema, consider adding explicit annotations to all fields.

♻️ Proposed fix for consistent annotations
 data class ManifestBuildAction(
+    `@SerializedName`("id")
     val id: String,
+    `@SerializedName`("name")
     val name: String,
+    `@SerializedName`("description")
     val description: String = "",
+    `@SerializedName`("category")
     val category: String = "CUSTOM",
+    `@SerializedName`("command")
     val command: String? = null,
+    `@SerializedName`("arguments")
     val arguments: List<String> = emptyList(),
     `@SerializedName`("gradle_task")
     val gradleTask: String? = null,
     `@SerializedName`("working_directory")
     val workingDirectory: String? = null,
+    `@SerializedName`("environment")
     val environment: Map<String, String> = emptyMap(),
     `@SerializedName`("timeout_ms")
     val timeoutMs: Long = 600_000
 )
🤖 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/loaders/PluginManifest.kt`
around lines 61 - 75, ManifestBuildAction has mixed use of `@SerializedName` which
can confuse the expected JSON schema; update the data class by adding
`@SerializedName` annotations for all properties (id, name, description, category,
command, arguments, environment) to match the JSON field names you expect (e.g.,
"id", "name", "description", "category", "command", "arguments", "environment")
while retaining the existing annotations for gradleTask ("gradle_task"),
workingDirectory ("working_directory") and timeoutMs ("timeout_ms") so
serialization is consistent and explicit across the class.
app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt (1)

192-193: Silent exception swallowing may hide plugin registration issues.

Per project learnings, prefer narrow exception handling over broad catch-all. Silently swallowing exceptions makes debugging plugin registration failures difficult. Consider logging or catching only expected exception types.

♻️ Proposed fix to add minimal logging
-                } catch (_: Exception) {
+                } catch (e: Exception) {
+                    Log.w("plugin_debug", "Failed to register menu items for plugin: ${plugin.javaClass.simpleName}", 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/utils/EditorActivityActions.kt`
around lines 192 - 193, The empty catch block swallowing all Exceptions should
be replaced: catch only the expected exception types thrown during plugin
registration (e.g., IllegalStateException/IOException or the concrete exceptions
your plugin APIs throw) or, if you must catch Exception, log the error instead
of ignoring it; update the catch in EditorActivityActions.kt (the catch (_:
Exception) { } block used around plugin registration) to call the project
logging utility (or Android Log.e/Timber) with a clear message like "plugin
registration failed" and include the exception so failures are visible for
debugging.
plugin-api/build.gradle.kts (1)

32-33: Using api() is correct; consider upgrading to kotlinx-coroutines 1.10.0+.

The api configuration is appropriate since Flow<CommandOutput> is exposed in the public IdeCommandService interface. Coroutines 1.9.0 is compatible with your project's Kotlin 2.1.21 and poses no compatibility issues. However, for Kotlin 2.1 projects, kotlinx-coroutines 1.10.0+ is the recommended version.

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

In `@plugin-api/build.gradle.kts` around lines 32 - 33, Keep the api configuration
but update the kotlinx-coroutines dependency version: replace the
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") entry with a 1.10.0+
version (e.g., 1.10.0 or later) to follow Kotlin 2.1.21 recommendations and
ensure compatibility for the public Flow<CommandOutput> in IdeCommandService.
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt (2)

50-56: Validate that gradlew exists and is executable before use.

The code assumes gradlew exists in the project root but doesn't verify this. If the wrapper is missing or not executable, the process will fail with a potentially confusing error.

🛠️ Proposed fix
             is CommandSpec.GradleTask -> {
                 val gradleWrapper = projectRoot?.let { File(it, "gradlew") }
                     ?: throw IllegalStateException("No project root available for Gradle task execution")
+                if (!gradleWrapper.exists()) {
+                    throw IllegalStateException("Gradle wrapper not found at ${gradleWrapper.absolutePath}")
+                }
+                if (!gradleWrapper.canExecute()) {
+                    throw IllegalStateException("Gradle wrapper is not executable: ${gradleWrapper.absolutePath}")
+                }
                 ProcessBuilder(listOf(gradleWrapper.absolutePath, spec.taskPath) + spec.arguments).apply {
                     directory(projectRoot)
                 }
             }
🤖 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/IdeCommandServiceImpl.kt`
around lines 50 - 56, CommandSpec.GradleTask currently assumes gradlew exists
and is executable; modify the block that builds gradleWrapper so you explicitly
validate File(projectRoot, "gradlew") exists and isFile and isExecutable (or
attempt gradleWrapper.setExecutable(true) and re-check), and if those checks
fail throw a clear IllegalStateException mentioning missing or non-executable
gradlew; then proceed to construct the ProcessBuilder with
gradleWrapper.absolutePath, spec.taskPath and spec.arguments as before. Ensure
you reference gradleWrapper, projectRoot, CommandSpec.GradleTask and
ProcessBuilder when locating and updating the code.

85-88: Minor race window between cancellation and clear.

A command could be added between forEach completing and clear() being called, causing it to be silently removed without cancellation. Consider iterating until the map is empty or using removeIf.

♻️ Proposed fix
     fun cancelAllCommands() {
-        runningCommands.values.forEach { it.cancel() }
-        runningCommands.clear()
+        runningCommands.entries.removeAll { (_, execution) ->
+            execution.cancel()
+            true
+        }
     }
🤖 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/IdeCommandServiceImpl.kt`
around lines 85 - 88, cancelAllCommands has a race where a command can be added
between iterating and clear(); fix by making the cancel-and-clear atomic: wrap
the loop and clear in a single synchronized block (or use a dedicated mutex) so
that runningCommands.values.forEach { it.cancel() } and runningCommands.clear()
execute under the same lock; update the cancelAllCommands implementation to
synchronize on the runningCommands instance (or the class-level lock) to prevent
new entries being added while cancelling.
🤖 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/actions/build/PluginBuildActionItem.kt`:
- Around line 42-62: The override of prepare in PluginBuildActionItem is missing
a call to the base implementation; call super.prepare(data) at the start of
PluginBuildActionItem.prepare so BaseBuildAction.prepare can perform its context
validation and set visible/enabled based on buildService state before you apply
plugin-specific logic (or if you intentionally want to bypass that behavior, add
a comment explaining the reason and consider extracting a different base class
rather than skipping super.prepare); keep the rest of the method (using
PluginBuildActionManager.isActionRunning and setting label/icon/enabled)
unchanged.
- Around line 112-130: The coroutine launched on actionScope can outlive the
Activity and call withContext(Dispatchers.Main) after the Activity is destroyed;
change the launch to use the Activity lifecycle (replace actionScope.launch with
activity.lifecycleScope.launch or otherwise tie the Job to activity.lifecycle),
wrap the execution.output.collect and execution.await call in a try-catch that
handles CancellationException and other exceptions, and before calling
activity.appendBuildOutput(...) or resetProgressIfIdle(activity) check
activity.isFinishing/isDestroyed (or use lifecycleScope which cancels
automatically) so UI updates are only attempted when the Activity is alive; keep
manager.notifyActionCompleted(pluginId, actionId, result) behavior but ensure it
runs in a safe context if it touches UI.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/build/PluginBuildActionManager.kt`:
- Around line 81-96: The executeAction function can throw from
commandService.executeCommand after extension?.onActionStarted(actionId) was
called, leaving plugins without onActionCompleted and activeExecutions
inconsistent; wrap the call to commandService.executeCommand(action.command,
action.timeoutMs) in a try/catch, on success continue to store the execution in
activeExecutions as now, but on catch call
extension?.onActionCompleted(actionId) (and ensure nothing was added to
activeExecutions) and either rethrow the exception or return null so the caller
knows it failed; ensure the executionKey and activeExecutions[executionKey]
assignment remain only after a successful executeCommand.
- Around line 50-55: The current code in PluginBuildActionManager silently
swallows Throwable when iterating extension.getBuildActions() and at the other
two similar sites, which hides serious errors; change each broad catch (_:
Throwable) to catch a narrow, expected exception type (e.g.,
IllegalStateException or Exception) and log the error instead of ignoring it —
include pluginId and name (and action when available) in the log message to aid
debugging; apply the same change for the other two occurrences in the same class
(the blocks that create RegisteredBuildAction entries) so failures are visible
and do not suppress Errors like OutOfMemoryError.
- Around line 115-120: In cleanupPlugin(pluginId: String) first find all
matching entries in activeExecutions whose keys startWith("$pluginId:"), call
the appropriate cancellation/termination method on each execution handle (e.g.,
cancel()/terminate()/stop() depending on the execution handle API) to stop the
running processes, wait or handle failures if the API is asynchronous, and only
after canceling remove those entries from activeExecutions; then proceed to
remove pluginExtensions.remove(pluginId), manifestActions.remove(pluginId), and
pluginNames.remove(pluginId).

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginDrawableResolver.kt`:
- Around line 20-22: The catch-all `catch (_: Throwable)` in
PluginDrawableResolver.kt should be replaced with narrow, explicit catches for
known recoverable exceptions (e.g., `Resources.NotFoundException` is already
caught; add `IllegalArgumentException` or other specific exceptions you've
observed) and avoid swallowing `Error` types; for any unexpected exception types
that you do want to catch, add a minimal log (e.g., warning with the exception
message and context) so failures are visible instead of silent. Locate the
try/catch block in class PluginDrawableResolver and replace the broad Throwable
catch with explicit exception types relevant to drawable resolution (and add
logging in those unexpected-but-handled branches).

---

Nitpick comments:
In
`@app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt`:
- Around line 135-140: resetProgressIfIdle currently only checks
buildService?.isBuildInProgress which is never updated by plugin actions; either
update the plugin action lifecycle to flip buildService.isBuildInProgress when a
plugin build starts/ends (set buildService?.isBuildInProgress = true at action
start and false at completion in the PluginBuildActionItem action handlers) or
add a separate flag (e.g.,
EditorHandlerActivity.editorViewModel.isPluginActionInProgress) and change
resetProgressIfIdle to consider both buildService?.isBuildInProgress and
isPluginActionInProgress so UI progress reflects both Gradle builds and plugin
actions consistently.

In `@app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt`:
- Around line 192-193: The empty catch block swallowing all Exceptions should be
replaced: catch only the expected exception types thrown during plugin
registration (e.g., IllegalStateException/IOException or the concrete exceptions
your plugin APIs throw) or, if you must catch Exception, log the error instead
of ignoring it; update the catch in EditorActivityActions.kt (the catch (_:
Exception) { } block used around plugin registration) to call the project
logging utility (or Android Log.e/Timber) with a clear message like "plugin
registration failed" and include the exception so failures are visible for
debugging.

In `@plugin-api/build.gradle.kts`:
- Around line 32-33: Keep the api configuration but update the
kotlinx-coroutines dependency version: replace the
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0") entry with a 1.10.0+
version (e.g., 1.10.0 or later) to follow Kotlin 2.1.21 recommendations and
ensure compatibility for the public Flow<CommandOutput> in IdeCommandService.

In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeCommandService.kt`:
- Around line 8-20: Add KDoc comments describing the public API surface:
document the IdeCommandService interface and each method (executeCommand,
isCommandRunning, cancelCommand, getRunningCommandCount) and the
CommandExecution interface and its members (executionId, output, await, cancel)
to explain purpose, parameters, return types, default values (e.g., timeoutMs
default 600_000), possible exceptions/behaviour (cancellation semantics,
lifecycle of output Flow), and usage examples or thread-safety notes as
appropriate to help plugin authors discover and use these APIs.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt`:
- Around line 61-75: ManifestBuildAction has mixed use of `@SerializedName` which
can confuse the expected JSON schema; update the data class by adding
`@SerializedName` annotations for all properties (id, name, description, category,
command, arguments, environment) to match the JSON field names you expect (e.g.,
"id", "name", "description", "category", "command", "arguments", "environment")
while retaining the existing annotations for gradleTask ("gradle_task"),
workingDirectory ("working_directory") and timeoutMs ("timeout_ms") so
serialization is consistent and explicit across the class.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt`:
- Around line 50-56: CommandSpec.GradleTask currently assumes gradlew exists and
is executable; modify the block that builds gradleWrapper so you explicitly
validate File(projectRoot, "gradlew") exists and isFile and isExecutable (or
attempt gradleWrapper.setExecutable(true) and re-check), and if those checks
fail throw a clear IllegalStateException mentioning missing or non-executable
gradlew; then proceed to construct the ProcessBuilder with
gradleWrapper.absolutePath, spec.taskPath and spec.arguments as before. Ensure
you reference gradleWrapper, projectRoot, CommandSpec.GradleTask and
ProcessBuilder when locating and updating the code.
- Around line 85-88: cancelAllCommands has a race where a command can be added
between iterating and clear(); fix by making the cancel-and-clear atomic: wrap
the loop and clear in a single synchronized block (or use a dedicated mutex) so
that runningCommands.values.forEach { it.cancel() } and runningCommands.clear()
execute under the same lock; update the cancelAllCommands implementation to
synchronize on the runningCommands instance (or the class-level lock) to prevent
new entries being added while cancelling.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginDrawableResolver.kt`:
- Around line 18-19: In PluginDrawableResolver (the method that currently does
"val drawable = ContextCompat.getDrawable(pluginContext, resId); return
drawable"), remove the unnecessary local variable and return the result of
ContextCompat.getDrawable(pluginContext, resId) directly to simplify the code
and improve conciseness; update the method body to a single direct return of
ContextCompat.getDrawable(pluginContext, resId).
🪄 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: 0c86fe83-7c35-439f-b672-371e07a39546

📥 Commits

Reviewing files that changed from the base of the PR and between cb05a7d and e64ffea.

📒 Files selected for processing (12)
  • app/src/main/java/com/itsaky/androidide/actions/EditorActivityAction.kt
  • app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt
  • app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
  • app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt
  • plugin-api/build.gradle.kts
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/BuildActionExtension.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/services/IdeCommandService.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/build/PluginBuildActionManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginDrawableResolver.kt

@hasanelfalakiy
Copy link
Copy Markdown

hasanelfalakiy commented Apr 3, 2026

is it possible to run .java and .kt files like in Intellij Idea? the results of which appear on the console

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)
app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt (1)

114-139: ⚠️ Potential issue | 🟠 Major

Narrow this failure path and always unwind the action.

runCatching funnels every unexpected exception in the collect/await block into the same onFailure. On the non-cancellation path that only resets the UI, so an exception in output handling can leave the manager thinking the action is still running while the process continues in the background. Catch CancellationException explicitly, unwind/cancel in finally, and let unrelated bugs fail fast. Based on learnings: prefer narrow exception handling that catches only the specific exception type reported in crashes (such as IllegalArgumentException) instead of a broad catch-all (e.g., catch (e: Exception)).

🤖 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/actions/build/PluginBuildActionItem.kt`
around lines 114 - 139, Replace the broad runCatching with a try/catch/finally
around the block that collects output and awaits execution: explicitly catch
CancellationException (in the catch, call manager.cancelAction(pluginId,
actionId) and rethrow), let other exceptions propagate (do not swallow them),
and in finally ensure UI cleanup by calling withContext(Dispatchers.Main) {
resetProgressIfIdle(activity) } and also ensure the manager is unwound if the
action did not reach manager.notifyActionCompleted(pluginId, actionId) (e.g.,
track a completed flag set after notifyActionCompleted and if not completed call
manager.cancelAction(pluginId, actionId) in finally). Ensure you still append
output via activity.appendBuildOutput from the main dispatcher as before.
🤖 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/actions/build/PluginBuildActionItem.kt`:
- Around line 145-149: resetProgressIfIdle prematurely clears
editorViewModel.isBuildInProgress by only checking
buildService?.isBuildInProgress; update it to also consult
PluginBuildActionManager so progress isn't cleared while other plugin actions
are still active. Add a method on PluginBuildActionManager (e.g.,
hasActiveExecutions() or isAnyActionRunning()) that checks the activeExecutions
map for any remaining entries, then change resetProgressIfIdle to only clear
editorViewModel.isBuildInProgress when buildService?.isBuildInProgress != true
AND PluginBuildActionManager.hasActiveExecutions() == false; keep
activity.invalidateOptionsMenu() unchanged.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt`:
- Around line 46-47: PluginManifest's Kotlin defaults are unsafe because
PluginManifestParser uses a raw Gson() so missing JSON can deserialize fields
(buildActions, ManifestBuildAction.timeout_ms, arguments, environment) to
null/0; update parsing to normalize these fields after deserialization or switch
to a Kotlin-aware adapter: in PluginManifestParser replace raw Gson usage with a
Gson configured for Kotlin (or manually post-process the parsed PluginManifest
instance) to ensure buildActions is non-null (replace null with emptyList()),
each ManifestBuildAction.arguments and .environment are non-null maps/lists, and
.timeout_ms is set to the default 600_000 when parsed as 0 or null; adjust code
paths in PluginBuildActionManager to rely on the normalized values.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt`:
- Around line 34-38: The concurrency check in executeCommand (via
requireConcurrencyLimit()) races because it reads runningCommands.size before
insertion; change the approach to reserve a slot atomically by moving the
capacity check-and-insert into a single synchronized/locked section that both
validates and inserts the execution id into runningCommands (and ensure release
happens in finally), i.e., replace the separate requireConcurrencyLimit() call
with an atomic reserve operation that throws when runningCommands.size >=
MAX_CONCURRENT_COMMANDS and otherwise adds the executionId to runningCommands,
and mirror that by removing the id in the same synchronized/locked scope's
finally block so the slot is held for the entire lifecycle.
- Around line 43-45: The workingDirectory is being constructed with File(it)
which leaves relative paths resolved against the process CWD and lets siblings
like "<root>-tmp" bypass the startsWith check; change both sites (the
spec.workingDirectory resolution near ProcessBuilder and the similar block at
lines 114-123) to use java.nio.file.Path: resolve the spec.workingDirectory
against projectRoot when it's not absolute, normalize the resulting Path, and
pass that Path (or its File) to validateWorkingDirectory; in
validateWorkingDirectory (or the calling code) compare normalized Paths using
equality or startsWith semantics (dir == root || dir.startsWith(root)) rather
than string prefix checks to ensure proper containment.
- Around line 159-160: The outputChannel currently uses a bounded
Channel<CommandOutput>(capacity = 256) and readStream coroutines call
outputChannel.send(...) which can suspend and deadlock the process; change the
strategy to a non-blocking send by replacing the bounded channel or the send
calls: either create outputChannel with Channel.UNLIMITED (matching
BuildOutputFragment) or switch readStream to use outputChannel.trySend(...) and
drop or coalesce excess lines on failure; update references to outputChannel and
ensure readStream/error/stdout readers handle backpressure without suspending so
waitFor()/process pipes can't block.

---

Duplicate comments:
In
`@app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt`:
- Around line 114-139: Replace the broad runCatching with a try/catch/finally
around the block that collects output and awaits execution: explicitly catch
CancellationException (in the catch, call manager.cancelAction(pluginId,
actionId) and rethrow), let other exceptions propagate (do not swallow them),
and in finally ensure UI cleanup by calling withContext(Dispatchers.Main) {
resetProgressIfIdle(activity) } and also ensure the manager is unwound if the
action did not reach manager.notifyActionCompleted(pluginId, actionId) (e.g.,
track a completed flag set after notifyActionCompleted and if not completed call
manager.cancelAction(pluginId, actionId) in finally). Ensure you still append
output via activity.appendBuildOutput from the main dispatcher as before.
🪄 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: e9549c4f-a238-4844-a64d-ffa536725a71

📥 Commits

Reviewing files that changed from the base of the PR and between e64ffea and d90259c.

📒 Files selected for processing (6)
  • app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt
  • app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/build/PluginBuildActionManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginDrawableResolver.kt
✅ Files skipped from review due to trivial changes (1)
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/build/PluginBuildActionManager.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/ui/PluginDrawableResolver.kt
  • app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt

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.

🧹 Nitpick comments (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt (1)

98-109: Consider logging swallowed exceptions for debuggability.

Static analysis flags these exceptions as swallowed. When manifest parsing fails silently, debugging plugin issues becomes difficult. At minimum, a debug-level log would aid troubleshooting without being noisy.

🔧 Proposed fix
         } catch (e: Exception) {
+            android.util.Log.d("PluginManifestParser", "Failed to parse manifest from jar: ${jarFile.name}", e)
             null
         }
     }

     fun parseFromString(json: String): PluginManifest? {
         return try {
             gson.fromJson(json, PluginManifest::class.java)?.normalize()
         } catch (e: Exception) {
+            android.util.Log.d("PluginManifestParser", "Failed to parse manifest from string", e)
             null
         }
     }
🤖 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/loaders/PluginManifest.kt`
around lines 98 - 109, The catch blocks in parseFromString (and the similar JSON
parsing function) swallow exceptions which hinders debugging; update the
Exception handlers in PluginManifest.parseFromString and the other parse method
to log the caught exception at debug level (e.g., using the existing logger or a
class-level Logger) before returning null, including context like the input json
and the exception message/stacktrace so failures to
gson.fromJson(...).normalize() are visible during troubleshooting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt`:
- Around line 98-109: The catch blocks in parseFromString (and the similar JSON
parsing function) swallow exceptions which hinders debugging; update the
Exception handlers in PluginManifest.parseFromString and the other parse method
to log the caught exception at debug level (e.g., using the existing logger or a
class-level Logger) before returning null, including context like the input json
and the exception message/stacktrace so failures to
gson.fromJson(...).normalize() are visible during troubleshooting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8afe9948-2364-4f54-b0ca-51a42cf0c4bd

📥 Commits

Reviewing files that changed from the base of the PR and between d90259c and a128f2a.

📒 Files selected for processing (4)
  • app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/build/PluginBuildActionManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeCommandServiceImpl.kt

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)
app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt (1)

149-166: ⚠️ Potential issue | 🟠 Major

The preserve filter never gets a chance to keep plugin.build.* actions.

Line 158 already clears EDITOR_TOOLBAR, so the predicate at Lines 161-165 never sees QuickRunAction, RunTasksAction, ProjectSyncAction, or the new plugin build actions. Exclude EDITOR_TOOLBAR from the initial locations array and clear that location only through clearActionsExceptWhere(...).

Minimal fix
-            val locations = arrayOf(
-                EDITOR_TOOLBAR,
-                EDITOR_FILE_TABS,
-                EDITOR_FILE_TREE,
-                ActionItem.Location.EDITOR_FIND_ACTION_MENU
-            )
+            val locations = arrayOf(
+                EDITOR_FILE_TABS,
+                EDITOR_FILE_TREE,
+                ActionItem.Location.EDITOR_FIND_ACTION_MENU
+            )
             val registry = ActionsRegistry.getInstance()
             locations.forEach(registry::clearActions)

             // Clear toolbar actions except build actions
             registry.clearActionsExceptWhere(EDITOR_TOOLBAR) { action ->
🤖 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/utils/EditorActivityActions.kt`
around lines 149 - 166, The current clearActions() first clears EDITOR_TOOLBAR
(in the locations array) so the subsequent
registry.clearActionsExceptWhere(EDITOR_TOOLBAR) predicate never runs for
toolbar items; remove EDITOR_TOOLBAR from the initial locations array and only
clear the toolbar via registry.clearActionsExceptWhere(EDITOR_TOOLBAR) { action
-> action.id == QuickRunAction.ID || action.id == RunTasksAction.ID || action.id
== ProjectSyncAction.ID || action.id.startsWith("plugin.build.") } so the
toolbar-preserve filter can actually keep QuickRunAction, RunTasksAction,
ProjectSyncAction and plugin.build.* actions.
🤖 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/actions/build/PluginBuildActionItem.kt`:
- Around line 115-140: If any non-cancellation exception is thrown from
execution.output.collect, execution.await(), or manager.notifyActionCompleted
the action remains registered as running; catch exceptions narrowly around those
calls and ensure the manager is always informed the action finished and the
error is reported. Specifically, wrap the collect/await/notify sequence with
try/catch that treats CancellationException by rethrowing (and calling
manager.cancelAction(pluginId, actionId) as now), while for other exceptions
call manager.notifyActionCompleted(pluginId, actionId, /* create/propagate a
failure result containing the exception */) and then run
withContext(Dispatchers.Main) { resetProgressIfIdle(activity) } so the entry is
cleared and the error is surfaced; apply this to the blocks referencing
execution.output.collect, execution.await(), manager.notifyActionCompleted,
pluginId and actionId.

---

Outside diff comments:
In `@app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt`:
- Around line 149-166: The current clearActions() first clears EDITOR_TOOLBAR
(in the locations array) so the subsequent
registry.clearActionsExceptWhere(EDITOR_TOOLBAR) predicate never runs for
toolbar items; remove EDITOR_TOOLBAR from the initial locations array and only
clear the toolbar via registry.clearActionsExceptWhere(EDITOR_TOOLBAR) { action
-> action.id == QuickRunAction.ID || action.id == RunTasksAction.ID || action.id
== ProjectSyncAction.ID || action.id.startsWith("plugin.build.") } so the
toolbar-preserve filter can actually keep QuickRunAction, RunTasksAction,
ProjectSyncAction and plugin.build.* actions.
🪄 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: 469e41e6-807d-4ebd-bb18-3498a1410eaa

📥 Commits

Reviewing files that changed from the base of the PR and between 307892c and 4468c37.

📒 Files selected for processing (2)
  • app/src/main/java/com/itsaky/androidide/actions/build/PluginBuildActionItem.kt
  • app/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.kt

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.

3 participants