diff --git a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.kt b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.kt index f46f81da9..9854aa4fd 100644 --- a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.kt +++ b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.kt @@ -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 { 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) diff --git a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt index 78711be7c..e2cfb6585 100644 --- a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt +++ b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/AndroidModule.kt @@ -249,30 +249,12 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this.. val threads = mutableListOf() 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. * diff --git a/build-tools/xml-utils/build.gradle b/build-tools/xml-utils/build.gradle index afe542682..e13516acf 100644 --- a/build-tools/xml-utils/build.gradle +++ b/build-tools/xml-utils/build.gradle @@ -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 diff --git a/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/resources/ResourceTableRegistry.kt b/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/resources/ResourceTableRegistry.kt index 7753e5f4f..a80a6f248 100644 --- a/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/resources/ResourceTableRegistry.kt +++ b/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/resources/ResourceTableRegistry.kt @@ -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 { companion object { + + @JvmStatic val COMPLETION_MODULE_RES_LOOKUP_KEY = Lookup.Key>() + @JvmStatic val COMPLETION_FRAMEWORK_RES_LOOKUP_KEY = Lookup.Key() + @JvmStatic fun getInstance(): ResourceTableRegistry = DefaultResourceTableRegistry } @@ -47,7 +52,7 @@ interface ResourceTableRegistry : XmlRegistry { * @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")) } diff --git a/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt b/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt index 36d12ef3d..87e71bf65 100644 --- a/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt +++ b/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt @@ -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() + } + /** * Get the information about the class with the given name. * * @param name The fully qualified name of the class. */ - fun getClass(name: String) : ClassInfo? -} \ No newline at end of file + fun getClass(name: String): ClassInfo? +} diff --git a/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/widgets/WidgetTable.kt b/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/widgets/WidgetTable.kt index 384aef0c9..ffa6698c4 100644 --- a/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/widgets/WidgetTable.kt +++ b/build-tools/xml-utils/src/main/java/com/itsaky/androidide/xml/widgets/WidgetTable.kt @@ -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() + } + /** * Get the widget for the given fully qualified name. For example, `android.widget.TextView`. * * @return The widget or `null`. */ - fun getWidget(name: String) : Widget? -} \ No newline at end of file + fun getWidget(name: String): Widget? +} diff --git a/lookup/src/main/java/com/itsaky/androidide/lookup/Lookup.java b/lookup/src/main/java/com/itsaky/androidide/lookup/Lookup.java index d69e12af7..5edcb0926 100644 --- a/lookup/src/main/java/com/itsaky/androidide/lookup/Lookup.java +++ b/lookup/src/main/java/com/itsaky/androidide/lookup/Lookup.java @@ -39,6 +39,15 @@ public interface Lookup { */ void register(Class 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 The type of the service. + */ + void update(Class klass, T instance); + /** * Unregister the service for given class. * @@ -84,6 +93,15 @@ public interface Lookup { @Nullable T lookup(Key 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 The type of the service. + */ + void update(Key key, T instance); + /** * The key that is used to register services in the table. * diff --git a/lookup/src/main/java/com/itsaky/androidide/lookup/internal/DefaultLookup.java b/lookup/src/main/java/com/itsaky/androidide/lookup/internal/DefaultLookup.java index 411604e65..54ae70e12 100644 --- a/lookup/src/main/java/com/itsaky/androidide/lookup/internal/DefaultLookup.java +++ b/lookup/src/main/java/com/itsaky/androidide/lookup/internal/DefaultLookup.java @@ -47,6 +47,11 @@ public final class DefaultLookup implements Lookup { register(key, instance); } + @Override + public void update(final Class klass, final T instance) { + update(key(klass), instance); + } + @Override public void unregister(final Class klass) { unregister(key(klass)); @@ -58,13 +63,9 @@ public final class DefaultLookup implements Lookup { return lookup(key(klass)); } - @SuppressWarnings("unchecked") @Override public void register(final Key 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 void update(final Key key, final T instance) { + registerOrUpdate(key, instance, true); + } + + @SuppressWarnings("unchecked") + private void registerOrUpdate(final Key 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 Key key(Class 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<>(); } diff --git a/lookup/src/test/java/com/itsaky/androidide/lookup/LookupTest.kt b/lookup/src/test/java/com/itsaky/androidide/lookup/LookupTest.kt index fa92bcfc6..a92d70099 100644 --- a/lookup/src/test/java/com/itsaky/androidide/lookup/LookupTest.kt +++ b/lookup/src/test/java/com/itsaky/androidide/lookup/LookupTest.kt @@ -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() - 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)