From 15e9483d958df1350288ff1fcd5e9d02aa4da757 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Sun, 26 Feb 2023 21:52:58 +0530 Subject: [PATCH] feat: annotation based TSLanguage registry --- .../ViewAdapterSymbolProcessorProvider.kt} | 8 +-- ...ols.ksp.processing.SymbolProcessorProvider | 2 +- .../editor/EditorHandlerActivity.kt | 12 +++- .../androidide/ui/editor/CodeEditorView.java | 1 + editor/build.gradle.kts | 3 + .../editor/language/java/JavaLanguage.kt | 3 + .../editor/language/json/JsonLanguage.kt | 20 +++--- .../language/treesitter/TSLanguageRegistry.kt | 67 +++++++++++++++++++ .../language/treesitter/TreeSitterLanguage.kt | 13 +++- .../treesitter/TreeSitterLanguageProvider.kt | 16 ++--- .../internal/TSLanguageRegistryImpl.kt | 56 ++++++++++++++++ .../editor/language/xml/XMLLanguage.kt | 6 +- 12 files changed, 179 insertions(+), 28 deletions(-) rename annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/{IDESymbolProcessorProvider.kt => inflater/ViewAdapterSymbolProcessorProvider.kt} (81%) create mode 100644 editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TSLanguageRegistry.kt create mode 100644 editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/internal/TSLanguageRegistryImpl.kt diff --git a/annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/IDESymbolProcessorProvider.kt b/annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/inflater/ViewAdapterSymbolProcessorProvider.kt similarity index 81% rename from annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/IDESymbolProcessorProvider.kt rename to annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/inflater/ViewAdapterSymbolProcessorProvider.kt index 9a8e3cbcb..c29c00c05 100644 --- a/annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/IDESymbolProcessorProvider.kt +++ b/annotation-ksp/src/main/kotlin/com/itsaky/androidide/annotations/ksp/inflater/ViewAdapterSymbolProcessorProvider.kt @@ -15,19 +15,19 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.annotations.ksp +package com.itsaky.androidide.annotations.ksp.inflater import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider -import com.itsaky.androidide.annotations.ksp.inflater.ViewAdapterSymbolProcessor /** - * AndroidIDE [SymbolProcessorProvider] implementation. + * [SymbolProcessorProvider] implementation for + * [ViewAdapter][com.itsaky.androidide.annotations.inflater.ViewAdapter] annotation. * * @author Akash Yadav */ -class IDESymbolProcessorProvider : SymbolProcessorProvider { +class ViewAdapterSymbolProcessorProvider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { return ViewAdapterSymbolProcessor(environment.codeGenerator, environment.logger) diff --git a/annotation-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/annotation-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider index 5e65fdc80..004759fc8 100644 --- a/annotation-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +++ b/annotation-ksp/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -15,4 +15,4 @@ # along with AndroidIDE. If not, see . # -com.itsaky.androidide.annotations.ksp.IDESymbolProcessorProvider \ No newline at end of file +com.itsaky.androidide.annotations.ksp.inflater.ViewAdapterSymbolProcessorProvider \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt index ede934963..88e1e6449 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt @@ -31,6 +31,10 @@ import com.itsaky.androidide.R.string import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_TOOLBAR import com.itsaky.androidide.actions.ActionsRegistry.Companion.getInstance +import com.itsaky.androidide.editor.language.java.JavaLanguage +import com.itsaky.androidide.editor.language.json.JsonLanguage +import com.itsaky.androidide.editor.language.treesitter.TSLanguageRegistry +import com.itsaky.androidide.editor.language.xml.XMLLanguage import com.itsaky.androidide.editor.schemes.IDEColorSchemeProvider import com.itsaky.androidide.editor.ui.IDEEditor import com.itsaky.androidide.interfaces.IEditorHandler @@ -76,6 +80,7 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { override fun preDestroy() { super.preDestroy() + TSLanguageRegistry.instance.destroy() viewModel.removeAllFiles() } @@ -106,7 +111,12 @@ open class EditorHandlerActivity : ProjectHandlerActivity(), IEditorHandler { } } - executeAsync { IDEColorSchemeProvider.initIfNeeded() } + executeAsync { + TSLanguageRegistry.instance.register(JavaLanguage.TS_TYPE, JavaLanguage.FACTORY) + TSLanguageRegistry.instance.register(JsonLanguage.TS_TYPE, JsonLanguage.FACTORY) + TSLanguageRegistry.instance.register(XMLLanguage.TS_TYPE, XMLLanguage.FACTORY) + IDEColorSchemeProvider.initIfNeeded() + } } @SuppressLint("RestrictedApi") diff --git a/app/src/main/java/com/itsaky/androidide/ui/editor/CodeEditorView.java b/app/src/main/java/com/itsaky/androidide/ui/editor/CodeEditorView.java index ae0aa40e0..d6b88891b 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/editor/CodeEditorView.java +++ b/app/src/main/java/com/itsaky/androidide/ui/editor/CodeEditorView.java @@ -53,6 +53,7 @@ import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.SizeUtils; import com.itsaky.androidide.app.BaseApplication; import com.itsaky.androidide.editor.databinding.LayoutCodeEditorBinding; +import com.itsaky.androidide.editor.language.treesitter.TSLanguageRegistry; import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage; import com.itsaky.androidide.editor.language.cpp.CppLanguage; import com.itsaky.androidide.editor.language.groovy.GroovyLanguage; diff --git a/editor/build.gradle.kts b/editor/build.gradle.kts index 851a348cc..6f2ccfde4 100644 --- a/editor/build.gradle.kts +++ b/editor/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.library") id("kotlin-android") + id("com.google.devtools.ksp") version libs.versions.ksp } android { @@ -8,6 +9,7 @@ android { } dependencies { + ksp(projects.annotationKsp) api(libs.androidide.ts) api(libs.androidide.ts.java) @@ -28,6 +30,7 @@ dependencies { implementation(libs.google.material) implementation(projects.actions) + implementation(projects.annotations) implementation(projects.common) implementation(projects.lexers) implementation(projects.shared) diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/java/JavaLanguage.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/java/JavaLanguage.kt index a7ab0da5a..7314e1d3f 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/java/JavaLanguage.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/java/JavaLanguage.kt @@ -18,6 +18,7 @@ package com.itsaky.androidide.editor.language.java import android.content.Context +import com.itsaky.androidide.editor.language.json.JsonLanguage import com.itsaky.androidide.editor.language.utils.CommonSymbolPairs import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage import com.itsaky.androidide.editor.language.newline.TSBracketsHandler @@ -39,6 +40,8 @@ class JavaLanguage(context: Context) : companion object { const val TS_TYPE = "java" + + @JvmField val FACTORY = Factory { JavaLanguage(it) } } override fun getLanguageServer(): ILanguageServer? { diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/json/JsonLanguage.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/json/JsonLanguage.kt index e313fcf97..b2b69ce54 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/json/JsonLanguage.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/json/JsonLanguage.kt @@ -19,6 +19,7 @@ package com.itsaky.androidide.editor.language.json import android.content.Context import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage +import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage.Factory import com.itsaky.androidide.treesitter.json.TSLanguageJson /** @@ -26,12 +27,15 @@ import com.itsaky.androidide.treesitter.json.TSLanguageJson * * @author Akash Yadav */ -class JsonLanguage(context: Context) : TreeSitterLanguage(context, TSLanguageJson.newInstance(), EXT_JSON) { - companion object { - const val EXT_JSON = "json" - } +class JsonLanguage(context: Context) : + TreeSitterLanguage(context, TSLanguageJson.newInstance(), TS_TYPE) { + companion object { + const val TS_TYPE = "json" - override fun getInterruptionLevel(): Int { - return INTERRUPTION_LEVEL_STRONG - } -} \ No newline at end of file + @JvmField val FACTORY = Factory { JsonLanguage(it) } + } + + override fun getInterruptionLevel(): Int { + return INTERRUPTION_LEVEL_STRONG + } +} diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TSLanguageRegistry.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TSLanguageRegistry.kt new file mode 100644 index 000000000..9d556d16d --- /dev/null +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TSLanguageRegistry.kt @@ -0,0 +1,67 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.editor.language.treesitter + +import com.itsaky.androidide.editor.language.treesitter.internal.TSLanguageRegistryImpl + +/** + * Registry for managing [TreeSitterLanguage factories][TreeSitterLanguage.Factory]. + * + * @author Akash Yadav + */ +interface TSLanguageRegistry { + + companion object { + @JvmStatic + val instance by lazy { TSLanguageRegistryImpl() } + } + + /** + * Registers the given [factory] for the given file types. + * + * @param fileType The file extension for which the given factory should be used. + * @param factory The factory which will create the [TreeSitterLanguage] instance. + * @throws AlreadyRegisteredException If an instance of [TreeSitterLanguage.Factory] is already + * registered for the given file type. + */ + fun register(fileType: String, factory: TreeSitterLanguage.Factory) + + /** + * Returns the instance of the [TreeSitterLanguage.Factory] for the given file type. + * + * @param fileType The file type (extension) to create the language factory instance for. + * @return The [TreeSitterLanguage.Factory] implmementation. + * @throws NotRegisteredException If no [TreeSitterLanguage.Factory] is registered for the given + * file type. + */ + fun getFactory(fileType: String): TreeSitterLanguage.Factory + + /** + * Destroys the language registry, removing all the registered language factory. This must be + * called only when the application is exiting. + */ + fun destroy() + + class AlreadyRegisteredException(type: String) : + IllegalStateException( + "An instance of TreeSitterLanguage.Factory is already registered for file type '$type'" + ) + + class NotRegisteredException(type: String) : + RuntimeException("No TreeSitterLanguage.Factory registered for file type '$type'") +} diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguage.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguage.kt index bd8af3f13..cca1c3c38 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguage.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguage.kt @@ -67,7 +67,7 @@ abstract class TreeSitterLanguage(context: Context, lang: TSLanguage, type: Stri log.error("Invalid color scheme returned by color scheme provider", scheme) return@readScheme } - + val langScheme = scheme.languages[type] ?: return@readScheme langScheme.styles.forEach { tsTheme.putStyleRule(it.key, it.value.makeStyle()) } } @@ -149,4 +149,15 @@ abstract class TreeSitterLanguage(context: Context, lang: TSLanguage, type: Stri override fun destroy() { languageSpec.close() } + + /** A [Factory] creates instance of a specific [TreeSitterLanguage] implementation. */ + fun interface Factory { + + /** + * Create the instance of the [TreeSitterLanguage] implementation. + * + * @param context The current context. + */ + fun create(context: Context): T + } } diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguageProvider.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguageProvider.kt index e9bbf4fc6..5bbe7eb7c 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguageProvider.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/TreeSitterLanguageProvider.kt @@ -18,9 +18,6 @@ package com.itsaky.androidide.editor.language.treesitter import android.content.Context -import com.itsaky.androidide.editor.language.java.JavaLanguage -import com.itsaky.androidide.editor.language.json.JsonLanguage -import com.itsaky.androidide.editor.language.xml.XMLLanguage import com.itsaky.androidide.editor.schemes.LanguageSpecProvider.BASE_SPEC_PATH import java.io.File import java.io.IOException @@ -34,19 +31,18 @@ object TreeSitterLanguageProvider { fun forFile(file: File, context: Context): TreeSitterLanguage? { val type = file.extension - + try { // check if there is at least highlights.scm file for this file type context.assets.open("${BASE_SPEC_PATH}/${type}/highlights.scm").close() } catch (e: IOException) { return null } - - return when (type) { - "java" -> JavaLanguage(context) - "json" -> JsonLanguage(context) - "xml" -> XMLLanguage(context) - else -> return null + + return try { + TSLanguageRegistry.instance.getFactory(type).create(context) + } catch (e: TSLanguageRegistry.NotRegisteredException) { + null } } } diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/internal/TSLanguageRegistryImpl.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/internal/TSLanguageRegistryImpl.kt new file mode 100644 index 000000000..842c73986 --- /dev/null +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/treesitter/internal/TSLanguageRegistryImpl.kt @@ -0,0 +1,56 @@ +/* + * 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 . + */ + +package com.itsaky.androidide.editor.language.treesitter.internal + +import com.itsaky.androidide.editor.language.treesitter.TSLanguageRegistry +import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage +import java.util.concurrent.ConcurrentHashMap + +/** + * Default implementation of [TSLanguageRegistry]. + * + * @author Akash Yadav + */ +class TSLanguageRegistryImpl : TSLanguageRegistry { + + private val registry = + ConcurrentHashMap>() + + override fun register( + fileType: String, + factory: TreeSitterLanguage.Factory + ) { + val older = registry.put(fileType, factory) + if (older != null) { + registry[fileType] = older + throw TSLanguageRegistry.AlreadyRegisteredException(fileType) + } + } + + @Suppress("UNCHECKED_CAST") + override fun getFactory( + fileType: String + ): TreeSitterLanguage.Factory { + return (registry[fileType] ?: throw TSLanguageRegistry.NotRegisteredException(fileType)) + as TreeSitterLanguage.Factory + } + + override fun destroy() { + registry.clear() + } +} diff --git a/editor/src/main/java/com/itsaky/androidide/editor/language/xml/XMLLanguage.kt b/editor/src/main/java/com/itsaky/androidide/editor/language/xml/XMLLanguage.kt index 7834a7a4c..7a9f82e93 100644 --- a/editor/src/main/java/com/itsaky/androidide/editor/language/xml/XMLLanguage.kt +++ b/editor/src/main/java/com/itsaky/androidide/editor/language/xml/XMLLanguage.kt @@ -19,13 +19,11 @@ package com.itsaky.androidide.editor.language.xml import android.content.Context import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage +import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage.Factory import com.itsaky.androidide.lsp.api.ILanguageServer import com.itsaky.androidide.lsp.api.ILanguageServerRegistry import com.itsaky.androidide.lsp.xml.XMLLanguageServer import com.itsaky.androidide.treesitter.xml.TSLanguageXml -import io.github.rosemoe.sora.lang.smartEnter.NewlineHandler -import io.github.rosemoe.sora.text.ContentReference -import io.github.rosemoe.sora.text.TextUtils.countLeadingSpaceCount import io.github.rosemoe.sora.util.MyCharacter /** @@ -38,6 +36,8 @@ class XMLLanguage(context: Context) : companion object { const val TS_TYPE = "xml" + + @JvmField val FACTORY = Factory { XMLLanguage(it) } } override fun getLanguageServer(): ILanguageServer? {