diff --git a/changelogs/v2.1.1-beta.md b/changelogs/v2.1.1-beta.md
new file mode 100644
index 000000000..97b76038c
--- /dev/null
+++ b/changelogs/v2.1.1-beta.md
@@ -0,0 +1,7 @@
+# **AndroidIDE v2.1.1-beta**
+
+This is a patch release which fixes a bug.
+
+## Fixes
+
+- Bug which caused AbstractMethodError in Tooling API implementation.
\ No newline at end of file
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt
index de0da850f..4d79e612e 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/XmlCompletionProvider.kt
@@ -18,6 +18,8 @@
package com.itsaky.androidide.lsp.xml.providers
import com.android.SdkConstants.ANDROID_MANIFEST_XML
+import com.android.aaptcompiler.AaptResourceType.ANIM
+import com.android.aaptcompiler.AaptResourceType.DRAWABLE
import com.android.aaptcompiler.AaptResourceType.LAYOUT
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.extractPathData
@@ -27,14 +29,19 @@ import com.itsaky.androidide.lsp.api.IServerSettings
import com.itsaky.androidide.lsp.models.CompletionParams
import com.itsaky.androidide.lsp.models.CompletionResult
import com.itsaky.androidide.lsp.models.CompletionResult.Companion.EMPTY
-import com.itsaky.androidide.lsp.xml.providers.completion.IXmlCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.AttrValueCompletionProvider
+import com.itsaky.androidide.lsp.xml.providers.completion.IXmlCompletionProvider
+import com.itsaky.androidide.lsp.xml.providers.completion.canCompleteManifest
+import com.itsaky.androidide.lsp.xml.providers.completion.common.CommonAttrCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.layout.LayoutAttrCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.layout.LayoutTagCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.manifest.ManifestAttrCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.manifest.ManifestAttrValueCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.manifest.ManifestTagCompletionProvider
-import com.itsaky.androidide.lsp.xml.providers.completion.canCompleteManifest
+import com.itsaky.androidide.lsp.xml.utils.AnimTagTransformer
+import com.itsaky.androidide.lsp.xml.utils.DrawableTagTransformer
+import com.itsaky.androidide.lsp.xml.utils.ITagTransformer
+import com.itsaky.androidide.lsp.xml.utils.NoOpTagTransformer
import com.itsaky.androidide.lsp.xml.utils.XmlUtils
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.ATTRIBUTE
@@ -46,10 +53,10 @@ import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.StopWatch
import com.itsaky.xml.INamespace
import io.github.rosemoe.sora.text.ContentReference
-import java.io.IOException
-import java.io.Reader
import org.eclipse.lemminx.dom.DOMParser
import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager
+import java.io.IOException
+import java.io.Reader
import kotlin.io.path.name
/**
@@ -131,10 +138,25 @@ class XmlCompletionProvider(settings: IServerSettings) :
return when (pathData.type) {
LAYOUT -> createLayoutCompleter(type)
null -> createNullTypeCompleter(pathData, type)
+ else -> createCommonCompleter(pathData, type)
+ }
+ }
+
+ private fun createCommonCompleter(pathData: ResourcePathData, type: NodeType): IXmlCompletionProvider? {
+ return when(type) {
+ ATTRIBUTE -> CommonAttrCompletionProvider( tagTransformerFor(pathData),this)
else -> null
}
}
-
+
+ private fun tagTransformerFor(pathData: ResourcePathData): ITagTransformer {
+ return when(pathData.type) {
+ ANIM -> AnimTagTransformer
+ DRAWABLE -> DrawableTagTransformer
+ else -> NoOpTagTransformer
+ }
+ }
+
private fun createNullTypeCompleter(
pathData: ResourcePathData,
type: NodeType
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/common/CommonAttrCompletionProvider.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/common/CommonAttrCompletionProvider.kt
new file mode 100644
index 000000000..c3c5be213
--- /dev/null
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/common/CommonAttrCompletionProvider.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.xml.providers.completion.common
+
+import com.android.aaptcompiler.ConfigDescription
+import com.android.aaptcompiler.ResourceGroup
+import com.android.aaptcompiler.Styleable
+import com.itsaky.androidide.lsp.api.ICompletionProvider
+import com.itsaky.androidide.lsp.xml.providers.completion.AttrCompletionProvider
+import com.itsaky.androidide.lsp.xml.utils.ITagTransformer
+import org.eclipse.lemminx.dom.DOMNode
+
+/**
+ * Provides attribute completion for all other resource types.
+ *
+ * @property tagTransform A function which returns the styleable entry name for the given tag name
+ * (first param) and its parent's tag name (second param).
+ * @author Akash Yadav
+ */
+internal class CommonAttrCompletionProvider(
+ private val tagTransform: ITagTransformer,
+ provider: ICompletionProvider
+) : AttrCompletionProvider(provider) {
+
+ override fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set {
+ val name = node.nodeName
+ val styleable =
+ styleables
+ .findEntry(tagTransform.transform(name, nodeAtCursor.parentNode?.nodeName ?: ""))
+ ?.findValue(ConfigDescription())
+ ?.value
+ if (styleable != null && styleable is Styleable) {
+ return setOf(styleable)
+ }
+
+ return emptySet()
+ }
+}
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestAttrCompletionProvider.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestAttrCompletionProvider.kt
index d58ecd72f..0eeb8c80c 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestAttrCompletionProvider.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestAttrCompletionProvider.kt
@@ -23,6 +23,7 @@ import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.Styleable
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.AttrCompletionProvider
+import com.itsaky.androidide.lsp.xml.providers.completion.MANIFEST_TAG_PREFIX
import com.itsaky.androidide.lsp.xml.providers.completion.canCompleteManifest
import com.itsaky.androidide.lsp.xml.providers.completion.manifestResourceTable
import com.itsaky.androidide.lsp.xml.providers.completion.transformToEntryName
@@ -46,7 +47,7 @@ class ManifestAttrCompletionProvider(provider: ICompletionProvider) :
override fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set {
val name = node.nodeName
val styleable =
- styleables.findEntry(transformToEntryName(name))?.findValue(ConfigDescription())?.value
+ styleables.findEntry(transformToEntryName(name, MANIFEST_TAG_PREFIX))?.findValue(ConfigDescription())?.value
if (styleable != null && styleable is Styleable) {
return setOf(styleable)
}
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestTagCompletionProvider.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestTagCompletionProvider.kt
index 8a06f4748..414f68d8b 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestTagCompletionProvider.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifest/ManifestTagCompletionProvider.kt
@@ -74,7 +74,7 @@ class ManifestTagCompletionProvider(provider: ICompletionProvider) :
styleables
.findEntries { it.startsWith(MANIFEST_TAG_PREFIX) }
- .map { transformToTagName(it.name) }
+ .map { transformToTagName(it.name, MANIFEST_TAG_PREFIX) }
.forEach {
val match = matchLevel(it, newPrefix)
if (match == NO_MATCH) {
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifestFileCompletions.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifestFileCompletions.kt
index 56801589b..2ca1eb594 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifestFileCompletions.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/manifestFileCompletions.kt
@@ -38,59 +38,4 @@ fun manifestResourceTable(): Set {
return setOf(
Lookup.DEFAULT.lookup(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES) ?: return emptySet()
)
-}
-
-/**
- * Transforms entry name to tag name.
- *
- * For example: `AndroidManifestUsesPermission` -> `uses-permission`
- */
-fun transformToTagName(entryName: String): String {
- val name = StringBuilder()
- var index = MANIFEST_TAG_PREFIX.length
- while (index < entryName.length) {
- var c = entryName[index]
- if (c.isUpperCase()) {
- if (index != MANIFEST_TAG_PREFIX.length) {
- name.append('-')
- }
- c = c.lowercaseChar()
- }
-
- name.append(c)
- ++index
- }
- return name.toString()
-}
-
-/**
- * Transforms entry name to tag name.
- *
- * For example: `uses-permission` -> `AndroidManifestUsesPermission`
- */
-fun transformToEntryName(tagName: String): String {
- if (tagName == "manifest") {
- return MANIFEST_TAG_PREFIX
- }
-
- val name = StringBuilder(MANIFEST_TAG_PREFIX)
-
- var index = 0
- var capitalize = false
- while (index < tagName.length) {
- var c = tagName[index]
- if (c == '-') {
- capitalize = true
- ++index
- continue
- }
- if (index == 0 || capitalize) {
- c = c.uppercaseChar()
- capitalize = false
- }
- name.append(c)
- ++index
- }
-
- return name.toString()
-}
+}
\ No newline at end of file
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/xmlCompletions.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/xmlCompletions.kt
index b1695af59..ca04d9b9f 100644
--- a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/xmlCompletions.kt
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/providers/completion/xmlCompletions.kt
@@ -55,4 +55,59 @@ fun findAllNamespaces(node: DOMNode): MutableSet> {
curr = curr.parentNode
}
return namespaces
-}
\ No newline at end of file
+}
+
+/**
+ * Transforms entry name to tag name.
+ *
+ * For example: `AndroidManifestUsesPermission` -> `uses-permission`
+ */
+fun transformToTagName(entryName: String, prefix: String = ""): String {
+ val name = StringBuilder()
+ var index = prefix.length
+ while (index < entryName.length) {
+ var c = entryName[index]
+ if (c.isUpperCase()) {
+ if (index != prefix.length) {
+ name.append('-')
+ }
+ c = c.lowercaseChar()
+ }
+
+ name.append(c)
+ ++index
+ }
+ return name.toString()
+}
+
+/**
+ * Transforms entry name to tag name.
+ *
+ * For example: `uses-permission` -> `AndroidManifestUsesPermission`
+ */
+fun transformToEntryName(tagName: String, prefix: String = ""): String {
+ if (tagName == "manifest") {
+ return MANIFEST_TAG_PREFIX
+ }
+
+ val name = StringBuilder(prefix)
+
+ var index = 0
+ var capitalize = false
+ while (index < tagName.length) {
+ var c = tagName[index]
+ if (c == '-') {
+ capitalize = true
+ ++index
+ continue
+ }
+ if (index == 0 || capitalize) {
+ c = c.uppercaseChar()
+ capitalize = false
+ }
+ name.append(c)
+ ++index
+ }
+
+ return name.toString()
+}
diff --git a/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/tagTransforms.kt b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/tagTransforms.kt
new file mode 100644
index 000000000..2a15ec170
--- /dev/null
+++ b/lsp/xml/src/main/java/com/itsaky/androidide/lsp/xml/utils/tagTransforms.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.xml.utils
+
+import com.itsaky.androidide.lsp.xml.providers.completion.transformToEntryName
+
+/** Transforms tag names to styleable entry names. */
+internal interface ITagTransformer {
+ /** Returns the styleable entry name for the given tag name and its parent's tag name. */
+ fun transform(tag: String, parent: String = ""): String
+}
+
+internal object NoOpTagTransformer : ITagTransformer {
+ override fun transform(tag: String, parent: String): String {
+ return ""
+ }
+}
+
+internal object DrawableTagTransformer : ITagTransformer {
+ override fun transform(tag: String, parent: String): String {
+ if (tag == "corners") {
+ return "DrawableCorners"
+ }
+
+ val prefix =
+ if (parent.isNotEmpty()) {
+ toEntry(parent).run { if (startsWith('#')) toEntry(tag) else this }
+ } else tag
+ val suffix =
+ if (parent.isNotEmpty() && !parent.startsWith('#')) transformToEntryName(tag) else ""
+
+ return "${prefix}Drawable${suffix}"
+ }
+}
+
+internal object AnimTagTransformer : ITagTransformer {
+ override fun transform(tag: String, parent: String): String {
+ if (tag == "set") {
+ return "AnimationSet"
+ }
+
+ return "${toEntry(tag)}${animEntrySuffix(tag)}"
+ }
+
+ /** Returns the suffix that is required to get the styleable entry for an anim resource. */
+ private fun animEntrySuffix(tag: String): String {
+ return when {
+ tag.endsWith("Animation") -> ""
+ tag.endsWith("Interpolator") -> ""
+ else -> "Animation"
+ }
+ }
+}
+
+/**
+ * Convert the tag name to entry name.
+ *
+ * @param tag The tag name.
+ */
+private fun toEntry(tag: String): String {
+ if (tag.startsWith('#')) {
+ return tag
+ }
+
+ when (tag) {
+ "selector" -> return "StateList"
+ "animated-selector" -> return "AnimatedStateList"
+ "animation" -> return "Animation"
+ "shape" -> return "Gradient"
+ }
+
+ return transformToEntryName(tag)
+}
diff --git a/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/providers/completion/CommonAttrCompletionProviderTest.kt b/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/providers/completion/CommonAttrCompletionProviderTest.kt
new file mode 100644
index 000000000..89ccdc236
--- /dev/null
+++ b/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/providers/completion/CommonAttrCompletionProviderTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.xml.providers.completion
+
+import com.google.common.truth.Truth.assertThat
+import com.itsaky.androidide.lsp.xml.CompletionHelper
+import com.itsaky.androidide.lsp.xml.CompletionHelperImpl
+import com.itsaky.androidide.lsp.xml.XMLLSPTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/**
+ * @author Akash Yadav
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE)
+class CommonAttrCompletionProviderTest : CompletionHelper by CompletionHelperImpl() {
+
+ @Before
+ fun setup() = XMLLSPTest.initProjectIfNeeded()
+
+ @Test
+ fun `test simple drawable attributes`() {
+ XMLLSPTest.apply {
+ openFile("../res/drawable/TestDrawableAttrs")
+ val (incomplete, items) = complete()
+ assertThat(incomplete).isFalse()
+ assertThat(items).contains("android:shape")
+ }
+ }
+}
\ No newline at end of file
diff --git a/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/utils/TagTransformsTest.kt b/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/utils/TagTransformsTest.kt
new file mode 100644
index 000000000..41f58ef49
--- /dev/null
+++ b/lsp/xml/src/test/java/com/itsaky/androidide/lsp/xml/utils/TagTransformsTest.kt
@@ -0,0 +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.xml.utils
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/** @author Akash Yadav */
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE)
+class TagTransformsTest {
+
+ @Test
+ fun `test drawable tag transforms`() {
+ val tagMap =
+ mapOf(
+ "selector" to "StateListDrawable",
+ "animated-selector" to "AnimatedStateListDrawable",
+ "animation" to "AnimationDrawable",
+ "shape" to "GradientDrawable",
+ "corners" to "DrawableCorners",
+ "layer" to "LayerDrawable",
+ "level-list" to "LevelListDrawable",
+ "rotate" to "RotateDrawable",
+ "animated-rotate" to "AnimatedRotateDrawable",
+ "inset" to "InsetDrawable",
+ "bitmap" to "BitmapDrawable",
+ "nine-patch" to "NinePatchDrawable",
+ "adaptive-icon" to "AdaptiveIconDrawable",
+ "ripple" to "RippleDrawable",
+ "scale" to "ScaleDrawable",
+ "clip" to "ClipDrawable",
+ "vector" to "VectorDrawable",
+ "animated-vector" to "AnimatedVectorDrawable"
+ )
+
+ tagMap.forEach { (tag, entry) ->
+ assertThat(DrawableTagTransformer.transform(tag, "#document")).isEqualTo(entry)
+ }
+ }
+
+ @Test
+ fun `test drawable sub tag transforms`() {
+ val tags =
+ listOf(
+ Triple("item", "selector", "StateListDrawableItem"),
+ Triple("item", "animated-selector", "AnimatedStateListDrawableItem"),
+ Triple("transition", "animated-selector", "AnimatedStateListDrawableTransition"),
+ Triple("size", "shape", "GradientDrawableSize"),
+ Triple("padding", "shape", "GradientDrawablePadding"),
+ Triple("gradient", "shape", "GradientDrawableGradient")
+ )
+
+ tags.forEach {
+ assertThat(DrawableTagTransformer.transform(it.first, it.second)).isEqualTo(it.third)
+ }
+ }
+
+ @Test
+ fun `test animation tag transforms`() {
+ val tagMap =
+ mapOf(
+ "set" to "AnimationSet",
+ "rotate" to "RotateAnimation",
+ "scale" to "ScaleAnimation",
+ "gridLayoutAnimation" to "GridLayoutAnimation",
+ "accelerateInterpolator" to "AccelerateInterpolator",
+ "accelerateDecelerateInterpolator" to "AccelerateDecelerateInterpolator"
+ )
+
+ tagMap.forEach { (tag, entry) ->
+ assertThat(AnimTagTransformer.transform(tag)).isEqualTo(entry)
+ }
+ }
+}
diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar
index 1728e1b69..6f729e921 100644
Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ
diff --git a/tests/test-project/app/src/main/res/drawable/TestDrawableAttrs_template.xml b/tests/test-project/app/src/main/res/drawable/TestDrawableAttrs_template.xml
new file mode 100644
index 000000000..4f30a929c
--- /dev/null
+++ b/tests/test-project/app/src/main/res/drawable/TestDrawableAttrs_template.xml
@@ -0,0 +1,4 @@
+
+
+