Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable
import android.view.Menu
import android.view.View
import androidx.annotation.CallSuper
import com.itsaky.androidide.idetooltips.TooltipCategory
import com.itsaky.androidide.utils.resolveAttr

/**
Expand Down Expand Up @@ -90,6 +91,14 @@ interface ActionItem {
*/
fun retrieveTooltipTag(isReadOnlyContext: Boolean): String = ""

/**
* Retrieves the tooltip category for this [ActionItem]. The default is
* [TooltipCategory.CATEGORY_IDE]; plugin-contributed actions override this
* to point at their own `plugin_<pluginId>` category so the lookup hits
* tooltip rows the plugin installed via [DocumentationExtension].
*/
fun retrieveTooltipCategory(): String = TooltipCategory.CATEGORY_IDE

/**
* The order of this action item. This is used only at some locations and not everywhere.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import android.content.Context
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import com.itsaky.androidide.plugins.extensions.MenuItem
import com.itsaky.androidide.plugins.manager.pluginCategory
import com.itsaky.androidide.plugins.manager.pluginTooltipTag


class PluginActionItem(
context: Context,
private val menuItem: MenuItem,
override val order: Int
override val order: Int,
val pluginId: String
) : EditorActivityAction() {

override val id: String = "plugin.${menuItem.id}"
Expand All @@ -29,6 +32,11 @@ class PluginActionItem(
visible = menuItem.isVisible
}

override fun retrieveTooltipTag(isReadOnlyContext: Boolean): String =
menuItem.tooltipTag ?: pluginTooltipTag(pluginId, menuItem.id)

override fun retrieveTooltipCategory(): String = pluginCategory(pluginId)

override suspend fun execAction(data: ActionData): Any {
return try {
// Execute the plugin's action callback on UI thread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.itsaky.androidide.plugins.extensions.NavigationItem
import com.itsaky.androidide.plugins.manager.pluginCategory
import com.itsaky.androidide.plugins.manager.pluginTooltipTag
import com.itsaky.androidide.plugins.manager.ui.PluginDrawableResolver
import com.itsaky.androidide.R
import kotlinx.coroutines.Dispatchers
Expand All @@ -17,7 +19,7 @@ class PluginSidebarActionItem(
private val context: Context,
private val navigationItem: NavigationItem,
baseOrder: Int,
pluginId: String? = null
val pluginId: String
) : SidebarActionItem {

override val id: String = "plugin_sidebar_${navigationItem.id}"
Expand All @@ -42,6 +44,11 @@ class PluginSidebarActionItem(
}
}

override fun retrieveTooltipTag(isReadOnlyContext: Boolean): String =
navigationItem.tooltipTag ?: pluginTooltipTag(pluginId, navigationItem.id)

override fun retrieveTooltipCategory(): String = pluginCategory(pluginId)

override suspend fun execAction(data: ActionData): Boolean {
return try {
// Plugin actions might need UI thread access for dialogs/UI operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,17 +459,19 @@ open class EditorHandlerActivity :
hint = getToolbarContentDescription(action, data),
onClick = { if (action.enabled) registry.executeAction(action, data) },
onLongClick = {
TooltipManager.showIdeCategoryTooltip(
TooltipManager.showTooltip(
context = this,
anchorView = content.projectActionsToolbar,
category = action.retrieveTooltipCategory(),
tag = action.retrieveTooltipTag(false),
)
},
onHover = { anchor ->
TooltipManager.cancelScheduledDismiss()
TooltipManager.showIdeCategoryTooltip(
TooltipManager.showTooltip(
context = this@EditorHandlerActivity,
anchorView = anchor,
category = action.retrieveTooltipCategory(),
tag = action.retrieveTooltipTag(false),
requestFocus = false,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.view.View
import com.itsaky.androidide.activities.editor.EditorHandlerActivity
import com.itsaky.androidide.databinding.FragmentEditorSidebarBinding
import com.itsaky.androidide.fragments.FragmentWithBinding
import com.itsaky.androidide.idetooltips.TooltipCategory
import com.itsaky.androidide.idetooltips.TooltipManager
import com.itsaky.androidide.utils.EditorSidebarActions

Expand All @@ -39,12 +40,17 @@ class EditorSidebarFragment : FragmentWithBinding<FragmentEditorSidebarBinding>(
EditorSidebarActions.setup(this)
}

fun setupTooltip(view: View, tooltipTag: String) {
fun setupTooltip(
view: View,
tooltipTag: String,
category: String = TooltipCategory.CATEGORY_IDE,
) {
(requireActivity() as? EditorHandlerActivity)?.let { activity ->
view.setOnLongClickListener { view ->
TooltipManager.showIdeCategoryTooltip(
TooltipManager.showTooltip(
context = view.context,
anchorView = view,
category = category,
tag = tooltipTag,
)
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@ class EditorActivityActions {
.forEach { plugin ->
try {
Log.d("plugin_debug", "Registering menu items for plugin: ${plugin.javaClass.simpleName}")
val pluginId = pluginManager.getPluginIdForInstance(plugin as com.itsaky.androidide.plugins.IPlugin) ?: ""
plugin.getMainMenuItems().forEach { menuItem ->
val action = PluginActionItem(context, menuItem, order++)
val action = PluginActionItem(context, menuItem, order++, pluginId)
Comment thread
Daniel-ADFA marked this conversation as resolved.
registry.registerAction(action)
}
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ internal object EditorSidebarActions {

if (view != null && action != null) {
val tag = action.retrieveTooltipTag(false)
sidebarFragment.setupTooltip(view, tag)
sidebarFragment.setupTooltip(view, tag, action.retrieveTooltipCategory())
}
}

Expand Down Expand Up @@ -254,7 +254,7 @@ internal object EditorSidebarActions {
}

sideMenuItems.forEach { navItem ->
val action = PluginSidebarActionItem(context, navItem, order++, pluginId)
val action = PluginSidebarActionItem(context, navItem, order++, pluginId ?: "")
registry.registerAction(action)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ data class PluginTooltipButton(
* - Paths with a leading "/" ("/some/shared/page") are treated as
* absolute within the local web server (slash stripped).
* - Anything containing "://" is passed through unchanged.
* - When [directPath] is true, namespacing is skipped and [uri] is
* used as-is under the local web server root.
* The stored path is then prefixed with "http://localhost:6174/" by the
* tooltip system when the button is rendered.
*/
Expand All @@ -105,5 +107,13 @@ data class PluginTooltipButton(
/**
* Order of this button (lower numbers appear first).
*/
val order: Int = 0
val order: Int = 0,

/**
* When true, [uri] is resolved as a direct path under the local web server
* (no `plugin/<pluginId>/` prefix). Use this to point at a shared page
* (for example a global plugins overview at "i/plugins-adfa.html") while
* keeping the path configurable by the plugin author. Default is false.
*/
val directPath: Boolean = false
Comment thread
Daniel-ADFA marked this conversation as resolved.
)
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ data class MenuItem(
val isVisible: Boolean = true,
val shortcut: String? = null,
val subItems: List<MenuItem> = emptyList(),
/**
* Optional tooltip tag to look up under the plugin's tooltip category
* (`plugin_<pluginId>`). When null, the IDE composes a tag using the
* convention `<pluginId>.<id>`. Supplying the same tooltipTag on a
* NavigationItem and a MenuItem lets a single PluginTooltipEntry serve
* both the sidebar and the toolbar surfaces.
*/
val tooltipTag: String? = null,
val action: () -> Unit
)

Expand Down Expand Up @@ -83,6 +91,13 @@ data class NavigationItem(
val isVisible: Boolean = true,
val group: String? = null,
val order: Int = 0,
/**
* Optional tooltip tag to look up under the plugin's tooltip category
* (`plugin_<pluginId>`). When null, the IDE composes a tag using the
* convention `<pluginId>.<id>` so plugins do not need to manually
* namespace tags to avoid collisions across plugins.
*/
val tooltipTag: String? = null,
val action: () -> Unit
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.itsaky.androidide.plugins.manager

const val PLUGIN_CATEGORY_PREFIX: String = "plugin_"

fun pluginCategory(pluginId: String): String = "$PLUGIN_CATEGORY_PREFIX$pluginId"

fun pluginTooltipTag(pluginId: String, itemId: String): String = "$pluginId.$itemId"
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ class PluginManager private constructor(
fun getAllPlugins(): List<PluginInfo> {
return loadedPlugins.values.map { loadedPlugin ->
PluginInfo(
metadata = loadedPlugin.manifest.toPluginMetadata(),
metadata = loadedPlugin.toPluginMetadata(),
isEnabled = loadedPlugin.isEnabled,
isLoaded = true
)
Expand Down Expand Up @@ -1486,4 +1486,10 @@ data class LoadedPlugin(
var isEnabled: Boolean = true,
val iconDayPath: String? = null,
val iconNightPath: String? = null
)
)

fun LoadedPlugin.toPluginMetadata(): PluginMetadata =
manifest.toPluginMetadata().copy(
iconDayPath = iconDayPath,
iconNightPath = iconNightPath,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase
import android.util.Log
import com.itsaky.androidide.plugins.extensions.DocumentationExtension
import com.itsaky.androidide.plugins.extensions.PluginTooltipEntry
import com.itsaky.androidide.plugins.manager.pluginCategory
import com.itsaky.androidide.resources.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand All @@ -23,7 +24,6 @@ class PluginDocumentationManager(private val context: Context) {

companion object {
private const val TAG = "PluginDocManager"
private const val PLUGIN_CATEGORY_PREFIX = "plugin_"
}

private val databaseName = "documentation.db"
Expand All @@ -43,7 +43,6 @@ class PluginDocumentationManager(private val context: Context) {
}
}

private fun pluginCategory(pluginId: String) = "$PLUGIN_CATEGORY_PREFIX$pluginId"

/**
* Initialize plugin documentation system.
Expand Down Expand Up @@ -99,7 +98,7 @@ class PluginDocumentationManager(private val context: Context) {
for (entry in entries) {
val tooltipId = insertTooltip(db, categoryId, entry)
entry.buttons.sortedBy { it.order }.forEachIndexed { index, button ->
val resolvedUri = resolvePluginButtonUri(pluginId, button.uri)
val resolvedUri = resolvePluginButtonUri(pluginId, button.uri, button.directPath)
insertTooltipButton(db, tooltipId, button.description, resolvedUri, index)
}
}
Expand Down Expand Up @@ -472,10 +471,14 @@ class PluginDocumentationManager(private val context: Context) {
return segments.joinToString("/")
}

private fun resolvePluginButtonUri(pluginId: String, rawUri: String): String {
private fun resolvePluginButtonUri(
pluginId: String,
rawUri: String,
directPath: Boolean
): String {
if (rawUri.isEmpty()) return rawUri
if (rawUri.contains("://")) return rawUri
val absolute = rawUri.startsWith("/")
val absolute = directPath || rawUri.startsWith("/")
val normalized = normalizeLocalDocumentationPath(rawUri.trimStart('/'))
return if (absolute) normalized else "plugin/$pluginId/$normalized"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.view.View
import com.itsaky.androidide.idetooltips.TooltipManager
import com.itsaky.androidide.plugins.manager.core.PluginManager
import com.itsaky.androidide.plugins.manager.pluginCategory
import com.itsaky.androidide.plugins.services.IdeTooltipService

/**
Expand All @@ -18,7 +19,7 @@ class IdeTooltipServiceImpl(
private val activityProvider: PluginManager.ActivityProvider?
) : IdeTooltipService {

private val pluginCategory = "plugin_$pluginId"
private val pluginCategory = pluginCategory(pluginId)

/**
* Returns a context suitable for inflating the tooltip layout.
Expand Down
Loading