Complete attribute values in manifest files

This commit is contained in:
Akash Yadav
2022-08-28 23:53:06 +05:30
parent 1db22da182
commit 0c1b03d646
20 changed files with 720 additions and 36 deletions
@@ -358,5 +358,33 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
return ResourceTableRegistry.getInstance().getManifestAttrTable(platform)
}
/** @see ResourceTableRegistry.getActivityActions */
fun getActivityActions(): List<String> {
return ResourceTableRegistry.getInstance()
.getActivityActions(getPlatformDir() ?: return emptyList())
}
/** @see ResourceTableRegistry.getBroadcastActions */
fun getBroadcastActions(): List<String> {
return ResourceTableRegistry.getInstance()
.getBroadcastActions(getPlatformDir() ?: return emptyList())
}
/** @see ResourceTableRegistry.getServiceActions */
fun getServiceActions(): List<String> {
return ResourceTableRegistry.getInstance()
.getServiceActions(getPlatformDir() ?: return emptyList())
}
/** @see ResourceTableRegistry.getCategories */
fun getCategories(): List<String> {
return ResourceTableRegistry.getInstance().getCategories(getPlatformDir() ?: return emptyList())
}
/** @see ResourceTableRegistry.getFeatures */
fun getFeatures(): List<String> {
return ResourceTableRegistry.getInstance().getFeatures(getPlatformDir() ?: return emptyList())
}
private fun getPlatformDir() = bootClassPaths.firstOrNull { it.name == "android.jar" }?.parentFile
}
@@ -62,6 +62,21 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
*/
fun getManifestAttrTable(platform: File): ResourceTable?
/** Get the list of all activity actions for the given [platform] directory. */
fun getActivityActions(platform: File): List<String>
/** Get the list of all broadcast actions for the given [platform] directory. */
fun getBroadcastActions(platform: File): List<String>
/** Get the list of all service actions for the given [platform] directory. */
fun getServiceActions(platform: File): List<String>
/** Get the list of all categories for the given [platform] directory. */
fun getCategories(platform: File): List<String>
/** Get the list of all features for the given [platform] directory. */
fun getFeatures(platform: File): List<String>
override fun forPlatformDir(platform: File): ResourceTable? {
return forPackage(PCK_ANDROID, File(platform, "data/res"))
}
@@ -17,6 +17,11 @@
package com.itsaky.androidide.xml.resources.internal
import com.android.SdkConstants
import com.android.SdkConstants.FN_INTENT_ACTIONS_ACTIVITY
import com.android.SdkConstants.FN_INTENT_ACTIONS_BROADCAST
import com.android.SdkConstants.FN_INTENT_ACTIONS_SERVICE
import com.android.SdkConstants.FN_INTENT_CATEGORIES
import com.android.SdkConstants.OS_PLATFORM_ATTRS_MANIFEST_XML
import com.android.aaptcompiler.BlameLogger
import com.android.aaptcompiler.ResourceTable
@@ -28,6 +33,11 @@ import com.itsaky.androidide.layoutlib.resources.ResourceVisibility.PUBLIC
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.resources.ResourceTableRegistry.Companion.PCK_ANDROID
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry.SingleLineValueEntryType.ACTIVITY_ACTIONS
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry.SingleLineValueEntryType.BROADCAST_ACTIONS
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry.SingleLineValueEntryType.CATEGORIES
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry.SingleLineValueEntryType.FEATURES
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry.SingleLineValueEntryType.SERVICE_ACTIONS
import java.io.File
import java.util.concurrent.ConcurrentHashMap
@@ -38,11 +48,26 @@ import java.util.concurrent.ConcurrentHashMap
*/
internal object DefaultResourceTableRegistry : ResourceTableRegistry {
/**
* Represents the type of single line entries read from files.
*
* @property filename The filename which contains the single-line entries.
*/
internal enum class SingleLineValueEntryType(val filename: String) {
ACTIVITY_ACTIONS(FN_INTENT_ACTIONS_ACTIVITY),
BROADCAST_ACTIONS(FN_INTENT_ACTIONS_BROADCAST),
SERVICE_ACTIONS(FN_INTENT_ACTIONS_SERVICE),
CATEGORIES(FN_INTENT_CATEGORIES),
FEATURES("features.txt")
}
private val log = ILogger.newInstance(javaClass.simpleName)
private val tables = ConcurrentHashMap<String, ResourceTable>()
private val platformTables = ConcurrentHashMap<String, ResourceTable>()
private val manifestAttrs = ConcurrentHashMap<String, ResourceTable>()
private val singleLineValueEntries =
ConcurrentHashMap<String, ConcurrentHashMap<SingleLineValueEntryType, List<String>>>()
override fun forPackage(name: String, vararg dirs: File): ResourceTable? {
@@ -59,6 +84,11 @@ internal object DefaultResourceTableRegistry : ResourceTableRegistry {
override fun forPlatformDir(platform: File): ResourceTable? {
getManifestAttrTable(platform)
getActivityActions(platform)
getBroadcastActions(platform)
getServiceActions(platform)
getCategories(platform)
getFeatures(platform)
return super.forPlatformDir(platform)
}
@@ -70,6 +100,70 @@ internal object DefaultResourceTableRegistry : ResourceTableRegistry {
}
}
override fun getActivityActions(platform: File): List<String> {
return getSingleLineEntry(platform, ACTIVITY_ACTIONS)
}
override fun getBroadcastActions(platform: File): List<String> {
return getSingleLineEntry(platform, BROADCAST_ACTIONS)
}
override fun getServiceActions(platform: File): List<String> {
return getSingleLineEntry(platform, SERVICE_ACTIONS)
}
override fun getCategories(platform: File): List<String> {
return getSingleLineEntry(platform, CATEGORIES)
}
override fun getFeatures(platform: File): List<String> {
return getSingleLineEntry(platform, FEATURES)
}
override fun removeTable(packageName: String) {
tables.remove(packageName)
}
override fun clear() {
tables.clear()
}
private fun getSingleLineEntry(platform: File, type: SingleLineValueEntryType): List<String> {
var entries = singleLineValueEntries[platform.path]
if (entries == null) {
entries = readSingleLineEntry(platform, type)
singleLineValueEntries[platform.path] = entries
}
return entries[type]
?: run {
readSingleLineEntriesTo(platform, type, entries)
entries[type] ?: emptyList()
}
}
private fun readSingleLineEntry(
platform: File,
type: SingleLineValueEntryType
): ConcurrentHashMap<SingleLineValueEntryType, List<String>> {
val map = ConcurrentHashMap<SingleLineValueEntryType, List<String>>()
readSingleLineEntriesTo(platform, type, map)
return map
}
private fun readSingleLineEntriesTo(
platform: File,
type: SingleLineValueEntryType,
map: ConcurrentHashMap<SingleLineValueEntryType, List<String>>
) {
val file = File(platform, "${SdkConstants.FD_DATA}/${type.filename}")
if (!file.exists() || !file.canRead()) {
return
}
map[type] = file.readLines()
}
private fun createManifestAttrTable(platform: File): ResourceTable? {
val attrs = File(platform, OS_PLATFORM_ATTRS_MANIFEST_XML)
if (!attrs.exists()) {
@@ -120,10 +214,6 @@ internal object DefaultResourceTableRegistry : ResourceTableRegistry {
return options
}
override fun removeTable(packageName: String) {
tables.remove(packageName)
}
private fun updateFromDirectory(
directory: File,
table: ResourceTable,
@@ -184,8 +274,4 @@ internal object DefaultResourceTableRegistry : ResourceTableRegistry {
}
}
}
override fun clear() {
tables.clear()
}
}
@@ -29,10 +29,10 @@ import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.xml.findAndroidJar
import com.itsaky.androidide.xml.resources.ResourceTableRegistry.Companion.PCK_ANDROID
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry
import java.io.File
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.io.File
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@@ -47,6 +47,7 @@ class ResourceTableRegistryTest {
@Test
fun `test with simple framework resource parsing`() {
val androidJar = findAndroidJar()
val platform = androidJar.parentFile!!
val registry = ResourceTableRegistry.getInstance()
val resDir = File(androidJar.parentFile, "data/res")
val resourceTable = registry.forPackage(PCK_ANDROID, resDir)
@@ -57,7 +58,7 @@ class ResourceTableRegistryTest {
assertThat(registry.forPackage(PCK_ANDROID, resDir)).isEqualTo(resourceTable)
// It should also create the manifest attrs table by default
registry.getManifestAttrTable(platform = androidJar.parentFile!!).apply {
registry.getManifestAttrTable(platform = platform).apply {
assertThat(this).isNotNull()
assertThat(this!!.packages).hasSize(1)
assertThat(this.packages.first().name).isEqualTo(PCK_ANDROID)
@@ -65,6 +66,89 @@ class ResourceTableRegistryTest {
assertThat(findPackage(PCK_ANDROID)!!.findGroup(STYLEABLE)).isNotNull()
}
registry.getActivityActions(platform).apply {
assertThat(this).isNotEmpty()
assertThat(this)
.containsAtLeastElementsIn(
arrayOf(
"android.app.action.SET_NEW_PASSWORD",
"android.app.action.START_ENCRYPTION",
"android.intent.action.CARRIER_SETUP",
"android.intent.action.CHOOSER",
"android.intent.action.MAIN",
"android.intent.action.MANAGE_APP_PERMISSION",
"android.intent.action.MANAGE_APP_PERMISSIONS"
)
)
assertThat(this.size).isGreaterThan(250) // actual size is 300+
}
registry.getBroadcastActions(platform).apply {
assertThat(this).isNotEmpty()
assertThat(this)
.containsAtLeastElementsIn(
arrayOf(
"android.telephony.action.SUBSCRIPTION_PLANS_CHANGED",
"android.telephony.action.SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED",
"android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE",
"android.telephony.euicc.action.OTA_STATUS_CHANGED",
"android.app.action.USER_ADDED",
"android.app.action.USER_REMOVED"
)
)
}
registry.getServiceActions(platform).apply {
assertThat(this).isNotEmpty()
assertThat(this)
.containsAtLeastElementsIn(
arrayOf(
"android.telephony.data.DataService",
"android.view.InputMethod",
"android.intent.action.TTS_SERVICE",
"android.media.MediaRoute2ProviderService",
"android.service.voice.VoiceInteractionService",
"android.service.vr.VrListenerService"
)
)
}
registry.getCategories(platform).apply {
assertThat(this).isNotEmpty()
assertThat(this)
.containsAtLeastElementsIn(
arrayOf(
"android.intent.category.HOME",
"android.intent.category.HOME_MAIN",
"android.intent.category.INFO",
"android.intent.category.LAUNCHER",
"android.intent.category.LAUNCHER_APP",
"android.net.category.EVENT_IKE_ERROR",
"android.net.category.EVENT_NETWORK_ERROR",
"android.service.quicksettings.action.QS_TILE_PREFERENCES"
)
)
}
registry.getFeatures(platform).apply {
assertThat(this).isNotEmpty()
assertThat(this)
.containsAtLeastElementsIn(
arrayOf(
"android.software.vulkan.deqp.level",
"android.software.webview",
"android.software.window_magnification",
"android.sofware.nfc.beam",
"android.hardware.strongbox_keystore",
"android.hardware.telephony",
"android.hardware.telephony.calling",
"android.hardware.telephony.carrierlock",
"android.hardware.ethernet",
"android.hardware.faketouch"
)
)
}
resourceTable.apply {
assertThat(this).isNotNull()
assertThat(this!!.packages).hasSize(1)
@@ -32,6 +32,7 @@ import com.itsaky.androidide.lsp.xml.providers.completion.common.AttrValueComple
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.manifest.canCompleteManifest
import com.itsaky.androidide.lsp.xml.utils.XmlUtils
@@ -149,6 +150,7 @@ class XmlCompletionProvider(settings: IServerSettings) :
return when (type) {
TAG -> ManifestTagCompletionProvider(this)
ATTRIBUTE -> ManifestAttrCompletionProvider(this)
ATTRIBUTE_VALUE -> ManifestAttrValueCompletionProvider(this)
else -> null
}
}
@@ -45,14 +45,20 @@ import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
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.ATTRIBUTE_VALUE
import org.eclipse.lemminx.dom.DOMAttr
import org.eclipse.lemminx.dom.DOMDocument
import org.eclipse.lemminx.dom.DOMNode
/**
* Provides completions for attribute value in layout XML files.
*
* @author Akash Yadav
*/
class AttrValueCompletionProvider(provider: ICompletionProvider) : IXmlCompletionProvider(provider) {
open class AttrValueCompletionProvider(provider: ICompletionProvider) :
IXmlCompletionProvider(provider) {
protected lateinit var nodeAtCursor: DOMNode
protected lateinit var attrAtCursor: DOMAttr
override fun canProvideCompletions(pathData: ResourcePathData, type: NodeType): Boolean {
return super.canProvideCompletions(pathData, type) && type == ATTRIBUTE_VALUE
@@ -65,9 +71,10 @@ class AttrValueCompletionProvider(provider: ICompletionProvider) : IXmlCompletio
type: NodeType,
prefix: String
): CompletionResult {
val attr = document.findAttrAt(params.position.requireIndex())
this.nodeAtCursor = document.findNodeAt(params.position.requireIndex()) ?: return EMPTY
this.attrAtCursor = document.findAttrAt(params.position.requireIndex()) ?: return EMPTY
val attrName =
attr?.localName
attrAtCursor.localName
?: run {
log.warn("Cannot find attribute at index ${params.position.index}")
return EMPTY
@@ -78,9 +85,9 @@ class AttrValueCompletionProvider(provider: ICompletionProvider) : IXmlCompletio
// not for 'textColor="@@cursor"'
val namespace =
attr.namespaceURI
attrAtCursor.namespaceURI
?: run {
log.warn("Unknown namespace for attribute", attr)
log.warn("Unknown namespace for attribute", attrAtCursor)
return EMPTY
}
@@ -106,6 +113,17 @@ class AttrValueCompletionProvider(provider: ICompletionProvider) : IXmlCompletio
}
}
completeInternal(attrName, prefix, groups, list)
return CompletionResult(list)
}
protected open fun completeInternal(
attrName: String,
prefix: String,
groups: MutableSet<Triple<String, ResourceTable, ResourceGroup>>,
result: MutableList<CompletionItem>
) {
for (triple in groups) {
val pack = triple.first
val table = triple.second
@@ -115,10 +133,8 @@ class AttrValueCompletionProvider(provider: ICompletionProvider) : IXmlCompletio
continue
}
addFromTable(table, entry, pack, prefix, list)
addFromTable(table, entry, pack, prefix, result)
}
return CompletionResult(list)
}
private fun addFromTable(
@@ -65,14 +65,4 @@ open class LayoutTagCompletionProvider(val provider: ICompletionProvider) :
return SimpleTagCompleter(provider)
}
protected fun match(simpleName: String, qualifiedName: String, prefix: String): MatchLevel {
val simpleNameMatchLevel = matchLevel(simpleName, prefix)
val nameMatchLevel = matchLevel(qualifiedName, prefix)
if (simpleNameMatchLevel == NO_MATCH && nameMatchLevel == NO_MATCH) {
return NO_MATCH
}
return MatchLevel.values()[max(simpleNameMatchLevel.ordinal, nameMatchLevel.ordinal)]
}
}
@@ -23,6 +23,7 @@ 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.xml.providers.completion.match
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.utils.ClassTrie
@@ -20,13 +20,10 @@ package com.itsaky.androidide.lsp.xml.providers.completion.manifest
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.android.aaptcompiler.Styleable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ICompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.AttrCompletionProvider
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import org.eclipse.lemminx.dom.DOMNode
/**
@@ -41,11 +38,7 @@ class ManifestAttrCompletionProvider(provider: ICompletionProvider) :
return super.canProvideCompletions(pathData, type) && canCompleteManifest(pathData, type)
}
override fun findResourceTables(nsUri: String): Set<ResourceTable> {
return setOf(
Lookup.DEFAULT.lookup(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES) ?: return emptySet()
)
}
override fun findResourceTables(nsUri: String) = manifestResourceTable()
override fun findNodeStyleables(node: DOMNode, styleables: ResourceGroup): Set<Styleable> {
val name = node.nodeName
@@ -0,0 +1,140 @@
/*
* 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.SdkConstants.ANDROID_NS_NAME_PREFIX
import com.android.SdkConstants.ATTR_NAME
import com.android.SdkConstants.TAG_ACTIVITY
import com.android.SdkConstants.TAG_INTENT_FILTER
import com.android.SdkConstants.TAG_RECEIVER
import com.android.SdkConstants.TAG_SERVICE
import com.android.aaptcompiler.ResourceGroup
import com.android.aaptcompiler.ResourceTable
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.MatchLevel.NO_MATCH
import com.itsaky.androidide.lsp.xml.providers.completion.common.AttrValueCompletionProvider
import com.itsaky.androidide.lsp.xml.providers.completion.match
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
/**
* Completes attribute values in manifest file.
*
* @author Akash Yadav
*/
class ManifestAttrValueCompletionProvider(provider: ICompletionProvider) :
AttrValueCompletionProvider(provider) {
override fun completeInternal(
attrName: String,
prefix: String,
groups: MutableSet<Triple<String, ResourceTable, ResourceGroup>>,
result: MutableList<CompletionItem>
) {
if (this.attrAtCursor.nodeName == /*android:name*/ "${ANDROID_NS_NAME_PREFIX}${ATTR_NAME}") {
when (this.nodeAtCursor.nodeName) {
"action" -> completeActionName(prefix, result)
"category" -> completeCategory(prefix, result)
// "uses-permission" -> completePermission(prefix, result)
"uses-feature" -> completeFeature(prefix, result)
}
}
super.completeInternal(attrName, prefix, groups, result)
}
private fun completeActionName(prefix: String, result: MutableList<CompletionItem>) {
val parent = this.nodeAtCursor.parentNode ?: return
val parentOfParent = parent.parentNode ?: return
if (parent.nodeName == TAG_INTENT_FILTER) {
when (parentOfParent.nodeName) {
TAG_ACTIVITY -> completeActivityActions(prefix, result)
TAG_RECEIVER -> completeReceiverActions(prefix, result)
TAG_SERVICE -> completeServiceActions(prefix, result)
}
}
}
private fun completeServiceActions(prefix: String, result: MutableList<CompletionItem>) {
val module = getModule()
if (module is AndroidModule) {
addMatches(prefix, module.getServiceActions(), result)
}
}
private fun completeActivityActions(prefix: String, result: MutableList<CompletionItem>) {
val module = getModule()
if (module is AndroidModule) {
addMatches(prefix, module.getActivityActions(), result)
}
}
private fun completeReceiverActions(prefix: String, result: MutableList<CompletionItem>) {
val module = getModule()
if (module is AndroidModule) {
addMatches(prefix, module.getBroadcastActions(), result)
}
}
private fun completeCategory(prefix: String, result: MutableList<CompletionItem>) {
val mod = getModule()
if (mod is AndroidModule) {
addMatches(prefix, mod.getCategories(), result)
}
}
private fun completeFeature(prefix: String, result: MutableList<CompletionItem>) {
val mod = getModule()
if (mod is AndroidModule) {
addMatches(prefix, mod.getFeatures(), result)
}
}
private fun addMatches(
prefix: String,
entries: List<String>,
result: MutableList<CompletionItem>
) {
for (entry in entries) {
val match =
if (entry.contains('.')) {
match(entry.substringAfterLast('.'), entry, prefix)
} else {
matchLevel(entry, prefix)
}
if (match == NO_MATCH) {
continue
}
result.add(createEnumOrFlagCompletionItem(ResourceTableRegistry.PCK_ANDROID, entry, match))
}
}
private fun getModule(): ModuleProject {
return Lookup.DEFAULT.lookup(ModuleProject.COMPLETION_MODULE_KEY)
?: throw IllegalStateException("No module project provided")
}
override fun findResourceTables(nsUri: String): Set<ResourceTable> {
return manifestResourceTable()
}
}
@@ -19,8 +19,11 @@ package com.itsaky.androidide.lsp.xml.providers.completion.manifest
import com.android.SdkConstants.ANDROID_MANIFEST_XML
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.xml.utils.XmlUtils.NodeType
import com.itsaky.androidide.utils.VMUtils
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
const val MANIFEST_TAG_PREFIX = "AndroidManifest"
@@ -31,6 +34,12 @@ fun canCompleteManifest(pathData: ResourcePathData, type: NodeType): Boolean {
pathData.file.name.endsWith("_template.xml"))
}
fun manifestResourceTable(): Set<ResourceTable> {
return setOf(
Lookup.DEFAULT.lookup(ResourceTableRegistry.COMPLETION_MANIFEST_ATTR_RES) ?: return emptySet()
)
}
/**
* Transforms entry name to tag name.
*
@@ -0,0 +1,33 @@
/*
* 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.itsaky.androidide.lsp.models.CompletionItem.Companion.matchLevel
import com.itsaky.androidide.lsp.models.MatchLevel
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
import kotlin.math.min
fun match(simpleName: String, qualifiedName: String, prefix: String): MatchLevel {
val simpleNameMatchLevel = matchLevel(simpleName, prefix)
val nameMatchLevel = matchLevel(qualifiedName, prefix)
if (simpleNameMatchLevel == NO_MATCH && nameMatchLevel == NO_MATCH) {
return NO_MATCH
}
return MatchLevel.values()[min(simpleNameMatchLevel.ordinal, nameMatchLevel.ordinal)]
}
@@ -0,0 +1,158 @@
/*
* 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 ManifestAttrValueCompletionProviderTest : CompletionHelper by CompletionHelperImpl() {
@Before fun setup() = XMLLSPTest.initProjectIfNeeded()
@Test
fun `simple value completion test`() {
XMLLSPTest.apply {
openFile("completion/ManifestAttrValueTest")
val (incomplete, items) = complete()
assertThat(incomplete).isFalse()
assertThat(items)
.containsAtLeast(
"game",
"audio",
"video",
"image",
"social",
"news",
"maps",
"productivity",
"accessibility"
)
}
}
@Test // prefix: 'M'
fun `activity action value completion test`() {
XMLLSPTest.apply {
assertHasSingleLineEntries(
"ManifestActivityActionCompletionTest",
arrayOf(
"android.intent.action.MAIN",
"android.intent.action.MANAGE_APP_PERMISSION",
"android.intent.action.MANAGE_APP_PERMISSIONS"
)
)
}
}
@Test // prefix: 'M'
fun `receiver action value completion test`() {
XMLLSPTest.apply {
assertHasSingleLineEntries(
"ManifestReceiverActionCompletionTest",
arrayOf(
"android.intent.action.MANAGE_PACKAGE_STORAGE",
"android.intent.action.MASTER_CLEAR_NOTIFICATION",
"android.intent.action.MEDIA_BAD_REMOVAL",
"android.intent.action.MEDIA_BUTTON",
"android.intent.action.MEDIA_CHECKING",
"android.intent.action.MEDIA_EJECT",
"android.intent.action.MEDIA_MOUNTED",
"android.intent.action.MEDIA_NOFS",
"android.intent.action.MEDIA_REMOVED",
"android.intent.action.MEDIA_SCANNER_FINISHED",
"android.intent.action.MEDIA_SCANNER_SCAN_FILE",
"android.intent.action.MEDIA_SCANNER_STARTED",
"android.intent.action.MEDIA_SHARED",
"android.intent.action.MEDIA_UNMOUNTABLE",
"android.intent.action.MEDIA_UNMOUNTED",
"android.intent.action.MY_PACKAGE_REPLACED",
"android.intent.action.MY_PACKAGE_SUSPENDED"
)
)
}
}
@Test // prefix: 'M'
fun `service action value completion test`() {
XMLLSPTest.apply {
assertHasSingleLineEntries(
"ManifestServiceActionCompletionTest",
arrayOf(
"android.media.MediaRoute2ProviderService",
"android.media.browse.MediaBrowserService"
)
)
}
}
@Test // prefix: 'A'
fun `category value completion test`() {
XMLLSPTest.apply {
assertHasSingleLineEntries(
"ManifestCategoryCompletionTest",
arrayOf(
"android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET",
"android.intent.category.ALTERNATIVE",
"android.intent.category.APP_BROWSER",
"android.intent.category.APP_CALCULATOR",
"android.intent.category.APP_CALENDAR",
"android.intent.category.APP_CONTACTS",
"android.intent.category.APP_EMAIL",
"android.intent.category.APP_FILES",
"android.intent.category.APP_GALLERY",
"android.intent.category.APP_MAPS",
"android.intent.category.APP_MARKET",
"android.intent.category.APP_MESSAGING",
"android.intent.category.APP_MUSIC"
)
)
}
}
@Test // prefix: 'f'
fun `feature value completion test`() {
XMLLSPTest.apply {
assertHasSingleLineEntries(
"ManifestFeatureCompletionTest",
arrayOf(
"android.hardware.faketouch",
"android.hardware.fingerprint",
"android.software.file_based_encryption",
"android.software.freeform_window_management"
)
)
}
}
private fun XMLLSPTest.assertHasSingleLineEntries(file: String, expect: Array<String>) {
openFile("completion/$file")
val (incomplete, items) = complete()
assertThat(incomplete).isFalse()
assertThat(items).containsAtLeastElementsIn(expect)
}
}
Binary file not shown.
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itsaky.test.app">
<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="M@@cursor@@" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itsaky.test.app">
<application
android:appCategory="@@cursor@@"
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,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itsaky.test.app">
<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="A@@cursor@@" />
</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-feature android:name="f@@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,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itsaky.test.app">
<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">
<receiver
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="M@@cursor@@" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.itsaky.test.app">
<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">
<service
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="M@@cursor@@" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>