Namespace based completion in attribute value completion provider

This commit is contained in:
Akash Yadav
2022-08-22 23:13:58 +05:30
parent 02478a7e4b
commit 4f9eaa0978
5 changed files with 95 additions and 46 deletions
@@ -43,8 +43,8 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
*
* @param name The package name for the resource table.
*/
fun forPackage(name: String, vararg dirs: File): ResourceTable
fun forPackage(name: String, vararg dirs: File): ResourceTable?
/**
* Remove the resource table entry for the given package name.
*
@@ -42,31 +42,32 @@ internal object DefaultResourceTableRegistry : ResourceTableRegistry {
private val tables = ConcurrentHashMap<String, ResourceTable>()
private val platformTables = ConcurrentHashMap<String, ResourceTable>()
override fun forPackage(name: String, vararg dirs: File): ResourceTable {
override fun forPackage(name: String, vararg dirs: File): ResourceTable? {
if (name == PCK_ANDROID) {
return platformResourceTable(dirs.iterator().next())
}
return tables[name]
?: createTable(*dirs).also {
?: createTable(*dirs)?.also {
tables[name] = it
it.packages.firstOrNull()?.name = name
}
}
private fun platformResourceTable(dir: File): ResourceTable {
var table = platformTables[dir.path]
if (table != null) {
return table
}
table = createTable(dir)
platformTables[dir.path] = table
return table
private fun platformResourceTable(dir: File): ResourceTable? {
return platformTables[dir.path]
?: createTable(dir)?.also {
platformTables[dir.path] = it
it.packages.firstOrNull()?.name = PCK_ANDROID
}
}
private fun createTable(vararg resDirs: File): ResourceTable {
private fun createTable(vararg resDirs: File): ResourceTable? {
if (resDirs.isEmpty()) {
return null
}
log.info("Creating resource table for resource directories $resDirs")
val logger = BlameLogger(IDELogger)
@@ -41,7 +41,8 @@ import org.eclipse.lemminx.dom.DOMDocument
abstract class IXmlCompletionProvider {
companion object {
const val NAMESPACE_PREFIX = "http://schemas.android.com/apk/res"
const val NAMESPACE_PREFIX = "http://schemas.android.com/apk/res/"
const val NAMESPACE_AUTO = "http://schemas.android.com/apk/res-auto"
}
/**
@@ -24,14 +24,16 @@ import com.android.aapt.Resources.Attribute.FormatFlags.DIMENSION
import com.android.aapt.Resources.Attribute.FormatFlags.ENUM
import com.android.aapt.Resources.Attribute.FormatFlags.FLAGS
import com.android.aapt.Resources.Attribute.FormatFlags.INTEGER
import com.android.aapt.Resources.Attribute.FormatFlags.REFERENCE_VALUE
import com.android.aapt.Resources.Attribute.FormatFlags.STRING
import com.android.aaptcompiler.AaptResourceType
import com.android.aaptcompiler.AaptResourceType.ATTR
import com.android.aaptcompiler.AaptResourceType.BOOL
import com.android.aaptcompiler.AaptResourceType.DIMEN
import com.android.aaptcompiler.AaptResourceType.UNKNOWN
import com.android.aaptcompiler.AttributeResource
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.ResourcePathData
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.aapt.findEntries
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.models.CompletionItem
@@ -44,6 +46,7 @@ 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 com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.resources.ResourceTableRegistry.Companion.COMPLETION_FRAMEWORK_RES_LOOKUP_KEY
import org.eclipse.lemminx.dom.DOMDocument
@@ -75,60 +78,73 @@ class AttrValueCompletionProvider : IXmlCompletionProvider() {
return EMPTY
}
val attrs =
Lookup.DEFAULT.lookup(COMPLETION_FRAMEWORK_RES_LOOKUP_KEY)
?.findPackage("android")
?.findGroup(ATTR)
val namespace =
attr.namespaceURI
?: run {
log.warn("Cannot get attributes list from resource table")
log.warn("Unknown namespace for attribute", attr)
return EMPTY
}
val entry =
attrs.findEntry(attrName)?.findValue(ConfigDescription())?.value
?: run {
log.warn("Cannot find entry for attribute: $attrName")
return EMPTY
}
if (entry !is AttributeResource) {
val tables = findResourceTables(namespace)
if (tables.isEmpty()) {
return EMPTY
}
val list = mutableListOf<CompletionItem>()
val pck = namespace.substringAfter(NAMESPACE_PREFIX)
if (entry.typeMask == REFERENCE_VALUE) {
for (table in tables) {
val attrs = table.findPackage(pck)?.findGroup(ATTR) ?: continue
val entry = attrs.findEntry(attrName)?.findValue(ConfigDescription())?.value ?: continue
if (entry !is AttributeResource) {
continue
}
addFromTable(table, entry, pck, prefix, list)
}
return CompletionResult(list)
}
private fun addFromTable(
table: ResourceTable,
entry: AttributeResource,
pck: String,
prefix: String,
list: MutableList<CompletionItem>
) {
if (entry.typeMask == FormatFlags.REFERENCE_VALUE) {
for (value in AaptResourceType.values()) {
if (value == UNKNOWN) {
continue
}
addValues("android", value, prefix, list)
addValues(table, pck, value, prefix, list)
}
} else {
// Check for specific attribute formats
if (entry.hasType(STRING)) {
addValues("android", type = AaptResourceType.STRING, prefix = prefix, result = list)
addValues(table, pck, type = AaptResourceType.STRING, prefix = prefix, result = list)
}
if (entry.hasType(INTEGER)) {
addValues("android", type = AaptResourceType.INTEGER, prefix = prefix, result = list)
addValues(table, pck, type = AaptResourceType.INTEGER, prefix = prefix, result = list)
}
if (entry.hasType(COLOR)) {
addValues("android", type = AaptResourceType.COLOR, prefix = prefix, result = list)
addValues(table, pck, type = AaptResourceType.COLOR, prefix = prefix, result = list)
}
if (entry.hasType(BOOLEAN)) {
addValues("android", type = AaptResourceType.BOOL, prefix = prefix, result = list)
addValues(table, pck, type = BOOL, prefix = prefix, result = list)
}
if (entry.hasType(DIMENSION)) {
addValues("android", type = AaptResourceType.DIMEN, prefix = prefix, result = list)
addValues(table, pck, type = DIMEN, prefix = prefix, result = list)
}
if (entry.hasType(INTEGER)) {
addValues("android", type = AaptResourceType.INTEGER, prefix = prefix, result = list)
addValues(table, pck, type = AaptResourceType.INTEGER, prefix = prefix, result = list)
}
if (entry.hasType(ENUM) || entry.hasType(FLAGS)) {
@@ -139,32 +155,63 @@ class AttrValueCompletionProvider : IXmlCompletionProvider() {
}
list.add(
createEnumOrFlagCompletionItem(
pck = "android",
name = symbol.symbol.name.entry!!,
matchLevel
)
createEnumOrFlagCompletionItem(pck = pck, name = symbol.symbol.name.entry!!, matchLevel)
)
}
}
}
}
return CompletionResult(list)
private fun findResourceTables(nsUri: String): Set<ResourceTable> {
if (nsUri == NAMESPACE_AUTO) {
return findAllModuleResourceTables()
}
val pck = nsUri.substringAfter(NAMESPACE_PREFIX)
if (pck.isBlank()) {
log.warn("Invalid namespace: $nsUri")
return emptySet()
}
if (pck == ResourceTableRegistry.PCK_ANDROID) {
val platformResTable =
Lookup.DEFAULT.lookup(COMPLETION_FRAMEWORK_RES_LOOKUP_KEY)
?: run {
log.debug("No platform resource table is set")
return emptySet()
}
return setOf(platformResTable)
}
val table =
ResourceTableRegistry.getInstance().forPackage(pck)
?: run {
log.error("Cannot find resource table for package: $pck")
return emptySet()
}
return setOf(table)
}
private fun findAllModuleResourceTables(): Set<ResourceTable> {
// TODO find all resource tables from completing file's module
return emptySet()
}
private fun addValues(
resources: ResourceTable,
pck: String,
type: AaptResourceType,
prefix: String,
result: MutableList<CompletionItem>
) {
val resources = Lookup.DEFAULT.lookup(COMPLETION_FRAMEWORK_RES_LOOKUP_KEY) ?: return
val colors =
val entries =
resources.findPackage(pck)?.findGroup(type)?.findEntries {
matchLevel(it, prefix) != NO_MATCH
}
?: return
colors.forEach {
entries.forEach {
result.add(
createAttrValueCompletionItem(pck, type.tagName, it.name, matchLevel(it.name, prefix))
)
Binary file not shown.