Repository navigation
Add plugin sidebar/toolbar tooltips and directPath URI option - #1268
Conversation
Plugin-contributed sidebar and toolbar items now route long-press and hover through the plugin's own tooltip category (plugin_<pluginId>), so they resolve to entries the plugin installs via DocumentationExtension. Adds PluginTooltipButton.directPath to bypass plugin/<pluginId>/ URI scoping (for example, to point at a shared i/plugins-adfa.html), and tooltipTag on MenuItem/NavigationItem so one tooltip entry can serve both surfaces. Fixes plugin manager icons: PluginMetadata now carries the iconDayPath and iconNightPath that PluginLoader extracts at install time, instead of always being null. Extracts pluginCategory() and pluginTooltipTag() helpers so the "plugin_" prefix and "<pluginId>.<itemId>" tag shape live in one place.
📝 WalkthroughRelease NotesNew Features
Implementation Improvements
Breaking Changes
|
| Cohort / File(s) | Summary |
|---|---|
Tooltip Infrastructure actions/src/main/java/.../ActionItem.kt, plugin-manager/.../PluginTooltipNaming.kt |
Added retrieveTooltipCategory() method to ActionItem defaulting to IDE category; introduced shared helper functions pluginCategory() and pluginTooltipTag() for consistent plugin tooltip naming. |
Action Item Implementations app/src/main/java/.../PluginActionItem.kt, app/src/main/java/.../PluginSidebarActionItem.kt |
Updated constructors to require non-null pluginId and added tooltip category/tag overrides using plugin-scoped metadata and fallback to computed names. |
UI Integration & Tooltip Routing app/src/main/java/.../EditorHandlerActivity.kt, app/src/main/java/.../EditorSidebarFragment.kt, app/src/main/java/.../EditorActivityActions.kt, app/src/main/java/.../EditorSidebarActions.kt |
Updated tooltip display calls to use TooltipManager.showTooltip with explicit category parameter retrieved from actions; modified tooltip setup to accept and forward category. |
Plugin API Extensions plugin-api/src/main/kotlin/.../UIExtension.kt, plugin-api/src/main/kotlin/.../DocumentationExtension.kt |
Added optional tooltipTag property to MenuItem and NavigationItem; added directPath boolean parameter to PluginTooltipButton for URI resolution control. |
Plugin Manager & Services plugin-manager/.../PluginManager.kt, plugin-manager/.../PluginDocumentationManager.kt, plugin-manager/.../IdeTooltipServiceImpl.kt |
Refactored icon path handling via LoadedPlugin.toPluginMetadata() helper; updated tooltip category computation to use shared pluginCategory() helper; added directPath parameter handling in URI resolution. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
- ADFA-3148: Refactor APK Analyzer plugin with Material table UI and ViewModel #1117 — Modifies PluginSidebarActionItem and editor/sidebar action integration to thread pluginId and tooltip metadata through the UI layer.
- ADFA-2867: Migrate plugin tooltips into documentation.db #1019 — Introduces plugin tooltip categorization with shared helpers and adjusts tooltip display to use plugin-scoped categories.
- ADFA-3747: Git UI tooltips #1224 — Modifies tooltip usage and definitions, updating tooltip call sites that overlap with this refactor's shift from showIdeCategoryTooltip to showTooltip.
Suggested reviewers
- itsaky-adfa
- dara-abijo-adfa
Poem
🐰 Tooltips dance in categories now,
Plugin-scoped and organized, wow!
From IDE to plugin realms they route,
Each action knows its tooltip route,
Organized whispers, no longer astray! 📚✨
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 21.74% 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 changes: adding plugin sidebar/toolbar tooltips and the directPath URI option for tooltips. |
| Description check | ✅ Passed | The description clearly explains the primary changes across multiple areas: tooltip routing for plugins, directPath support, tooltipTag properties, and icon metadata fixes. |
| 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 docstrings
- Create stacked PR
- Commit on current branch
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
ADFA-3777
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.
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt (1)
243-257:⚠️ Potential issue | 🟠 MajorDon't register plugin sidebar items with an empty fallback ID.
If
getPluginIdForInstance()returnsnull, the empty-string fallback will feed malformed values intoPluginSidebarActionItemandSidebarSlotManager.getDeclaredSlots(...), which can break tooltip routing and the sidebar slot check. Skip unresolved plugins instead.Suggested fix
- val pluginId = pluginManager.getPluginIdForInstance(plugin as com.itsaky.androidide.plugins.IPlugin) - val declaredSlots = SidebarSlotManager.getDeclaredSlots(pluginId ?: "") + val pluginId = pluginManager.getPluginIdForInstance( + plugin as com.itsaky.androidide.plugins.IPlugin + ) ?: run { + Log.w("plugin_debug", "Skipping sidebar items for plugin without a registered ID: ${plugin.javaClass.simpleName}") + return@forEach + } + val declaredSlots = SidebarSlotManager.getDeclaredSlots(pluginId) @@ - val action = PluginSidebarActionItem(context, navItem, order++, pluginId ?: "") + val action = PluginSidebarActionItem(context, navItem, order++, pluginId)🤖 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/EditorSidebarActions.kt` around lines 243 - 257, The code currently uses pluginManager.getPluginIdForInstance(plugin ...) and falls back to an empty string which leads to malformed values; change the loop to skip any plugin where pluginManager.getPluginIdForInstance(...) returns null: call pluginManager.getPluginIdForInstance(plugin as com.itsaky.androidide.plugins.IPlugin) into a val pluginId and if pluginId == null return@forEach (or continue), then pass pluginId (non-null) into SidebarSlotManager.getDeclaredSlots(...) and PluginSidebarActionItem(...) and perform the sideMenuItems size check only for resolved pluginId values.plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt (1)
52-68:⚠️ Potential issue | 🔴 CriticalBinary compatibility break for precompiled plugins confirmed.
Lines 66 and 100 add
tooltipTagparameter toMenuItemandNavigationItemdata classes. In Kotlin/JVM, this changes constructor signatures and breaks already-compiled plugins at runtime (NoSuchMethodError) unless compiled with@JvmOverloads. Since no@JvmOverloadsannotation is present, precompiled third-party plugins depending on this API will fail.Internal plugins (markdown-preview-plugin, keystore-generator-plugin, apk-viewer-plugin) built from source can recompile, but external/third-party plugins compiled against earlier versions will break. No API version enforcement exists in PluginLoader to prevent loading incompatible plugins.
Recommend either:
- Add
@JvmOverloadsannotation to affected data classes- Add explicit API-version checking in plugin loading
- Document breaking change prominently for plugin developers
🤖 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/extensions/UIExtension.kt` around lines 52 - 68, Adding the nullable tooltipTag parameter to the data classes breaks binary compatibility for precompiled plugins; update the MenuItem and NavigationItem constructors to preserve JVM overloads by annotating the classes' primary constructors with `@JvmOverloads` so callers compiled against the old constructors still resolve, and ensure the new nullable parameter has a default value (tooltipTag: String? = null) in the declarations (refer to MenuItem and NavigationItem) so JVM overloads are generated for all constructor arities; alternatively, if you opt not to use `@JvmOverloads`, add explicit API-version checking in the plugin loading path to reject incompatible plugins instead.
🧹 Nitpick comments (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (1)
1491-1495: Narrow the helper's visibility.This extension is only used inside
PluginManager.kt, so making itprivatewould reduce the public surface area and avoid having twotoPluginMetadata()helpers in the same file.♻️ Proposed tweak
-fun LoadedPlugin.toPluginMetadata(): PluginMetadata = +private fun LoadedPlugin.toPluginMetadata(): PluginMetadata = manifest.toPluginMetadata().copy( iconDayPath = iconDayPath, iconNightPath = iconNightPath, )🤖 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 1491 - 1495, The LoadedPlugin.toPluginMetadata() extension is only used within PluginManager.kt, so narrow its visibility by marking the extension function private (i.e., change its declaration to a private extension) to reduce public surface and avoid duplicate helpers; locate the LoadedPlugin.toPluginMetadata() extension in PluginManager.kt and make it private while preserving its current behavior and return value.
🤖 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/utils/EditorActivityActions.kt`:
- Around line 186-190: The code currently falls back to an empty pluginId when
pluginManager.getPluginIdForInstance(plugin as
com.itsaky.androidide.plugins.IPlugin) returns null, which registers actions
with malformed metadata; instead, detect a null pluginId and skip registering
that plugin's contributions entirely (do not construct PluginActionItem or
iterate its getMainMenuItems()), logging or silently ignoring as appropriate;
update the block around pluginManager.getPluginIdForInstance,
PluginActionItem(...) and plugin.getMainMenuItems() so registration only
proceeds when a non-null, non-empty pluginId is returned.
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/DocumentationExtension.kt`:
- Around line 110-118: The PluginTooltipButton data class gained a fourth
parameter directPath, breaking binary compatibility with the previous 3-arg
constructor; add a secondary constructor on PluginTooltipButton with the
original three parameters (label: String, uri: String, order: Int) that
delegates to the primary constructor and supplies false for directPath (e.g.
constructor(label: String, uri: String, order: Int) : this(label, uri, order,
false)) so existing binaries keep working.
---
Outside diff comments:
In `@app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt`:
- Around line 243-257: The code currently uses
pluginManager.getPluginIdForInstance(plugin ...) and falls back to an empty
string which leads to malformed values; change the loop to skip any plugin where
pluginManager.getPluginIdForInstance(...) returns null: call
pluginManager.getPluginIdForInstance(plugin as
com.itsaky.androidide.plugins.IPlugin) into a val pluginId and if pluginId ==
null return@forEach (or continue), then pass pluginId (non-null) into
SidebarSlotManager.getDeclaredSlots(...) and PluginSidebarActionItem(...) and
perform the sideMenuItems size check only for resolved pluginId values.
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.kt`:
- Around line 52-68: Adding the nullable tooltipTag parameter to the data
classes breaks binary compatibility for precompiled plugins; update the MenuItem
and NavigationItem constructors to preserve JVM overloads by annotating the
classes' primary constructors with `@JvmOverloads` so callers compiled against the
old constructors still resolve, and ensure the new nullable parameter has a
default value (tooltipTag: String? = null) in the declarations (refer to
MenuItem and NavigationItem) so JVM overloads are generated for all constructor
arities; alternatively, if you opt not to use `@JvmOverloads`, add explicit
API-version checking in the plugin loading path to reject incompatible plugins
instead.
---
Nitpick comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 1491-1495: The LoadedPlugin.toPluginMetadata() extension is only
used within PluginManager.kt, so narrow its visibility by marking the extension
function private (i.e., change its declaration to a private extension) to reduce
public surface and avoid duplicate helpers; locate the
LoadedPlugin.toPluginMetadata() extension in PluginManager.kt and make it
private while preserving its current behavior and return value.
🪄 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: 12083327-9d44-45b8-8fc2-8d787902002c
📒 Files selected for processing (13)
actions/src/main/java/com/itsaky/androidide/actions/ActionItem.ktapp/src/main/java/com/itsaky/androidide/actions/PluginActionItem.ktapp/src/main/java/com/itsaky/androidide/actions/PluginSidebarActionItem.ktapp/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.ktapp/src/main/java/com/itsaky/androidide/fragments/sidebar/EditorSidebarFragment.ktapp/src/main/java/com/itsaky/androidide/utils/EditorActivityActions.ktapp/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.ktplugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/DocumentationExtension.ktplugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/UIExtension.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/PluginTooltipNaming.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/PluginDocumentationManager.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/services/IdeTooltipServiceImpl.kt
jatezzz
left a comment
There was a problem hiding this comment.
Please take a look to coderabbit comments
Plugin-contributed sidebar and toolbar items now route long-press and hover through the plugin's own tooltip category (plugin_<pluginId>), so they resolve to entries the plugin installs via DocumentationExtension. Adds PluginTooltipButton.directPath to bypass plugin/<pluginId>/ URI scoping (for example, to point at a shared i/plugins-adfa.html), and tooltipTag on MenuItem/NavigationItem so one tooltip entry can serve both surfaces. Fixes plugin manager icons: PluginMetadata now carries the iconDayPath and iconNightPath that PluginLoader extracts at install time, instead of always being null. Extracts pluginCategory() and pluginTooltipTag() helpers so the "plugin_" prefix and "<pluginId>.<itemId>" tag shape live in one place.
…2173) * style: spotless reformat of DocumentationExtension.kt, no functional change Editing the file pulls it under the Spotless ratchet, which reindents it to tabs and adds trailing commas. Kept separate from the ADFA-6560 fix. * ADFA-6560: Restore PluginTooltipButton's pre-directPath constructors #1268 added directPath to PluginTooltipButton, which replaced the (String, String, int) constructor. Plugins built before it, such as older Keystore Generator installs, call that constructor from getTooltipEntries, so their documentation install fails with NoSuchMethodError on every launch (fatal on older releases). Add the old constructor back as a hidden secondary constructor. It restores both (String, String, int) and its default-order synthetic, while staying invisible to Kotlin source, so current callers resolve to the primary constructor as before. * style: spotless reformat of UIExtension.kt and FileOpenExtension.kt, no functional change Editing both files pulls them under the Spotless ratchet. Kept separate from the ADFA-6560 fix. * ADFA-6560: Restore the pre-tooltipTag constructors of four data classes NavigationItem, TabItem, FileTabMenuItem and MenuItem gained tooltipTag (MenuItem also icon) after April, which replaced the constructors that plugins built before then call. An old Keystore Generator build still failed on NavigationItem's constructor in getSideMenuItems after the PluginTooltipButton fix. Add the old constructors back as hidden secondary constructors. NavigationItem, TabItem and FileTabMenuItem take the old parameters with the old defaults, which restores both the full constructor and its default-argument synthetic. MenuItem's full constructor survived through @jvmoverloads, so only its default-argument constructor is restored, with the exact JVM signature and the April defaults applied from the mask. * ADFA-6560: Compile plugin-api interface bodies as Java default methods Pinning languageVersion to 2.1 (ADFA-4058, later 2.0) dropped Kotlin's newer jvm-default behaviour, so plugin-api compiled every interface body as an abstract method plus a DefaultImpls static. Plugins built against the earlier plugin-api call the interface default with invoke-super. The April Keystore Generator, for one, hit AbstractMethodError on UIExtension.getToolbarActions() in EditorActivity.onCreate. The plugin crash guard caught it, but the editor stayed blank until the plugin was disabled. jvmDefault = ENABLE keeps the language pin and puts the bodies back on the interfaces. It keeps DefaultImpls for plugins built since May, and restores the IdeArchiveService.extract$default and IdeCommandService.executeCommand$default helpers that pre-May callers link against. A plugin built against this plugin-api has no DefaultImpls stubs, so on an IDE built between ADFA-4058 and 26.42 it needs to override every interface method it uses. PLUGIN_API_CHANGELOG.md says so. InterfaceDefaultMethodsTest fails without the change: getToolbarActions is not a default method, and neither is any body behind a DefaultImpls static. fragment-ktx joins the test runtime classpath because EditorTabExtension takes a Fragment and reflection has to load it.
Plugin-contributed sidebar and toolbar items now route long-press and hover through the plugin's own tooltip category (plugin_), so they resolve to entries the plugin installs via DocumentationExtension. Adds PluginTooltipButton.directPath to bypass plugin// URI scoping (for example, to point at a shared i/plugins-adfa.html), and tooltipTag on MenuItem/NavigationItem so one tooltip entry can serve both surfaces.
Fixes plugin manager icons: PluginMetadata now carries the iconDayPath and iconNightPath that PluginLoader extracts at install time, instead of always being null. Extracts pluginCategory() and pluginTooltipTag() helpers so the "plugin_" prefix and "." tag shape live in one place.