Provide proper instances of XmlRegistry to completion providers with the Lookup API

This commit is contained in:
Akash Yadav
2022-08-20 19:20:15 +05:30
parent be8c0b021a
commit ba2d8da7d7
9 changed files with 130 additions and 44 deletions
@@ -16,6 +16,7 @@
*/
package com.itsaky.androidide.language
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.api.ILanguageServer
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.models.CompletionParams
@@ -24,13 +25,18 @@ import com.itsaky.androidide.lsp.models.FailureType.COMPLETION
import com.itsaky.androidide.lsp.models.LSPFailure
import com.itsaky.androidide.models.Position
import com.itsaky.androidide.progress.ProcessCancelledException
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.versions.ApiVersions
import com.itsaky.androidide.xml.widgets.WidgetTable
import io.github.rosemoe.sora.lang.completion.CompletionCancelledException
import io.github.rosemoe.sora.lang.completion.CompletionHelper
import io.github.rosemoe.sora.text.CharPosition
import io.github.rosemoe.sora.text.ContentReference
import java.nio.file.Path
import java.util.function.*
import java.util.function.Predicate
/**
* Common implementation of completion provider which requests completions to provided language
@@ -59,6 +65,32 @@ class CommonCompletionProvider(private val server: ILanguageServer) {
): List<CompletionItem> {
val completionResult =
try {
val module = ProjectManager.findModuleForFile(file)
if (module != null && module is AndroidModule) {
val lookup = Lookup.DEFAULT
val versions = module.getApiVersions()
if (versions != null) {
lookup.update(ApiVersions.COMPLETION_LOOKUP_KEY, versions)
}
val widgets = module.getWidgetTable()
if (widgets != null) {
lookup.update(WidgetTable.COMPLETION_LOOKUP_KEY, widgets)
}
val moduleResources = module.getSourceResourceTables()
if (moduleResources.isNotEmpty()) {
lookup.update(ResourceTableRegistry.COMPLETION_MODULE_RES_LOOKUP_KEY, moduleResources)
}
val frameworkResources = module.getFrameworkResourceTable()
if (frameworkResources != null) {
lookup.update(
ResourceTableRegistry.COMPLETION_FRAMEWORK_RES_LOOKUP_KEY,
frameworkResources
)
}
}
val prefix = CompletionHelper.computePrefix(content, position) { prefixMatcher.test(it) }
val params =
CompletionParams(Position(position.line, position.column, position.index), file)
@@ -249,30 +249,12 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
val threads = mutableListOf<Thread>()
threads.add(
Thread {
val registry = ResourceTableRegistry.getInstance()
if (platformDir != null) {
registry.forPlatformDir(platformDir)
}
mainSourceSet?.sourceProvider?.resDirectories?.forEach { registry.forResourceDir(it) }
getFrameworkResourceTable()
getSourceResourceTables()
}
)
if (platformDir != null) {
threads.add(
Thread {
val registry = ApiVersionsRegistry.getInstance()
registry.forPlatformDir(platformDir)
}
)
threads.add(
Thread {
val registry = WidgetTableRegistry.getInstance()
registry.forPlatformDir(platformDir)
}
)
}
threads.add(Thread(this::getApiVersions))
threads.add(Thread(this::getWidgetTable))
for (thread in threads) {
thread.start()
@@ -310,7 +292,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
return null
}
/**
* Get the [ResourceTable] instance for this module's compile SDK.
*
@@ -321,10 +303,10 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
if (platformDir != null) {
return ResourceTableRegistry.getInstance().forPlatformDir(platformDir)
}
return null
}
/**
* Get the resource tables for this module as well as it's dependent modules.
*
+1
View File
@@ -58,6 +58,7 @@ dependencies {
implementation project(path: ':logger')
implementation project(path: ':build-tools:jdt')
api project(path: ':lookup')
api project(path: ':build-tools:aaptcompiler')
testImplementation tests.junit
@@ -18,6 +18,7 @@
package com.itsaky.androidide.xml.resources
import com.android.aaptcompiler.ResourceTable
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.xml.registry.XmlRegistry
import com.itsaky.androidide.xml.resources.internal.DefaultResourceTableRegistry
import java.io.File
@@ -30,6 +31,10 @@ import java.io.File
interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
companion object {
@JvmStatic val COMPLETION_MODULE_RES_LOOKUP_KEY = Lookup.Key<Set<ResourceTable>>()
@JvmStatic val COMPLETION_FRAMEWORK_RES_LOOKUP_KEY = Lookup.Key<ResourceTable>()
@JvmStatic fun getInstance(): ResourceTableRegistry = DefaultResourceTableRegistry
}
@@ -47,7 +52,7 @@ interface ResourceTableRegistry : XmlRegistry<ResourceTable> {
* @param dir The resource directory to remove the resource table entry for.
*/
fun removeTable(dir: File)
override fun forPlatformDir(platform: File): ResourceTable? {
return forResourceDir(File(platform, "data/res"))
}
@@ -17,17 +17,23 @@
package com.itsaky.androidide.xml.versions
import com.itsaky.androidide.lookup.Lookup
/**
* API information about classes.
*
* @author Akash Yadav
*/
interface ApiVersions {
companion object {
@JvmStatic val COMPLETION_LOOKUP_KEY = Lookup.Key<ApiVersions>()
}
/**
* Get the information about the class with the given name.
*
* @param name The fully qualified name of the class.
*/
fun getClass(name: String) : ClassInfo?
}
fun getClass(name: String): ClassInfo?
}
@@ -17,17 +17,23 @@
package com.itsaky.androidide.xml.widgets
import com.itsaky.androidide.lookup.Lookup
/**
* A widget table holds information about all widgets defined in the `widgets.txt`.
*
* @author Akash Yadav
*/
interface WidgetTable {
companion object {
@JvmStatic val COMPLETION_LOOKUP_KEY = Lookup.Key<WidgetTable>()
}
/**
* Get the widget for the given fully qualified name. For example, `android.widget.TextView`.
*
* @return The widget or `null`.
*/
fun getWidget(name: String) : Widget?
}
fun getWidget(name: String): Widget?
}
@@ -39,6 +39,15 @@ public interface Lookup {
*/
<T> void register(Class<T> klass, T instance);
/**
* Updates the service for the given class. Registers if not already registered.
*
* @param klass The class to update the service for.
* @param instance The instance of the service.
* @param <T> The type of the service.
*/
<T> void update(Class<T> klass, T instance);
/**
* Unregister the service for given class.
*
@@ -84,6 +93,15 @@ public interface Lookup {
@Nullable
<T> T lookup(Key<T> key);
/**
* Updates the service for the given key. Registers if not already registered.
*
* @param key The key to update the service for.
* @param instance The instance of the service.
* @param <T> The type of the service.
*/
<T> void update(Key<T> key, T instance);
/**
* The key that is used to register services in the table.
*
@@ -47,6 +47,11 @@ public final class DefaultLookup implements Lookup {
register(key, instance);
}
@Override
public <T> void update(final Class<T> klass, final T instance) {
update(key(klass), instance);
}
@Override
public <T> void unregister(final Class<T> klass) {
unregister(key(klass));
@@ -58,13 +63,9 @@ public final class DefaultLookup implements Lookup {
return lookup(key(klass));
}
@SuppressWarnings("unchecked")
@Override
public <T> void register(final Key<T> key, final T instance) {
final T existing = (T) services.put(key, instance);
if (existing != null) {
throw new ServiceRegisteredException();
}
registerOrUpdate(key, instance, false);
}
@Override
@@ -79,12 +80,26 @@ public final class DefaultLookup implements Lookup {
return (T) services.get(key);
}
@Override
public <T> void update(final Key<T> key, final T instance) {
registerOrUpdate(key, instance, true);
}
@SuppressWarnings("unchecked")
private <T> void registerOrUpdate(final Key<T> key, final T instance, boolean update) {
final T existing = (T) services.put(key, instance);
if (existing != null && !update) {
services.put(key, existing);
throw new ServiceRegisteredException();
}
}
@SuppressWarnings("unchecked")
@NotNull
private <T> Key<T> key(Class<T> klass) {
final var key = keyTable.get(klass);
if (key == null) {
// Returning a new key instance will always make all the methods above return null
// Returning a new key instance will always make the lookup methods above return null
return new Key<>();
}
@@ -29,9 +29,18 @@ class LookupTest {
@Test
fun `test with class`() {
val lookup = Lookup.DEFAULT
val service = TestServiceImpl()
lookup.register(TestService::class.java, service)
// Test register
var service = TestServiceImpl()
lookup.register(TestService::class.java, service)
lookup.lookup(TestService::class.java).apply {
assertThat(this).isNotNull()
assertThat(this).isEqualTo(service)
}
// Test update
service = TestServiceImpl()
lookup.update(TestService::class.java, service)
lookup.lookup(TestService::class.java).apply {
assertThat(this).isNotNull()
assertThat(this).isEqualTo(service)
@@ -46,15 +55,27 @@ class LookupTest {
fun `test with key`() {
val lookup = Lookup.DEFAULT
val key = Key<TestService>()
val service = TestServiceImpl()
lookup.register(key, service)
// Test register
var service = TestServiceImpl()
lookup.register(key, service)
lookup.lookup(key).apply {
assertThat(this).isNotNull()
assertThat(this).isEqualTo(service)
}
// Service not registered with the Class object. Must return null.
lookup.lookup(TestService::class.java).apply { assertThat(this).isNull() }
// Test update
service = TestServiceImpl()
lookup.update(key, service)
lookup.lookup(key).apply {
assertThat(this).isNotNull()
assertThat(this).isEqualTo(service)
}
// Not service with the Class object. Must return null.
// Service not registered with the Class object. Must return null.
lookup.lookup(TestService::class.java).apply { assertThat(this).isNull() }
lookup.unregister(key)