fix(editor): asynchronously read and apply tree sitter languages

This commit is contained in:
Akash Yadav
2023-11-25 14:38:35 +05:30
parent fdcc86b465
commit ec1eccd6ee
8 changed files with 122 additions and 107 deletions
@@ -240,8 +240,8 @@ abstract class LogViewFragment :
IDEColorSchemeProvider.readSchemeAsync(
context = requireContext(),
type = LogLanguage.TS_TYPE,
coroutineScope = editor.editorScope
coroutineScope = editor.editorScope,
type = LogLanguage.TS_TYPE
) { scheme ->
editor.applyTreeSitterLang(LogLanguage(requireContext()), LogLanguage.TS_TYPE, scheme)
}
@@ -20,6 +20,7 @@ package com.itsaky.androidide.editor.language.treesitter
import android.content.Context
import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage.Factory
import com.itsaky.androidide.treesitter.kotlin.TSLanguageKotlin
import com.itsaky.androidide.utils.withStopWatch
import io.github.rosemoe.sora.lang.Language.INTERRUPTION_LEVEL_STRONG
/**
@@ -28,7 +29,7 @@ import io.github.rosemoe.sora.lang.Language.INTERRUPTION_LEVEL_STRONG
* @author Akash Yadav
*/
open class KotlinLanguage(context: Context) :
TreeSitterLanguage(context, TSLanguageKotlin.getInstance(), TS_TYPE_KT) {
TreeSitterLanguage(context, withStopWatch("TSLangKt.getInstance()") { TSLanguageKotlin.getInstance() }, TS_TYPE_KT) {
companion object {
@@ -42,6 +42,13 @@ interface TSLanguageRegistry {
*/
fun <T : TreeSitterLanguage> register(fileType: String, factory: TreeSitterLanguage.Factory<T>)
/**
* Checks whether a [TreeSitterLanguage] has been registered for the given [file type][fileType].
*
* @return `true` if a [TreeSitterLanguage] has been registered for [fileType], `false` otherwise.
*/
fun hasLanguage(fileType: String): Boolean
/**
* Returns the instance of the [TreeSitterLanguage.Factory] for the given file type.
*
@@ -27,7 +27,15 @@ import java.io.File
*/
object TreeSitterLanguageProvider {
fun hasTsLanguage(file: File) : Boolean {
return TSLanguageRegistry.instance.hasLanguage(file.extension)
}
fun forFile(file: File, context: Context): TreeSitterLanguage? {
if (!hasTsLanguage(file)) {
return null
}
val type = file.extension
return try {
@@ -42,6 +42,10 @@ class TSLanguageRegistryImpl : TSLanguageRegistry {
}
}
override fun hasLanguage(fileType: String): Boolean {
return registry.containsKey(fileType)
}
@Suppress("UNCHECKED_CAST")
override fun <T : TreeSitterLanguage> getFactory(
fileType: String
@@ -23,7 +23,6 @@ import com.itsaky.androidide.eventbus.events.editor.ColorSchemeInvalidatedEvent
import com.itsaky.androidide.preferences.internal.DEFAULT_COLOR_SCHEME
import com.itsaky.androidide.preferences.internal.colorScheme
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
import com.itsaky.androidide.tasks.executeAsyncProvideError
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.isSystemInDarkMode
@@ -178,8 +177,8 @@ object IDEColorSchemeProvider {
@JvmOverloads
fun readSchemeAsync(
context: Context,
type: String? = null,
coroutineScope: CoroutineScope,
type: String? = null,
callbackContext: CoroutineContext = Dispatchers.Main,
callback: (SchemeAndroidIDE?) -> Unit
) {
@@ -87,6 +87,7 @@ import io.github.rosemoe.sora.widget.component.EditorTextActionWindow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -100,13 +101,10 @@ import java.io.File
*
* @author Akash Yadav
*/
open class IDEEditor @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
private val editorFeatures: EditorFeatures = EditorFeatures()
) : CodeEditor(context, attrs, defStyleAttr, defStyleRes), IEditor by editorFeatures, ILspEditor {
open class IDEEditor @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleRes: Int = 0,
private val editorFeatures: EditorFeatures = EditorFeatures()) :
CodeEditor(context, attrs, defStyleAttr, defStyleRes), IEditor by editorFeatures, ILspEditor {
@Suppress("PropertyName")
internal var _file: File? = null
@@ -127,8 +125,7 @@ open class IDEEditor @JvmOverloads constructor(
}
diagnosticWindow.showDiagnostic(
languageClient.getDiagnosticAt(file, cursor.leftLine, cursor.leftColumn)
)
languageClient.getDiagnosticAt(file, cursor.leftLine, cursor.leftColumn))
}
/**
@@ -140,6 +137,7 @@ open class IDEEditor @JvmOverloads constructor(
protected val eventDispatcher = EditorEventDispatcher()
private var setupTsLanguageJob: Job? = null
private var sigHelpCancelChecker: ICancelChecker? = null
var languageServer: ILanguageServer? = null
@@ -163,8 +161,7 @@ open class IDEEditor @JvmOverloads constructor(
*/
val signatureHelpWindow: SignatureHelpWindow
get() {
return _signatureHelpWindow ?: SignatureHelpWindow(this)
.also { _signatureHelpWindow = it }
return _signatureHelpWindow ?: SignatureHelpWindow(this).also { _signatureHelpWindow = it }
}
/**
@@ -172,8 +169,7 @@ open class IDEEditor @JvmOverloads constructor(
*/
val diagnosticWindow: DiagnosticWindow
get() {
return _diagnosticWindow ?: DiagnosticWindow(this)
.also { _diagnosticWindow = it }
return _diagnosticWindow ?: DiagnosticWindow(this).also { _diagnosticWindow = it }
}
companion object {
@@ -186,8 +182,7 @@ open class IDEEditor @JvmOverloads constructor(
* Create input type flags for the editor.
*/
fun createInputTypeFlags(): Int {
var flags = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE or
EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
var flags = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE or EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
if (visiblePasswordFlag) {
flags = flags or EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
}
@@ -361,10 +356,8 @@ open class IDEEditor @JvmOverloads constructor(
}
// not overridable!
final override fun <T : EditorBuiltinComponent?> replaceComponent(
clazz: Class<T>,
replacement: T & Any
) {
final override fun <T : EditorBuiltinComponent?> replaceComponent(clazz: Class<T>,
replacement: T & Any) {
super.replaceComponent(clazz, replacement)
}
@@ -415,6 +408,8 @@ open class IDEEditor @JvmOverloads constructor(
EventBus.getDefault().unregister(this)
}
setupTsLanguageJob?.cancel("Editor is releasing resources.")
if (editorScope.isActive) {
editorScope.cancelIfActive("Editor is releasing resources.")
}
@@ -557,37 +552,30 @@ open class IDEEditor @JvmOverloads constructor(
if (file == null) {
return
}
val language = createLanguage(file)
val extension = file.extension
if (language is TreeSitterLanguage) {
IDEColorSchemeProvider.readSchemeAsync(
context = context,
type = extension,
coroutineScope = editorScope
) { scheme ->
applyTreeSitterLang(language, extension, scheme)
createLanguage(file) { language ->
val extension = file.extension
if (language is TreeSitterLanguage) {
IDEColorSchemeProvider.readSchemeAsync(context = context, coroutineScope = editorScope,
type = extension) { scheme ->
applyTreeSitterLang(language, extension, scheme)
}
} else {
setEditorLanguage(language)
}
} else {
setEditorLanguage(language)
}
}
/**
* Applies the given [TreeSitterLanguage] and the [color scheme][scheme] for the given [file type][type].
*/
open fun applyTreeSitterLang(
language: TreeSitterLanguage,
type: String,
scheme: SchemeAndroidIDE?
) {
open fun applyTreeSitterLang(language: TreeSitterLanguage, type: String,
scheme: SchemeAndroidIDE?) {
applyTreeSitterLangInternal(language, type, scheme)
}
private fun applyTreeSitterLangInternal(
language: TreeSitterLanguage,
type: String,
scheme: SchemeAndroidIDE?
) {
private fun applyTreeSitterLangInternal(language: TreeSitterLanguage, type: String,
scheme: SchemeAndroidIDE?) {
if (isReleased) {
return
}
@@ -616,21 +604,42 @@ open class IDEEditor @JvmOverloads constructor(
setEditorLanguage(language)
}
private fun createLanguage(file: File): Language {
private inline fun createLanguage(file: File, crossinline callback: (Language?) -> Unit) {
// 1 -> If the given File object does not represent a file, return emtpy language
if (!file.isFile) {
return EmptyLanguage()
return callback(EmptyLanguage())
}
val tsLang = TreeSitterLanguageProvider.forFile(file, context)
if (tsLang != null) {
return tsLang
// 2 -> In case a TreeSitterLanguage has been registered for this file type,
// Initialize the TreeSitterLanguage asynchronously and then invoke the callback
if (TreeSitterLanguageProvider.hasTsLanguage(file)) {
// lazily create TS languages as they need to read files from assets
setupTsLanguageJob = editorScope.launch {
callback(TreeSitterLanguageProvider.forFile(file, context))
}.also { job ->
job.invokeOnCompletion { err ->
if (err != null) {
log.error("Failed to setup tree sitter language for file: $file", err)
}
setupTsLanguageJob = null
}
}
return
}
return when (FileUtils.getFileExtension(file)) {
// 3 -> Check if we have ANTLR4 lexer-based languages for this file,
// return the language if we do, otherwise return an empty language
val lang = when (FileUtils.getFileExtension(file)) {
"gradle" -> GroovyLanguage()
"c", "h", "cc", "cpp", "cxx" -> CppLanguage()
else -> EmptyLanguage()
}
callback(lang)
}
/**
@@ -688,10 +697,8 @@ open class IDEEditor @JvmOverloads constructor(
EventBus.getDefault().register(this)
}
private inline fun launchCancellableAsyncWithProgress(
@StringRes message: Int,
crossinline action: suspend CoroutineScope.(flashbar: Flashbar, cancelChecker: ICancelChecker) -> Unit
): Job? {
private inline fun launchCancellableAsyncWithProgress(@StringRes message: Int,
crossinline action: suspend CoroutineScope.(flashbar: Flashbar, cancelChecker: ICancelChecker) -> Unit): Job? {
if (isReleased) {
return null
}
@@ -738,48 +745,45 @@ open class IDEEditor @JvmOverloads constructor(
return@withContext
}
languageClient.showDocument(
ShowDocumentParams(file1, range)
)
languageClient.showDocument(ShowDocumentParams(file1, range))
}
protected open suspend fun onFindReferencesResult(
result: ReferenceResult?
) = withContext(Dispatchers.Main) {
protected open suspend fun onFindReferencesResult(result: ReferenceResult?) =
withContext(Dispatchers.Main) {
if (isReleased) {
return@withContext
}
val languageClient = languageClient ?: run {
log.error("No language client found to handle the references result")
return@withContext
}
if (result == null) {
log.error("Invalid references result from language server (null)")
flashError(string.msg_no_references)
return@withContext
}
val locations = result.locations
if (locations.isEmpty()) {
log.error("No references found")
flashError(string.msg_no_references)
return@withContext
}
if (result.locations.size == 1) {
val (file, range) = result.locations[0]
if (DocumentUtils.isSameFile(file, getFile()!!.toPath())) {
setSelection(range)
if (isReleased) {
return@withContext
}
}
languageClient.showLocations(result.locations)
}
val languageClient = languageClient ?: run {
log.error("No language client found to handle the references result")
return@withContext
}
if (result == null) {
log.error("Invalid references result from language server (null)")
flashError(string.msg_no_references)
return@withContext
}
val locations = result.locations
if (locations.isEmpty()) {
log.error("No references found")
flashError(string.msg_no_references)
return@withContext
}
if (result.locations.size == 1) {
val (file, range) = result.locations[0]
if (DocumentUtils.isSameFile(file, getFile()!!.toPath())) {
setSelection(range)
return@withContext
}
}
languageClient.showLocations(result.locations)
}
protected open fun dispatchDocumentOpenEvent() {
if (isReleased) {
@@ -790,11 +794,7 @@ open class IDEEditor @JvmOverloads constructor(
this.fileVersion = 0
val openEvent = DocumentOpenEvent(
file.toPath(),
text.toString(),
fileVersion
)
val openEvent = DocumentOpenEvent(file.toPath(), text.toString(), fileVersion)
eventDispatcher.dispatch(openEvent)
}
@@ -820,8 +820,8 @@ open class IDEEditor @JvmOverloads constructor(
val changeRange = Range(Position(start.line, start.column, start.index),
Position(end.line, end.column, end.index))
val changedText = event.changedText.toString()
val changeEvent = DocumentChangeEvent(file, changedText, text.toString(),
++fileVersion, type, changeDelta, changeRange)
val changeEvent = DocumentChangeEvent(file, changedText, text.toString(), ++fileVersion, type,
changeDelta, changeRange)
eventDispatcher.dispatch(changeEvent)
}
@@ -874,13 +874,9 @@ open class IDEEditor @JvmOverloads constructor(
}
}
private fun configureFlashbar(
builder: Flashbar.Builder,
@StringRes message: Int,
cancelChecker: ICancelChecker
) {
builder.message(message)
.negativeActionText(android.R.string.cancel)
private fun configureFlashbar(builder: Flashbar.Builder, @StringRes message: Int,
cancelChecker: ICancelChecker) {
builder.message(message).negativeActionText(android.R.string.cancel)
.negativeActionTapListener { bar: Flashbar ->
cancelChecker.cancel()
bar.dismiss()
@@ -70,8 +70,8 @@ class ShowXmlActivity : BaseIDEActivity() {
IDEColorSchemeProvider.readSchemeAsync(
context = this,
type = XMLLanguage.TS_TYPE,
coroutineScope = activityScope
coroutineScope = activityScope,
type = XMLLanguage.TS_TYPE
) {
if (it == null) {
log.error("Unable to load color sheme")