feat: annotation based TSLanguage registry

This commit is contained in:
Akash Yadav
2023-02-26 21:52:58 +05:30
parent 75f7899451
commit 15e9483d95
12 changed files with 179 additions and 28 deletions
@@ -15,19 +15,19 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
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)
@@ -15,4 +15,4 @@
# along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
#
com.itsaky.androidide.annotations.ksp.IDESymbolProcessorProvider
com.itsaky.androidide.annotations.ksp.inflater.ViewAdapterSymbolProcessorProvider
@@ -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")
@@ -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;
+3
View File
@@ -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)
@@ -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? {
@@ -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
}
}
@JvmField val FACTORY = Factory { JsonLanguage(it) }
}
override fun getInterruptionLevel(): Int {
return INTERRUPTION_LEVEL_STRONG
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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 <T : TreeSitterLanguage> register(fileType: String, factory: TreeSitterLanguage.Factory<T>)
/**
* 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 <T : TreeSitterLanguage> getFactory(fileType: String): TreeSitterLanguage.Factory<T>
/**
* 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'")
}
@@ -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<T : TreeSitterLanguage> {
/**
* Create the instance of the [TreeSitterLanguage] implementation.
*
* @param context The current context.
*/
fun create(context: Context): T
}
}
@@ -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<TreeSitterLanguage>(type).create(context)
} catch (e: TSLanguageRegistry.NotRegisteredException) {
null
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, TreeSitterLanguage.Factory<out TreeSitterLanguage>>()
override fun <T : TreeSitterLanguage> register(
fileType: String,
factory: TreeSitterLanguage.Factory<T>
) {
val older = registry.put(fileType, factory)
if (older != null) {
registry[fileType] = older
throw TSLanguageRegistry.AlreadyRegisteredException(fileType)
}
}
@Suppress("UNCHECKED_CAST")
override fun <T : TreeSitterLanguage> getFactory(
fileType: String
): TreeSitterLanguage.Factory<T> {
return (registry[fileType] ?: throw TSLanguageRegistry.NotRegisteredException(fileType))
as TreeSitterLanguage.Factory<T>
}
override fun destroy() {
registry.clear()
}
}
@@ -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? {