feat: add indentation support in TreeSitterLanguage

This commit is contained in:
Akash Yadav
2023-01-04 10:33:20 +05:30
parent 4f27c31563
commit 3db99be599
8 changed files with 152 additions and 25 deletions
@@ -53,7 +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.TreeSitterLanguage;
import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage;
import com.itsaky.androidide.editor.language.cpp.CppLanguage;
import com.itsaky.androidide.editor.language.groovy.GroovyLanguage;
import com.itsaky.androidide.editor.language.java.JavaLanguage;
@@ -74,7 +74,6 @@ import com.itsaky.androidide.syntax.colorschemes.DynamicColorScheme;
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE;
import com.itsaky.androidide.utils.FileUtil;
import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.LSPUtils;
import com.itsaky.androidide.utils.TypefaceUtilsKt;
import org.greenrobot.eventbus.EventBus;
@@ -0,0 +1,31 @@
; Query for computing indents in tree-sitter language
; Capture names can be 'indent' or 'outdent'. Any other capture name will be ignored
; indent - increments indentation
; outdent - decrements indentation
[
"("
"["
"{"
] @indent
[
(class_body)
(enum_body)
(interface_body)
(constructor_declaration)
(constructor_body)
(block)
(switch_block)
(array_initializer)
(argument_list)
(formal_parameters)
] @indent
(expression_statement (method_invocation) @indent)
[
")"
"]"
"}"
] @outdent
@@ -122,7 +122,12 @@ public abstract class IDELanguage implements Language {
public int getTabSize() {
return EditorPreferencesKt.getTabSize();
}
@Override
public int getIndentAdvance(@NonNull final ContentReference content, final int line, final int column) {
return getIndentAdvance(content.getLine(line).substring(0, column));
}
public int getIndentAdvance(@NonNull String line) {
return 0;
}
@@ -20,7 +20,7 @@ package com.itsaky.androidide.editor.language.java
import android.content.Context
import com.itsaky.androidide.editor.language.BraceHandler
import com.itsaky.androidide.editor.language.CommonSymbolPairs
import com.itsaky.androidide.editor.language.TreeSitterLanguage
import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage
import com.itsaky.androidide.lsp.api.ILanguageServer
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
import com.itsaky.androidide.lsp.java.JavaLanguageServer
@@ -54,11 +54,6 @@ class JavaLanguage(context: Context) :
return INTERRUPTION_LEVEL_SLIGHT
}
override fun getIndentAdvance(content: ContentReference, line: Int, column: Int): Int {
// TODO Implement this with tree-sitter
return 0
}
override fun getSymbolPairs(): SymbolPairMatch {
return JavaSymbolPairs()
}
@@ -15,20 +15,25 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.editor.language
package com.itsaky.androidide.editor.language.treesitter
import android.content.Context
import com.itsaky.androidide.editor.language.CommonSymbolPairs
import com.itsaky.androidide.editor.language.IDELanguage
import com.itsaky.androidide.editor.schemes.IDEColorScheme
import com.itsaky.androidide.editor.schemes.IDEColorSchemeProvider
import com.itsaky.androidide.editor.schemes.LanguageSpecProvider.getLanguageSpec
import com.itsaky.androidide.editor.schemes.LocalCaptureSpecProvider.newLocalCaptureSpec
import com.itsaky.androidide.treesitter.TSLanguage
import com.itsaky.androidide.treesitter.TSParser
import com.itsaky.androidide.treesitter.TSQueryCursor
import com.itsaky.androidide.treesitter.TSQueryMatch
import com.itsaky.androidide.utils.ILogger
import io.github.rosemoe.sora.editor.ts.TsAnalyzeManager
import io.github.rosemoe.sora.editor.ts.TsLanguageSpec
import io.github.rosemoe.sora.editor.ts.TsTheme
import io.github.rosemoe.sora.lang.analysis.AnalyzeManager
import io.github.rosemoe.sora.widget.SymbolPairMatch
import kotlin.math.max
/**
* Tree Sitter language implementation.
@@ -39,38 +44,74 @@ abstract class TreeSitterLanguage(context: Context, lang: TSLanguage, type: Stri
IDELanguage() {
private lateinit var tsTheme: TsTheme
private lateinit var languageSpec: TsLanguageSpec
private val analyzer by lazy { TsAnalyzeManager(languageSpec, tsTheme) }
private lateinit var languageSpec: TreeSitterLanguageSpec
private val analyzer by lazy { TsAnalyzeManager(languageSpec.spec, tsTheme) }
private val log = ILogger.newInstance("TreeSitterLanguage")
init {
this.languageSpec = getLanguageSpec(context, type, lang, newLocalCaptureSpec(type))
this.tsTheme = TsTheme(languageSpec.tsQuery)
this.tsTheme = TsTheme(languageSpec.spec.tsQuery)
IDEColorSchemeProvider.readScheme(context) { scheme ->
if (scheme == null) {
log.error("Failed to read color scheme")
return@readScheme
}
if (scheme !is IDEColorScheme) {
log.error("Invalid color scheme returned by color scheme provider", scheme)
return@readScheme
}
val langScheme =
checkNotNull(scheme.languages[type]) { "No color scheme found for file type '$type'" }
langScheme.styles.forEach { tsTheme.putStyleRule(it.key, it.value.makeStyle()) }
}
}
fun finalizeIndent(indent: Int) : Int {
return max(0, indent) * tabSize
}
override fun getAnalyzeManager(): AnalyzeManager {
return this.analyzer
}
override fun getSymbolPairs(): SymbolPairMatch {
return CommonSymbolPairs()
}
override fun getIndentAdvance(line: String): Int {
return TSParser().use { parser ->
parser.language = languageSpec.language
val tree = parser.parseString(line)
return@use TSQueryCursor().use { cursor ->
cursor.exec(languageSpec.indentsQuery, tree.rootNode)
var indent = 0
var match: TSQueryMatch? = cursor.nextMatch()
while (match != null) {
for (capture in match.captures) {
val captureName =
languageSpec.indentsQuery.getCaptureNameForId(capture.index)
if (captureName == "indent") {
++indent
} else if (captureName == "outdent") {
--indent
}
}
match = cursor.nextMatch()
}
if (indent > 1) {
indent = 1
}
finalizeIndent(indent)
}
}
}
override fun destroy() {
languageSpec.close()
@@ -0,0 +1,58 @@
/*
* 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.treesitter.TSLanguage
import com.itsaky.androidide.treesitter.TSQuery
import com.itsaky.androidide.treesitter.TSQueryError
import io.github.rosemoe.sora.editor.ts.TsLanguageSpec
import java.io.Closeable
/**
* Extension of [TsLanguageSpec] for AndroidIDE.
*
* @author Akash Yadav
*/
class TreeSitterLanguageSpec
@JvmOverloads
constructor(val spec: TsLanguageSpec, indentsQueryScm: String = "") : Closeable {
// <editor-fold desc="">
val language: TSLanguage
get() = spec.language
// </editor-fold>
val indentsQuery = TSQuery(language, indentsQueryScm)
init {
indentsQuery.validateOrThrow(name = "indents")
}
override fun close() {
indentsQuery.close()
spec.closed
}
}
private fun TSQuery.validateOrThrow(name: String) {
if (errorType != TSQueryError.None) {
throw IllegalArgumentException(
"query(name:$name) parsing failed: ${errorType.name} at text offset $errorOffset"
)
}
}
@@ -18,7 +18,7 @@
package com.itsaky.androidide.editor.language.xml
import android.content.Context
import com.itsaky.androidide.editor.language.TreeSitterLanguage
import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguage
import com.itsaky.androidide.lsp.api.ILanguageServer
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
import com.itsaky.androidide.lsp.xml.XMLLanguageServer
@@ -51,10 +51,6 @@ class XMLLanguage(context: Context) :
return INTERRUPTION_LEVEL_STRONG
}
override fun getIndentAdvance(content: ContentReference, line: Int, column: Int): Int {
return 0
}
override fun getNewlineHandlers(): Array<NewlineHandler> {
return emptyArray()
}
@@ -18,6 +18,7 @@
package com.itsaky.androidide.editor.schemes
import android.content.Context
import com.itsaky.androidide.editor.language.treesitter.TreeSitterLanguageSpec
import com.itsaky.androidide.treesitter.TSLanguage
import com.itsaky.androidide.utils.ILogger
import io.github.rosemoe.sora.editor.ts.LocalsCaptureSpec
@@ -40,8 +41,8 @@ object LanguageSpecProvider {
type: String,
lang: TSLanguage,
localsCaptureSpec: LocalsCaptureSpec = LocalsCaptureSpec.DEFAULT
): TsLanguageSpec {
return TsLanguageSpec(
): TreeSitterLanguageSpec {
val editorLangSpec = TsLanguageSpec(
language = lang,
highlightScmSource = readScheme(context, type, "highlights"),
localsScmSource = readScheme(context, type, "locals"),
@@ -49,6 +50,7 @@ object LanguageSpecProvider {
bracketsScmSource = readScheme(context, type, "brackets"),
localsCaptureSpec = localsCaptureSpec
)
return TreeSitterLanguageSpec(spec = editorLangSpec, indentsQueryScm = readScheme(context, type, "indents"))
}
private fun readScheme(context: Context, type: String, name: String): String {