From f79a1cec3ad1950e428dc797ada81e046676714b Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 27 Jul 2026 13:14:53 +0000 Subject: [PATCH 1/5] ADFA-4867: Extract computeImportCandidates so lsp:kotlin tests compile AddImportActionTest, added in ADFA-4747, calls computeImportCandidates, which the same commit removed. :lsp:kotlin test sources have not compiled since, so no unit test in the module could run. Extract the candidate resolution back out of execAction as an internal seam, mirroring OrganizeImportsAction.computeOrganizeEdit. Candidates are now keyed by fully-qualified name rather than JvmSymbol. postExec only ever used symbol.fqName, and keying by it collapses two index entries for the same class into a single chooser entry. --- .../lsp/kotlin/actions/AddImportAction.kt | 46 +++++++++++++------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt index 362c5effa4..bf9641a32b 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt @@ -6,6 +6,7 @@ import com.itsaky.androidide.actions.markInvisible import com.itsaky.androidide.actions.newDialogBuilder import com.itsaky.androidide.actions.requireFile import com.itsaky.androidide.idetooltips.TooltipTag +import com.itsaky.androidide.lsp.kotlin.compiler.AbstractCompilationEnvironment import com.itsaky.androidide.lsp.kotlin.compiler.index.findSymbolBySimpleName import com.itsaky.androidide.lsp.kotlin.diagnostic.DiagnosticAction import com.itsaky.androidide.lsp.kotlin.utils.insertImport @@ -19,7 +20,7 @@ import com.itsaky.androidide.resources.R import com.itsaky.androidide.utils.flashError import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.appdevforall.codeonthego.indexing.jvm.JvmSymbol +import java.nio.file.Path class AddImportAction : BaseKotlinCodeAction() { override var titleTextRes: Int = R.string.action_import_classes @@ -47,26 +48,41 @@ class AddImportAction : BaseKotlinCodeAction() { } } - override suspend fun execAction(data: ActionData): Map> { + override suspend fun execAction(data: ActionData): Map> { val (_, extra) = data.findDiagnosticExtra() ?: return emptyMap() val (env, action) = extra - val file = data.requireFile() - val nioPath = file.toPath() + val nioPath = data.requireFile().toPath() + return withContext(Dispatchers.IO) { + computeImportCandidates(env, nioPath, action.referenceName) + } + } + + /** + * Resolves [referenceName] to the importable classifiers known to [env], each mapped to the edits + * that import it into the file at [nioPath]. Keyed by fully-qualified name, which is what + * [postExec] shows in the chooser -- so two index entries for the same class collapse into one + * entry instead of duplicating it. + * + * Blocking: does the `getCurrentKtFile` `.get()` and a SQLite-backed index query, so callers must + * stay off the main thread ([execAction] wraps it in [Dispatchers.IO]). + */ + internal fun computeImportCandidates( + env: AbstractCompilationEnvironment, + nioPath: Path, + referenceName: String, + ): Map> { val ktFile = - withContext(Dispatchers.IO) { - env.ktSymbolIndex - .getCurrentKtFile(nioPath) - .get() - } - ?: return emptyMap() + env.ktSymbolIndex + .getCurrentKtFile(nioPath) + .get() ?: return emptyMap() return env.ktSymbolIndex - .findSymbolBySimpleName(action.referenceName, limit = 0) + .findSymbolBySimpleName(referenceName, limit = 0) .filter { it.kind.isClassifier } - .associateWith { symbol -> insertImport(ktFile, symbol.fqName) } + .associate { it.fqName to insertImport(ktFile, it.fqName) } } override fun postExec( @@ -80,7 +96,7 @@ class AddImportAction : BaseKotlinCodeAction() { } @Suppress("UNCHECKED_CAST") - result as Map> + result as Map> if (result.isEmpty()) { logger.warn("No classifiers to import.") @@ -99,9 +115,9 @@ class AddImportAction : BaseKotlinCodeAction() { val nioPath = file.toPath() val actions = result - .map { (symbol, edits) -> + .map { (fqName, edits) -> CodeActionItem( - title = symbol.fqName, + title = fqName, changes = listOf(DocumentChange(file = nioPath, edits = edits)), kind = CodeActionKind.QuickFix, command = Command.CMD_FORMAT_CODE, From 8436ce8dc825be80a8b8da99cb4de9d0105878fe Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 27 Jul 2026 13:15:14 +0000 Subject: [PATCH 2/5] ADFA-4867: Point Kotlin code actions at their own tooltip tags Tooltip content is authored per tag (ADFA-4730) and looked up by the literal string, so a wrong tag fails silently at runtime: the action shows another action's tooltip, or none at all. - Organize imports and Null-safety fixes carried no tag, orphaning the content authored for them. - Implement members used Java's editor.codeactions.overridesuper. - Import class(es) used editor.codeactions.kotlin.fiximports, which describes a different action, and one ADFA-4821 records as not implemented for Kotlin. It now uses .kotlin.addimport. - Comment and Uncomment are shared with Java, so the tag becomes a required constructor argument and each language passes its own. Not defaulted, so a new language cannot silently inherit another's tooltip. KotlinCodeActionTooltipTagTest pins all six id-to-tag pairs and asserts no Kotlin action borrows a Java tag. editor.codeactions.kotlin.addimport is new and has no content authored yet. Nothing here is visible in the app until ADFA-4868 is fixed: the code-actions submenu resolves actions under the wrong location, so no tooltip tag is read today. --- .../androidide/idetooltips/TooltipTag.kt | 10 +- .../lsp/actions/CommentLineAction.kt | 175 +++++++++-------- .../lsp/actions/UncommentLineAction.kt | 185 +++++++++--------- .../lsp/java/actions/JavaCodeActionsMenu.kt | 165 ++++++++-------- .../lsp/kotlin/KotlinCodeActionsMenu.kt | 15 +- .../lsp/kotlin/actions/AddImportAction.kt | 2 +- .../kotlin/actions/ImplementMembersAction.kt | 2 +- .../lsp/kotlin/actions/NullSafetyAction.kt | 2 + .../kotlin/actions/OrganizeImportsAction.kt | 2 + .../kotlin/KotlinCodeActionTooltipTagTest.kt | 45 +++++ 10 files changed, 340 insertions(+), 263 deletions(-) create mode 100644 lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index cacf5cc30e..57f9d1eb35 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -79,7 +79,15 @@ object TooltipTag { const val EDITOR_CODE_ACTIONS_GEN_TO_STRING_DIALOG = "editor.codeactions.gentostring.dialog" const val EDITOR_CODE_ACTIONS_UNUSED_IMPORTS = "editor.codeactions.unusedimports" const val EDITOR_CODE_ACTIONS_ORGANIZE_IMPORTS = "editor.codeactions.organizeimports" - const val EDITOR_CODE_ACTIONS_KT_FIX_IMPORTS = "editor.codeactions.kotlin.fiximports" + + // Kotlin code actions. Tags are per-language even where the action exists in both languages, + // so the tooltip can describe the Kotlin behaviour (see ADFA-4730). + const val EDITOR_CODE_ACTIONS_KT_COMMENT = "editor.codeactions.kotlin.comment" + const val EDITOR_CODE_ACTIONS_KT_UNCOMMENT = "editor.codeactions.kotlin.uncomment" + const val EDITOR_CODE_ACTIONS_KT_ADD_IMPORT = "editor.codeactions.kotlin.addimport" + const val EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS = "editor.codeactions.kotlin.organizeimports" + const val EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER = "editor.codeactions.kotlin.overridesuper" + const val EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX = "editor.codeactions.kotlin.nullsafetyfix" const val EXIT_TO_MAIN = "exit.to.main" diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt index d95d804fd3..c95135e1a5 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt @@ -1,86 +1,89 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ -package com.itsaky.androidide.lsp.actions - -import android.content.Context -import android.graphics.drawable.Drawable -import com.itsaky.androidide.actions.ActionData -import com.itsaky.androidide.actions.ActionItem -import com.itsaky.androidide.actions.EditorActionItem -import com.itsaky.androidide.actions.hasRequiredData -import com.itsaky.androidide.actions.markInvisible -import com.itsaky.androidide.actions.requireContext -import com.itsaky.androidide.actions.requireEditor -import com.itsaky.androidide.actions.requireFile -import com.itsaky.androidide.idetooltips.TooltipTag -import com.itsaky.androidide.resources.R -import io.github.rosemoe.sora.text.batchEdit -import java.io.File - -/** @author Akash Yadav */ -class CommentLineAction( - lang: String, - private val targetFileExtensions: List, - private val lineCommentToken: String, -) : EditorActionItem { - - constructor(lang: String, extension: String, lineCommentToken: String) : - this(lang, listOf(extension), lineCommentToken) - override val id: String = "ide.editor.lsp.$lang.commentLine" - override var label: String = "" - - override var visible = true - override var enabled = true - override var icon: Drawable? = null - override var location: ActionItem.Location = ActionItem.Location.EDITOR_CODE_ACTIONS - override var requiresUIThread: Boolean = true - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_COMMENT - - override fun prepare(data: ActionData) { - super.prepare(data) - - if (!data.hasRequiredData(Context::class.java, File::class.java)) { - markInvisible() - return - } - - val context = data.requireContext() - label = context.getString(R.string.action_comment_line) - - val file = data.requireFile() - if (file.extension !in targetFileExtensions) { - markInvisible() - return - } - } - - override suspend fun execAction(data: ActionData): Boolean { - val editor = data.requireEditor() - val text = editor.text - val cursor = editor.cursor - - text.batchEdit { - for (line in cursor.leftLine..cursor.rightLine) { - text.insert(line, 0, lineCommentToken) - } - } - - return true - } - - override fun dismissOnAction() = true -} +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ +package com.itsaky.androidide.lsp.actions + +import android.content.Context +import android.graphics.drawable.Drawable +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.EditorActionItem +import com.itsaky.androidide.actions.hasRequiredData +import com.itsaky.androidide.actions.markInvisible +import com.itsaky.androidide.actions.requireContext +import com.itsaky.androidide.actions.requireEditor +import com.itsaky.androidide.actions.requireFile +import com.itsaky.androidide.resources.R +import io.github.rosemoe.sora.text.batchEdit +import java.io.File + +/** @author Akash Yadav */ +class CommentLineAction( + lang: String, + private val targetFileExtensions: List, + private val lineCommentToken: String, + tag: String, +) : EditorActionItem { + constructor(lang: String, extension: String, lineCommentToken: String, tag: String) : + this(lang, listOf(extension), lineCommentToken, tag) + + override val id: String = "ide.editor.lsp.$lang.commentLine" + override var label: String = "" + + override var visible = true + override var enabled = true + override var icon: Drawable? = null + override var location: ActionItem.Location = ActionItem.Location.EDITOR_CODE_ACTIONS + override var requiresUIThread: Boolean = true + + // Required, not defaulted: one instance is registered per language, and a default would let a + // new language silently inherit another language's tooltip. + override var tooltipTag: String = tag + + override fun prepare(data: ActionData) { + super.prepare(data) + + if (!data.hasRequiredData(Context::class.java, File::class.java)) { + markInvisible() + return + } + + val context = data.requireContext() + label = context.getString(R.string.action_comment_line) + + val file = data.requireFile() + if (file.extension !in targetFileExtensions) { + markInvisible() + return + } + } + + override suspend fun execAction(data: ActionData): Boolean { + val editor = data.requireEditor() + val text = editor.text + val cursor = editor.cursor + + text.batchEdit { + for (line in cursor.leftLine..cursor.rightLine) { + text.insert(line, 0, lineCommentToken) + } + } + + return true + } + + override fun dismissOnAction() = true +} diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt index 66cf2b9ffd..057ce5dd8a 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt @@ -1,92 +1,93 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ -package com.itsaky.androidide.lsp.actions - -import android.content.Context -import android.graphics.drawable.Drawable -import com.itsaky.androidide.actions.ActionData -import com.itsaky.androidide.actions.ActionItem -import com.itsaky.androidide.actions.EditorActionItem -import com.itsaky.androidide.actions.hasRequiredData -import com.itsaky.androidide.actions.markInvisible -import com.itsaky.androidide.actions.requireContext -import com.itsaky.androidide.actions.requireEditor -import com.itsaky.androidide.actions.requireFile -import com.itsaky.androidide.idetooltips.TooltipTag -import com.itsaky.androidide.resources.R -import io.github.rosemoe.sora.text.batchEdit -import java.io.File - -/** @author Akash Yadav */ -class UncommentLineAction( - lang: String, - private val targetFileExtensions: List, - private val lineCommentToken: String, -) : EditorActionItem { - - constructor(lang: String, extension: String, lineCommentToken: String) : - this(lang, listOf(extension), lineCommentToken) - - override val id: String = "ide.editor.lsp.$lang.uncommentLine" - override var label: String = "" - - override var visible = true - override var enabled = true - override var icon: Drawable? = null - override var location: ActionItem.Location = ActionItem.Location.EDITOR_CODE_ACTIONS - override var requiresUIThread: Boolean = true - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_UNCOMMENT - - - override fun prepare(data: ActionData) { - super.prepare(data) - - if (!data.hasRequiredData(Context::class.java, File::class.java)) { - markInvisible() - return - } - - val context = data.requireContext() - label = context.getString(R.string.action_uncomment_line) - - val file = data.requireFile() - if (file.extension !in targetFileExtensions) { - markInvisible() - return - } - } - - override suspend fun execAction(data: ActionData): Boolean { - val editor = data.requireEditor() - val text = editor.text - val cursor = editor.cursor - - text.batchEdit { - for (line in cursor.leftLine..cursor.rightLine) { - val l = text.getLineString(line) - if (l.trim().startsWith(lineCommentToken)) { - val i = l.indexOf(lineCommentToken) - text.delete(line, i, line, i + 2) - } - } - } - - return true - } - - override fun dismissOnAction() = true -} +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ +package com.itsaky.androidide.lsp.actions + +import android.content.Context +import android.graphics.drawable.Drawable +import com.itsaky.androidide.actions.ActionData +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.actions.EditorActionItem +import com.itsaky.androidide.actions.hasRequiredData +import com.itsaky.androidide.actions.markInvisible +import com.itsaky.androidide.actions.requireContext +import com.itsaky.androidide.actions.requireEditor +import com.itsaky.androidide.actions.requireFile +import com.itsaky.androidide.resources.R +import io.github.rosemoe.sora.text.batchEdit +import java.io.File + +/** @author Akash Yadav */ +class UncommentLineAction( + lang: String, + private val targetFileExtensions: List, + private val lineCommentToken: String, + tag: String, +) : EditorActionItem { + constructor(lang: String, extension: String, lineCommentToken: String, tag: String) : + this(lang, listOf(extension), lineCommentToken, tag) + + override val id: String = "ide.editor.lsp.$lang.uncommentLine" + override var label: String = "" + + override var visible = true + override var enabled = true + override var icon: Drawable? = null + override var location: ActionItem.Location = ActionItem.Location.EDITOR_CODE_ACTIONS + override var requiresUIThread: Boolean = true + + // Required, not defaulted: one instance is registered per language, and a default would let a + // new language silently inherit another language's tooltip. + override var tooltipTag: String = tag + + override fun prepare(data: ActionData) { + super.prepare(data) + + if (!data.hasRequiredData(Context::class.java, File::class.java)) { + markInvisible() + return + } + + val context = data.requireContext() + label = context.getString(R.string.action_uncomment_line) + + val file = data.requireFile() + if (file.extension !in targetFileExtensions) { + markInvisible() + return + } + } + + override suspend fun execAction(data: ActionData): Boolean { + val editor = data.requireEditor() + val text = editor.text + val cursor = editor.cursor + + text.batchEdit { + for (line in cursor.leftLine..cursor.rightLine) { + val l = text.getLineString(line) + if (l.trim().startsWith(lineCommentToken)) { + val i = l.indexOf(lineCommentToken) + text.delete(line, i, line, i + 2) + } + } + } + + return true + } + + override fun dismissOnAction() = true +} diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/actions/JavaCodeActionsMenu.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/actions/JavaCodeActionsMenu.kt index 1f1b3ecdfc..cfd4fd35e5 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/actions/JavaCodeActionsMenu.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/actions/JavaCodeActionsMenu.kt @@ -1,80 +1,85 @@ -/* - * This file is part of AndroidIDE. - * - * AndroidIDE is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * AndroidIDE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with AndroidIDE. If not, see . - */ - -package com.itsaky.androidide.lsp.java.actions - -import com.itsaky.androidide.actions.ActionItem -import com.itsaky.androidide.lsp.actions.IActionsMenuProvider -import com.itsaky.androidide.lsp.actions.CommentLineAction -import com.itsaky.androidide.lsp.java.actions.common.FindReferencesAction -import com.itsaky.androidide.lsp.java.actions.common.GoToDefinitionAction -import com.itsaky.androidide.lsp.java.actions.common.OrganizeImportsAction -import com.itsaky.androidide.lsp.java.actions.common.RemoveUnusedImportsAction -import com.itsaky.androidide.lsp.actions.UncommentLineAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.AddImportAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.AddThrowsAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.AutoFixImportsAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.CreateMissingMethodAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.FieldToBlockAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.ImplementAbstractMethodsAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveClassAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveMethodAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveUnusedThrowsAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.SuppressUncheckedWarningAction -import com.itsaky.androidide.lsp.java.actions.diagnostics.VariableToStatementAction -import com.itsaky.androidide.lsp.java.actions.generators.GenerateConstructorAction -import com.itsaky.androidide.lsp.java.actions.generators.GenerateMissingConstructorAction -import com.itsaky.androidide.lsp.java.actions.generators.GenerateSettersAndGettersAction -import com.itsaky.androidide.lsp.java.actions.generators.GenerateToStringMethodAction -import com.itsaky.androidide.lsp.java.actions.generators.OverrideSuperclassMethodsAction - -/** - * Java code actions. - * @author Akash Yadav - */ -object JavaCodeActionsMenu : IActionsMenuProvider { - - private const val LANG = "java" - private const val EXT = "java" - private const val LINE_COMMENT_TOKEN = "//" - - override val actions: List = - listOf( - CommentLineAction(LANG, EXT, LINE_COMMENT_TOKEN), - UncommentLineAction(LANG, EXT, LINE_COMMENT_TOKEN), - GoToDefinitionAction(), - FindReferencesAction(), - AddImportAction(), - AutoFixImportsAction(), - ImplementAbstractMethodsAction(), - VariableToStatementAction(), - FieldToBlockAction(), - RemoveClassAction(), - RemoveMethodAction(), - RemoveUnusedThrowsAction(), - CreateMissingMethodAction(), - SuppressUncheckedWarningAction(), - AddThrowsAction(), - GenerateSettersAndGettersAction(), - OverrideSuperclassMethodsAction(), - GenerateMissingConstructorAction(), - GenerateConstructorAction(), - GenerateToStringMethodAction(), - RemoveUnusedImportsAction(), - OrganizeImportsAction() - ) -} +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.lsp.java.actions + +import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.idetooltips.TooltipTag +import com.itsaky.androidide.lsp.actions.CommentLineAction +import com.itsaky.androidide.lsp.actions.IActionsMenuProvider +import com.itsaky.androidide.lsp.actions.UncommentLineAction +import com.itsaky.androidide.lsp.java.actions.common.FindReferencesAction +import com.itsaky.androidide.lsp.java.actions.common.GoToDefinitionAction +import com.itsaky.androidide.lsp.java.actions.common.OrganizeImportsAction +import com.itsaky.androidide.lsp.java.actions.common.RemoveUnusedImportsAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.AddImportAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.AddThrowsAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.AutoFixImportsAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.CreateMissingMethodAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.FieldToBlockAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.ImplementAbstractMethodsAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveClassAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveMethodAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.RemoveUnusedThrowsAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.SuppressUncheckedWarningAction +import com.itsaky.androidide.lsp.java.actions.diagnostics.VariableToStatementAction +import com.itsaky.androidide.lsp.java.actions.generators.GenerateConstructorAction +import com.itsaky.androidide.lsp.java.actions.generators.GenerateMissingConstructorAction +import com.itsaky.androidide.lsp.java.actions.generators.GenerateSettersAndGettersAction +import com.itsaky.androidide.lsp.java.actions.generators.GenerateToStringMethodAction +import com.itsaky.androidide.lsp.java.actions.generators.OverrideSuperclassMethodsAction + +/** + * Java code actions. + * @author Akash Yadav + */ +object JavaCodeActionsMenu : IActionsMenuProvider { + private const val LANG = "java" + private const val EXT = "java" + private const val LINE_COMMENT_TOKEN = "//" + + override val actions: List = + listOf( + CommentLineAction(LANG, EXT, LINE_COMMENT_TOKEN, TooltipTag.EDITOR_CODE_ACTIONS_COMMENT), + UncommentLineAction( + LANG, + EXT, + LINE_COMMENT_TOKEN, + TooltipTag.EDITOR_CODE_ACTIONS_UNCOMMENT, + ), + GoToDefinitionAction(), + FindReferencesAction(), + AddImportAction(), + AutoFixImportsAction(), + ImplementAbstractMethodsAction(), + VariableToStatementAction(), + FieldToBlockAction(), + RemoveClassAction(), + RemoveMethodAction(), + RemoveUnusedThrowsAction(), + CreateMissingMethodAction(), + SuppressUncheckedWarningAction(), + AddThrowsAction(), + GenerateSettersAndGettersAction(), + OverrideSuperclassMethodsAction(), + GenerateMissingConstructorAction(), + GenerateConstructorAction(), + GenerateToStringMethodAction(), + RemoveUnusedImportsAction(), + OrganizeImportsAction(), + ) +} diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt index 2ce2175745..e7d5482a0e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt @@ -1,6 +1,7 @@ package com.itsaky.androidide.lsp.kotlin import com.itsaky.androidide.actions.ActionItem +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.lsp.actions.CommentLineAction import com.itsaky.androidide.lsp.actions.IActionsMenuProvider import com.itsaky.androidide.lsp.actions.UncommentLineAction @@ -16,8 +17,18 @@ object KotlinCodeActionsMenu : IActionsMenuProvider { override val actions: List = listOf( - CommentLineAction(KT_LANG, KT_EXTS, KT_LINE_COMMENT_TOKEN), - UncommentLineAction(KT_LANG, KT_EXTS, KT_LINE_COMMENT_TOKEN), + CommentLineAction( + KT_LANG, + KT_EXTS, + KT_LINE_COMMENT_TOKEN, + TooltipTag.EDITOR_CODE_ACTIONS_KT_COMMENT, + ), + UncommentLineAction( + KT_LANG, + KT_EXTS, + KT_LINE_COMMENT_TOKEN, + TooltipTag.EDITOR_CODE_ACTIONS_KT_UNCOMMENT, + ), AddImportAction(), OrganizeImportsAction(), NullSafetyAction(), diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt index bf9641a32b..77d3407e28 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt @@ -24,7 +24,7 @@ import java.nio.file.Path class AddImportAction : BaseKotlinCodeAction() { override var titleTextRes: Int = R.string.action_import_classes - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_FIX_IMPORTS + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_ADD_IMPORT override val id: String = "ide.editor.lsp.kt.diagnostics.addImport" override var label: String = "" diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt index a432403754..9b2de30f2e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt @@ -30,7 +30,7 @@ import java.nio.file.Path class ImplementMembersAction : BaseKotlinCodeAction() { override var titleTextRes: Int = R.string.action_implement_members - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_OVERRIDE_SUPER + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER override val id: String = "ide.editor.lsp.kt.implementMembers" override var label: String = "" diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt index 3d8f21d435..48420f066c 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt @@ -5,6 +5,7 @@ import com.itsaky.androidide.actions.markInvisible import com.itsaky.androidide.actions.newDialogBuilder import com.itsaky.androidide.actions.requireContext import com.itsaky.androidide.actions.requireFile +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.lsp.kotlin.compiler.read import com.itsaky.androidide.lsp.kotlin.diagnostic.DiagnosticAction import com.itsaky.androidide.lsp.kotlin.utils.NullSafetyKind @@ -34,6 +35,7 @@ class NullSafetyAction : BaseKotlinCodeAction() { override var titleTextRes: Int = R.string.action_null_safety_fixes override val id: String = "ide.editor.lsp.kt.diagnostics.nullSafety" override var label: String = "" + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX override fun prepare(data: ActionData) { super.prepare(data) diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt index 603bfaf6cc..ca2d5aa8b9 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt @@ -3,6 +3,7 @@ package com.itsaky.androidide.lsp.kotlin.actions import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.get import com.itsaky.androidide.actions.requireFile +import com.itsaky.androidide.idetooltips.TooltipTag import com.itsaky.androidide.lsp.kotlin.KotlinLanguageServer import com.itsaky.androidide.lsp.kotlin.compiler.AbstractCompilationEnvironment import com.itsaky.androidide.lsp.kotlin.compiler.modules.analyzeMaybeDangling @@ -24,6 +25,7 @@ class OrganizeImportsAction : BaseKotlinCodeAction() { override var titleTextRes: Int = R.string.action_organize_imports override val id: String = "ide.editor.lsp.kt.organizeImports" override var label: String = "" + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS companion object { private val logger = LoggerFactory.getLogger(OrganizeImportsAction::class.java) diff --git a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt new file mode 100644 index 0000000000..442510a2c8 --- /dev/null +++ b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt @@ -0,0 +1,45 @@ +package com.itsaky.androidide.lsp.kotlin + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Pins each Kotlin code action to its tooltip tag. Tooltip content is authored per tag and looked + * up by the literal string, so a wrong tag fails silently at runtime: the action either shows + * another action's tooltip or none at all (ADFA-4867). Expected values are spelled out rather than + * read from [com.itsaky.androidide.idetooltips.TooltipTag] so that editing a constant fails here. + */ +class KotlinCodeActionTooltipTagTest { + private val actualTags + get() = KotlinCodeActionsMenu.actions.associate { it.id to it.tooltipTag } + + @Test + fun `every kotlin code action maps to its own tooltip tag`() { + val expected = + mapOf( + "ide.editor.lsp.kt.commentLine" to "editor.codeactions.kotlin.comment", + "ide.editor.lsp.kt.uncommentLine" to "editor.codeactions.kotlin.uncomment", + "ide.editor.lsp.kt.diagnostics.addImport" to "editor.codeactions.kotlin.addimport", + "ide.editor.lsp.kt.organizeImports" to "editor.codeactions.kotlin.organizeimports", + "ide.editor.lsp.kt.diagnostics.nullSafety" to "editor.codeactions.kotlin.nullsafetyfix", + "ide.editor.lsp.kt.implementMembers" to "editor.codeactions.kotlin.overridesuper", + ) + assertEquals(expected, actualTags) + } + + /** Guards the specific regression: a Kotlin action reusing a Java tag, or carrying none. */ + @Test + fun `no kotlin code action borrows a java tooltip tag`() { + actualTags.forEach { (id, tag) -> + assertTrue( + "$id has no tooltip tag", + tag.isNotEmpty(), + ) + assertTrue( + "$id uses non-Kotlin tooltip tag '$tag'", + tag.startsWith("editor.codeactions.kotlin."), + ) + } + } +} From d202e77c8f0ef5d1e4b398a5eae42aae8e93950a Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 27 Jul 2026 14:17:58 +0000 Subject: [PATCH 3/5] ADFA-4867: Rename the add-import tag and single-source action ids Two changes. The Import class(es) tag becomes editor.codeactions.kotlin.importclasses, matching the label users actually see. addimport described the class name in our code rather than the user-facing concept, and Elissa names tags by the latter. Action ids move into each action's companion object as a single definition and are reused from there, following the pattern already in FieldBasedAction. The tooltip-tag test was the second place these ids were spelled out; it now reads the companion ids and the TooltipTag constants instead of literals. CommentLineAction and UncommentLineAction take a companion idFor(lang) rather than a const, since one instance is registered per language. KT_LANG in KotlinCodeActionsMenu becomes internal so the test uses the same value the menu does. Also adds the Surround with try/catch entry to the test, which ADFA-4613 registered after this branch was cut. --- .../androidide/idetooltips/TooltipTag.kt | 2 +- .../lsp/actions/CommentLineAction.kt | 7 +++- .../lsp/actions/UncommentLineAction.kt | 7 +++- .../lsp/kotlin/KotlinCodeActionsMenu.kt | 2 +- .../lsp/kotlin/actions/AddImportAction.kt | 8 +++-- .../kotlin/actions/ImplementMembersAction.kt | 6 +++- .../lsp/kotlin/actions/NullSafetyAction.kt | 6 +++- .../kotlin/actions/OrganizeImportsAction.kt | 4 ++- .../actions/SurroundWithTryCatchAction.kt | 6 +++- .../kotlin/KotlinCodeActionTooltipTagTest.kt | 33 +++++++++++++------ 10 files changed, 61 insertions(+), 20 deletions(-) diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index dfaa034942..8c48e8e5c1 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -84,7 +84,7 @@ object TooltipTag { // so the tooltip can describe the Kotlin behaviour (see ADFA-4730). const val EDITOR_CODE_ACTIONS_KT_COMMENT = "editor.codeactions.kotlin.comment" const val EDITOR_CODE_ACTIONS_KT_UNCOMMENT = "editor.codeactions.kotlin.uncomment" - const val EDITOR_CODE_ACTIONS_KT_ADD_IMPORT = "editor.codeactions.kotlin.addimport" + const val EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES = "editor.codeactions.kotlin.importclasses" const val EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS = "editor.codeactions.kotlin.organizeimports" const val EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER = "editor.codeactions.kotlin.overridesuper" const val EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX = "editor.codeactions.kotlin.nullsafetyfix" diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt index c95135e1a5..cd5b5eff56 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/CommentLineAction.kt @@ -37,10 +37,15 @@ class CommentLineAction( private val lineCommentToken: String, tag: String, ) : EditorActionItem { + companion object { + /** The id is per-language, since one instance is registered per language. */ + fun idFor(lang: String) = "ide.editor.lsp.$lang.commentLine" + } + constructor(lang: String, extension: String, lineCommentToken: String, tag: String) : this(lang, listOf(extension), lineCommentToken, tag) - override val id: String = "ide.editor.lsp.$lang.commentLine" + override val id: String = idFor(lang) override var label: String = "" override var visible = true diff --git a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt index 057ce5dd8a..9a271012f6 100644 --- a/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt +++ b/lsp/api/src/main/java/com/itsaky/androidide/lsp/actions/UncommentLineAction.kt @@ -37,10 +37,15 @@ class UncommentLineAction( private val lineCommentToken: String, tag: String, ) : EditorActionItem { + companion object { + /** The id is per-language, since one instance is registered per language. */ + fun idFor(lang: String) = "ide.editor.lsp.$lang.uncommentLine" + } + constructor(lang: String, extension: String, lineCommentToken: String, tag: String) : this(lang, listOf(extension), lineCommentToken, tag) - override val id: String = "ide.editor.lsp.$lang.uncommentLine" + override val id: String = idFor(lang) override var label: String = "" override var visible = true diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt index 581b0ad7ce..7acaabf9a1 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt @@ -12,7 +12,7 @@ import com.itsaky.androidide.lsp.kotlin.actions.OrganizeImportsAction import com.itsaky.androidide.lsp.kotlin.actions.SurroundWithTryCatchAction object KotlinCodeActionsMenu : IActionsMenuProvider { - private const val KT_LANG = "kt" + internal const val KT_LANG = "kt" private val KT_EXTS = listOf("kt", "kts") private const val KT_LINE_COMMENT_TOKEN = "//" diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt index 77d3407e28..a79949a7ab 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt @@ -23,10 +23,14 @@ import kotlinx.coroutines.withContext import java.nio.file.Path class AddImportAction : BaseKotlinCodeAction() { + companion object { + const val ID = "ide.editor.lsp.kt.diagnostics.addImport" + } + override var titleTextRes: Int = R.string.action_import_classes - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_ADD_IMPORT + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES - override val id: String = "ide.editor.lsp.kt.diagnostics.addImport" + override val id: String = ID override var label: String = "" override fun prepare(data: ActionData) { diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt index 9b2de30f2e..89cbb2d18e 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt @@ -29,9 +29,13 @@ import org.jetbrains.kotlin.psi.KtFile import java.nio.file.Path class ImplementMembersAction : BaseKotlinCodeAction() { + companion object { + const val ID = "ide.editor.lsp.kt.implementMembers" + } + override var titleTextRes: Int = R.string.action_implement_members override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER - override val id: String = "ide.editor.lsp.kt.implementMembers" + override val id: String = ID override var label: String = "" // Intentionally no prepare() visibility gate: the action is visible on any Kotlin file (BaseKotlinCodeAction diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt index 48420f066c..7327aa9d2d 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt @@ -32,8 +32,12 @@ import kotlinx.coroutines.withContext * are out of scope here. */ class NullSafetyAction : BaseKotlinCodeAction() { + companion object { + const val ID = "ide.editor.lsp.kt.diagnostics.nullSafety" + } + override var titleTextRes: Int = R.string.action_null_safety_fixes - override val id: String = "ide.editor.lsp.kt.diagnostics.nullSafety" + override val id: String = ID override var label: String = "" override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt index ca2d5aa8b9..ce5dfafb97 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/OrganizeImportsAction.kt @@ -23,11 +23,13 @@ import java.nio.file.Path class OrganizeImportsAction : BaseKotlinCodeAction() { override var titleTextRes: Int = R.string.action_organize_imports - override val id: String = "ide.editor.lsp.kt.organizeImports" + override val id: String = ID override var label: String = "" override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS companion object { + const val ID = "ide.editor.lsp.kt.organizeImports" + private val logger = LoggerFactory.getLogger(OrganizeImportsAction::class.java) } diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/SurroundWithTryCatchAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/SurroundWithTryCatchAction.kt index d34d116892..b73eb23b6b 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/SurroundWithTryCatchAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/SurroundWithTryCatchAction.kt @@ -14,10 +14,14 @@ import com.itsaky.androidide.lsp.models.TextEdit import com.itsaky.androidide.resources.R class SurroundWithTryCatchAction : BaseKotlinCodeAction() { + companion object { + const val ID = "ide.editor.lsp.kt.surroundWithTryCatch" + } + override var titleTextRes: Int = R.string.action_surround_with_try_catch override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_SURROUND_TRY_CATCH - override val id: String = "ide.editor.lsp.kt.surroundWithTryCatch" + override val id: String = ID override var label: String = "" // Reads the editor selection, so it must run on the UI thread (as CommentLineAction does). diff --git a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt index 442510a2c8..c6425744c0 100644 --- a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt +++ b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt @@ -1,14 +1,26 @@ package com.itsaky.androidide.lsp.kotlin +import com.itsaky.androidide.idetooltips.TooltipTag +import com.itsaky.androidide.lsp.actions.CommentLineAction +import com.itsaky.androidide.lsp.actions.UncommentLineAction +import com.itsaky.androidide.lsp.kotlin.KotlinCodeActionsMenu.KT_LANG +import com.itsaky.androidide.lsp.kotlin.actions.AddImportAction +import com.itsaky.androidide.lsp.kotlin.actions.ImplementMembersAction +import com.itsaky.androidide.lsp.kotlin.actions.NullSafetyAction +import com.itsaky.androidide.lsp.kotlin.actions.OrganizeImportsAction +import com.itsaky.androidide.lsp.kotlin.actions.SurroundWithTryCatchAction import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test /** - * Pins each Kotlin code action to its tooltip tag. Tooltip content is authored per tag and looked - * up by the literal string, so a wrong tag fails silently at runtime: the action either shows - * another action's tooltip or none at all (ADFA-4867). Expected values are spelled out rather than - * read from [com.itsaky.androidide.idetooltips.TooltipTag] so that editing a constant fails here. + * Pins each Kotlin code action to its tooltip tag. Tooltip content is authored per tag and looked up + * by that tag, so a wrong tag fails silently at runtime: the action either shows another action's + * tooltip or none at all (ADFA-4867). + * + * This catches an action pointing at the wrong constant, or a newly registered action carrying no + * tag. It cannot catch a tag's *value* being edited, since both sides read the same constant -- the + * prefix assertion below is the backstop for a Kotlin action drifting onto a Java tag. */ class KotlinCodeActionTooltipTagTest { private val actualTags @@ -18,12 +30,13 @@ class KotlinCodeActionTooltipTagTest { fun `every kotlin code action maps to its own tooltip tag`() { val expected = mapOf( - "ide.editor.lsp.kt.commentLine" to "editor.codeactions.kotlin.comment", - "ide.editor.lsp.kt.uncommentLine" to "editor.codeactions.kotlin.uncomment", - "ide.editor.lsp.kt.diagnostics.addImport" to "editor.codeactions.kotlin.addimport", - "ide.editor.lsp.kt.organizeImports" to "editor.codeactions.kotlin.organizeimports", - "ide.editor.lsp.kt.diagnostics.nullSafety" to "editor.codeactions.kotlin.nullsafetyfix", - "ide.editor.lsp.kt.implementMembers" to "editor.codeactions.kotlin.overridesuper", + CommentLineAction.idFor(KT_LANG) to TooltipTag.EDITOR_CODE_ACTIONS_KT_COMMENT, + UncommentLineAction.idFor(KT_LANG) to TooltipTag.EDITOR_CODE_ACTIONS_KT_UNCOMMENT, + AddImportAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES, + OrganizeImportsAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS, + NullSafetyAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX, + ImplementMembersAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER, + SurroundWithTryCatchAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_SURROUND_TRY_CATCH, ) assertEquals(expected, actualTags) } From c7c209bd961a09182286f9f8b6018d7ccc7541aa Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 27 Jul 2026 14:29:11 +0000 Subject: [PATCH 4/5] ADFA-4867: Retag Implement members as implementmembers, not overridesuper Implement members only inserts stubs for inherited members whose effective modality is ABSTRACT (AbstractMemberStubs.kt, membersToImplement). It cannot override a concrete open member, so it is the equivalent of Java's "Implement abstract method(s)" widened to properties -- not of Java's "Override superclass methods", which offers any non-final inherited method (OverrideSuperclassMethodsAction.kt:141). The tag now says what the action does. editor.codeactions.kotlin.overridesuper is retired; editor.codeactions.kotlin.implementmembers is new and needs content authored. The label stays "Implement members" -- that is what IntelliJ and Android Studio call it, so it is what Kotlin developers expect. Java's ImplementAbstractMethodsAction borrows editor.codeactions.overridesuper for the same reason, so neither language had a correct tag for this. That half is tracked in ADFA-4869. --- .../main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt | 2 +- .../androidide/lsp/kotlin/actions/ImplementMembersAction.kt | 2 +- .../androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index 8c48e8e5c1..33ec637000 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -86,7 +86,7 @@ object TooltipTag { const val EDITOR_CODE_ACTIONS_KT_UNCOMMENT = "editor.codeactions.kotlin.uncomment" const val EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES = "editor.codeactions.kotlin.importclasses" const val EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS = "editor.codeactions.kotlin.organizeimports" - const val EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER = "editor.codeactions.kotlin.overridesuper" + const val EDITOR_CODE_ACTIONS_KT_IMPLEMENT_MEMBERS = "editor.codeactions.kotlin.implementmembers" const val EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX = "editor.codeactions.kotlin.nullsafetyfix" const val EDITOR_CODE_ACTIONS_KT_SURROUND_TRY_CATCH = "editor.codeactions.kotlin.trycatch" diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt index 89cbb2d18e..bdc1eb2c07 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/ImplementMembersAction.kt @@ -34,7 +34,7 @@ class ImplementMembersAction : BaseKotlinCodeAction() { } override var titleTextRes: Int = R.string.action_implement_members - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPLEMENT_MEMBERS override val id: String = ID override var label: String = "" diff --git a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt index c6425744c0..4938c009e5 100644 --- a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt +++ b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt @@ -35,7 +35,7 @@ class KotlinCodeActionTooltipTagTest { AddImportAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES, OrganizeImportsAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS, NullSafetyAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX, - ImplementMembersAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_OVERRIDE_SUPER, + ImplementMembersAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPLEMENT_MEMBERS, SurroundWithTryCatchAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_SURROUND_TRY_CATCH, ) assertEquals(expected, actualTags) From ca0e0db1cbce7d325393e23ba7c17289f5a8259e Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Mon, 27 Jul 2026 15:56:00 +0000 Subject: [PATCH 5/5] ADFA-4867: Use importclass, the tag name Elissa specified Her updated table on ADFA-4821 spells it editor.codeactions.kotlin.importclass, singular. We had importclasses. She owns the tag namespace, so match it. Every other Kotlin tag in that table already matches what this branch wires. --- .../main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt | 2 +- .../com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt | 2 +- .../androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt index 33ec637000..1af2e6f074 100644 --- a/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt +++ b/idetooltips/src/main/java/com/itsaky/androidide/idetooltips/TooltipTag.kt @@ -84,7 +84,7 @@ object TooltipTag { // so the tooltip can describe the Kotlin behaviour (see ADFA-4730). const val EDITOR_CODE_ACTIONS_KT_COMMENT = "editor.codeactions.kotlin.comment" const val EDITOR_CODE_ACTIONS_KT_UNCOMMENT = "editor.codeactions.kotlin.uncomment" - const val EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES = "editor.codeactions.kotlin.importclasses" + const val EDITOR_CODE_ACTIONS_KT_IMPORT_CLASS = "editor.codeactions.kotlin.importclass" const val EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS = "editor.codeactions.kotlin.organizeimports" const val EDITOR_CODE_ACTIONS_KT_IMPLEMENT_MEMBERS = "editor.codeactions.kotlin.implementmembers" const val EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX = "editor.codeactions.kotlin.nullsafetyfix" diff --git a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt index a79949a7ab..ca820ac7ac 100644 --- a/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt +++ b/lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/AddImportAction.kt @@ -28,7 +28,7 @@ class AddImportAction : BaseKotlinCodeAction() { } override var titleTextRes: Int = R.string.action_import_classes - override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES + override var tooltipTag: String = TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASS override val id: String = ID override var label: String = "" diff --git a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt index 4938c009e5..7111eb0103 100644 --- a/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt +++ b/lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionTooltipTagTest.kt @@ -32,7 +32,7 @@ class KotlinCodeActionTooltipTagTest { mapOf( CommentLineAction.idFor(KT_LANG) to TooltipTag.EDITOR_CODE_ACTIONS_KT_COMMENT, UncommentLineAction.idFor(KT_LANG) to TooltipTag.EDITOR_CODE_ACTIONS_KT_UNCOMMENT, - AddImportAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASSES, + AddImportAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPORT_CLASS, OrganizeImportsAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_ORGANIZE_IMPORTS, NullSafetyAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_NULL_SAFETY_FIX, ImplementMembersAction.ID to TooltipTag.EDITOR_CODE_ACTIONS_KT_IMPLEMENT_MEMBERS,