mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Added completion provider for manifest tags
This commit is contained in:
@@ -114,17 +114,22 @@ class CommonCompletionProvider(private val server: ILanguageServer) {
|
||||
|
||||
val frameworkResources = module.getFrameworkResourceTable()
|
||||
if (frameworkResources != null) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES_LOOKUP_KEY, frameworkResources)
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES, frameworkResources)
|
||||
}
|
||||
|
||||
val moduleResources = module.getSourceResourceTables()
|
||||
if (moduleResources.isNotEmpty()) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_MODULE_RES_LOOKUP_KEY, moduleResources)
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_MODULE_RES, moduleResources)
|
||||
}
|
||||
|
||||
val depResTables = module.getDependencyResourceTables()
|
||||
if (depResTables.isNotEmpty()) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_DEP_RES_LOOKUP_KEY, depResTables)
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_DEP_RES, depResTables)
|
||||
}
|
||||
|
||||
val manifestAttrTable = module.getManifestAttrTable()
|
||||
if (manifestAttrTable != null) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES, manifestAttrTable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,5 +352,11 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the resource table for the attrs_manifest.xml file. */
|
||||
fun getManifestAttrTable(): ResourceTable? {
|
||||
val platform = getPlatformDir() ?: return null
|
||||
return ResourceTableRegistry.getInstance().getManifestAttrTable(platform)
|
||||
}
|
||||
|
||||
private fun getPlatformDir() = bootClassPaths.firstOrNull { it.name == "android.jar" }?.parentFile
|
||||
}
|
||||
|
||||
+4
-3
@@ -33,9 +33,10 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
|
||||
|
||||
companion object {
|
||||
const val PCK_ANDROID = "android"
|
||||
@JvmStatic val COMPLETION_MODULE_RES_LOOKUP_KEY = Lookup.Key<Set<ResourceTable>>()
|
||||
@JvmStatic val COMPLETION_DEP_RES_LOOKUP_KEY = Lookup.Key<Set<ResourceTable>>()
|
||||
@JvmStatic val COMPLETION_FRAMEWORK_RES_LOOKUP_KEY = Lookup.Key<ResourceTable>()
|
||||
@JvmStatic val COMPLETION_MODULE_RES = Lookup.Key<Set<ResourceTable>>()
|
||||
@JvmStatic val COMPLETION_DEP_RES = Lookup.Key<Set<ResourceTable>>()
|
||||
@JvmStatic val COMPLETION_FRAMEWORK_RES = Lookup.Key<ResourceTable>()
|
||||
@JvmStatic val COMPLETION_MANIFEST_ATTR_RES = Lookup.Key<ResourceTable>()
|
||||
|
||||
@JvmStatic fun getInstance(): ResourceTableRegistry = DefaultResourceTableRegistry
|
||||
}
|
||||
|
||||
+29
-2
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.xml.providers
|
||||
|
||||
import com.android.SdkConstants.ANDROID_MANIFEST_XML
|
||||
import com.android.aaptcompiler.AaptResourceType.LAYOUT
|
||||
import com.android.aaptcompiler.ResourcePathData
|
||||
import com.android.aaptcompiler.extractPathData
|
||||
@@ -27,9 +28,11 @@ 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.layout.LayoutAttributeCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.common.AttrValueCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.layout.LayoutAttributeCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.layout.LayoutTagCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.manifest.ManifestCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.manifest.ManifestTagCompletionProvider
|
||||
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
|
||||
@@ -84,7 +87,7 @@ class XmlCompletionProvider(settings: IServerSettings) :
|
||||
if (prefix.isBlank() && type != ATTRIBUTE_VALUE) {
|
||||
return EMPTY
|
||||
}
|
||||
|
||||
|
||||
val pathData = extractPathData(params.file.toFile())
|
||||
|
||||
val completer =
|
||||
@@ -120,6 +123,30 @@ class XmlCompletionProvider(settings: IServerSettings) :
|
||||
private fun getCompleter(pathData: ResourcePathData, type: NodeType): IXmlCompletionProvider? {
|
||||
return when (pathData.type) {
|
||||
LAYOUT -> createLayoutCompleter(type)
|
||||
null -> createNullTypeCompleter(pathData, type)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNullTypeCompleter(
|
||||
pathData: ResourcePathData,
|
||||
type: NodeType
|
||||
): IXmlCompletionProvider? {
|
||||
|
||||
// In test cases
|
||||
if (ManifestCompletionProvider.canComplete(pathData, type)) {
|
||||
return createManifestCompleter(type)
|
||||
}
|
||||
|
||||
return when (pathData.file.name) {
|
||||
ANDROID_MANIFEST_XML -> createManifestCompleter(type)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createManifestCompleter(type: NodeType): IXmlCompletionProvider? {
|
||||
return when (type) {
|
||||
TAG -> ManifestTagCompletionProvider(this)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -229,7 +229,7 @@ abstract class IXmlCompletionProvider(private val provider: ICompletionProvider)
|
||||
|
||||
if (pck == ResourceTableRegistry.PCK_ANDROID) {
|
||||
val platformResTable =
|
||||
Lookup.DEFAULT.lookup(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES_LOOKUP_KEY)
|
||||
Lookup.DEFAULT.lookup(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES)
|
||||
?: run {
|
||||
log.debug("No platform resource table is set")
|
||||
return emptySet()
|
||||
@@ -251,9 +251,9 @@ abstract class IXmlCompletionProvider(private val provider: ICompletionProvider)
|
||||
protected open fun findAllModuleResourceTables(): Set<ResourceTable> {
|
||||
val lookup = Lookup.DEFAULT
|
||||
val sourceResTables =
|
||||
lookup.lookup(ResourceTableRegistry.COMPLETION_MODULE_RES_LOOKUP_KEY) ?: emptySet()
|
||||
lookup.lookup(ResourceTableRegistry.COMPLETION_MODULE_RES) ?: emptySet()
|
||||
val depResTables =
|
||||
lookup.lookup(ResourceTableRegistry.COMPLETION_DEP_RES_LOOKUP_KEY) ?: emptySet()
|
||||
lookup.lookup(ResourceTableRegistry.COMPLETION_DEP_RES) ?: emptySet()
|
||||
return mutableSetOf<ResourceTable>().also {
|
||||
it.addAll(sourceResTables)
|
||||
it.addAll(depResTables)
|
||||
|
||||
+1
-2
@@ -24,14 +24,13 @@ import com.itsaky.androidide.lsp.models.CompletionResult
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel
|
||||
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.IXmlCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.TagCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.TAG
|
||||
import kotlin.math.max
|
||||
import org.eclipse.lemminx.dom.DOMDocument
|
||||
|
||||
/**
|
||||
* [TagCompletionProvider] implementation for providing completing tags in an XML layout file.
|
||||
* [LayoutCompletionProvider] implementation for providing completing tags in an XML layout file.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
|
||||
+18
-5
@@ -15,21 +15,34 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.xml.providers.completion
|
||||
package com.itsaky.androidide.lsp.xml.providers.completion.manifest
|
||||
|
||||
import com.android.SdkConstants
|
||||
import com.android.aaptcompiler.ResourcePathData
|
||||
import com.itsaky.androidide.lsp.api.ICompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.providers.completion.IXmlCompletionProvider
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.TAG
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
|
||||
/**
|
||||
* Base class for provider which provide XML tag completions.
|
||||
* Base class for completers which provide completions in Android manifest.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class TagCompletionProvider(provider: ICompletionProvider) : IXmlCompletionProvider(provider) {
|
||||
abstract class ManifestCompletionProvider(provider: ICompletionProvider) :
|
||||
IXmlCompletionProvider(provider) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun canComplete(pathData: ResourcePathData, type: NodeType) : Boolean {
|
||||
return pathData.file.name == SdkConstants.ANDROID_MANIFEST_XML ||
|
||||
(VMUtils.isJvm() &&
|
||||
pathData.file.name.startsWith("Manifest") &&
|
||||
pathData.file.name.endsWith("_template.xml"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun canProvideCompletions(pathData: ResourcePathData, type: NodeType): Boolean {
|
||||
return super.canProvideCompletions(pathData, type) && type == TAG
|
||||
return super.canProvideCompletions(pathData, type) && canComplete(pathData, type)
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.lsp.xml.providers.completion.manifest
|
||||
|
||||
import com.android.aaptcompiler.AaptResourceType.STYLEABLE
|
||||
import com.android.aaptcompiler.ResourcePathData
|
||||
import com.itsaky.androidide.aapt.findEntries
|
||||
import com.itsaky.androidide.lookup.Lookup
|
||||
import com.itsaky.androidide.lsp.api.ICompletionProvider
|
||||
import com.itsaky.androidide.lsp.models.CompletionItem
|
||||
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.models.MatchLevel.NO_MATCH
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
|
||||
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType.TAG
|
||||
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
|
||||
import org.eclipse.lemminx.dom.DOMDocument
|
||||
|
||||
/**
|
||||
* Provides tag completion in AndroidManifest.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ManifestTagCompletionProvider(provider: ICompletionProvider) :
|
||||
ManifestCompletionProvider(provider) {
|
||||
|
||||
companion object {
|
||||
private const val MANIFEST_TAG_PREFIX = "AndroidManifest"
|
||||
}
|
||||
|
||||
override fun canProvideCompletions(pathData: ResourcePathData, type: NodeType): Boolean {
|
||||
return super.canProvideCompletions(pathData, type) && type == TAG
|
||||
}
|
||||
|
||||
override fun doComplete(
|
||||
params: CompletionParams,
|
||||
pathData: ResourcePathData,
|
||||
document: DOMDocument,
|
||||
type: NodeType,
|
||||
prefix: String
|
||||
): CompletionResult {
|
||||
val newPrefix =
|
||||
if (prefix.startsWith("<")) {
|
||||
prefix.substring(1)
|
||||
} else {
|
||||
prefix
|
||||
}
|
||||
|
||||
val styleables =
|
||||
Lookup.DEFAULT.lookup(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES)
|
||||
?.findPackage(ResourceTableRegistry.PCK_ANDROID)
|
||||
?.findGroup(STYLEABLE)
|
||||
?: return EMPTY
|
||||
|
||||
val result = mutableListOf<CompletionItem>()
|
||||
|
||||
styleables
|
||||
.findEntries { it.startsWith(MANIFEST_TAG_PREFIX) }
|
||||
.map { transformTagName(it.name) }
|
||||
.forEach {
|
||||
val match = matchLevel(it, newPrefix)
|
||||
if (match == NO_MATCH) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
result.add(createTagCompletionItem(it, it, match))
|
||||
}
|
||||
|
||||
return CompletionResult(result)
|
||||
}
|
||||
|
||||
private fun transformTagName(tag: String): String {
|
||||
val name = StringBuilder()
|
||||
var index = MANIFEST_TAG_PREFIX.length
|
||||
while (index < tag.length) {
|
||||
var c = tag[index]
|
||||
if (c.isUpperCase()) {
|
||||
if (index != MANIFEST_TAG_PREFIX.length) {
|
||||
name.append('-')
|
||||
}
|
||||
c = c.lowercaseChar()
|
||||
}
|
||||
|
||||
name.append(c)
|
||||
++index
|
||||
}
|
||||
|
||||
return name.toString()
|
||||
}
|
||||
}
|
||||
@@ -55,17 +55,22 @@ object XMLLSPTest : LSPTest() {
|
||||
|
||||
val frameworkResources = module.getFrameworkResourceTable()
|
||||
if (frameworkResources != null) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES_LOOKUP_KEY, frameworkResources)
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_FRAMEWORK_RES, frameworkResources)
|
||||
}
|
||||
|
||||
val moduleResources = module.getSourceResourceTables()
|
||||
if (moduleResources.isNotEmpty()) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_MODULE_RES_LOOKUP_KEY, moduleResources)
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_MODULE_RES, moduleResources)
|
||||
}
|
||||
|
||||
val depResTables = module.getDependencyResourceTables()
|
||||
if (depResTables.isNotEmpty()) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_DEP_RES_LOOKUP_KEY, depResTables)
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_DEP_RES, depResTables)
|
||||
}
|
||||
|
||||
val manifestAttrTable = module.getManifestAttrTable()
|
||||
if (manifestAttrTable != null) {
|
||||
lookup.update(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES, manifestAttrTable)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
throw RuntimeException(e)
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 ManifestTagCompletionProviderTest : CompletionHelper by CompletionHelperImpl() {
|
||||
|
||||
@Before
|
||||
fun setup () = XMLLSPTest.initProjectIfNeeded()
|
||||
|
||||
@Test // prefix: 'perm'
|
||||
fun `simple tag completion test with prefix 'perm'` () {
|
||||
XMLLSPTest.apply {
|
||||
openFile("completion/ManifestPermTagTest")
|
||||
val (isIncomplete, items) = complete()
|
||||
assertThat(isIncomplete).isFalse()
|
||||
assertThat(items).containsAtLeast("permission", "permission-group", "permission-tree")
|
||||
}
|
||||
}
|
||||
|
||||
@Test // prefix: 'uses'
|
||||
fun `simple tag completion test with prefix 'uses'` () {
|
||||
XMLLSPTest.apply {
|
||||
openFile("completion/ManifestUsesTagTest")
|
||||
val (isIncomplete, items) = complete()
|
||||
assertThat(isIncomplete).isFalse()
|
||||
assertThat(items).containsAtLeast("uses-permission", "uses-sdk", "uses-configuration", "uses-feature")
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.itsaky.test.app">
|
||||
|
||||
<perm@@cursor@@>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.itsaky.test.app">
|
||||
|
||||
<uses@@cursor@@>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
Reference in New Issue
Block a user