Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit cdb15ce

Browse files
committed
fix(editor): improve indentation for tree sitter languages
The indentation provider for tree sitter languages has been adapted from nvim-treesitter's indentation logic.
1 parent ab101fb commit cdb15ce

File tree

13 files changed

+950
-173
lines changed

13 files changed

+950
-173
lines changed

editor-api/src/main/java/com/itsaky/androidide/treesitter/api/TreeSitterQueryMatch.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class TreeSitterQueryMatch @JvmOverloads internal constructor(
2929
id: Int = 0,
3030
patternIndex: Int = 0,
3131
captures: Array<out TSQueryCapture?>? = null
32-
) : TSQueryMatch(id, patternIndex, captures),
32+
) : TSQueryMatch(id, patternIndex, captures, null),
3333
RecyclableObjectPool.Recyclable by DefaultRecyclable() {
3434

3535
companion object {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* This file is part of AndroidIDE.
3+
*
4+
* AndroidIDE is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* AndroidIDE is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.itsaky.androidide.treesitter
19+
20+
/**
21+
* Get the first node at the line and column.
22+
*
23+
* @param line The 0-based line number.
24+
* @param colBytes The 0-based column number in bytes.
25+
*/
26+
fun TSNode.getNodeAt(line: Int, colBytes: Int): TSNode? {
27+
return getDescendantForPointRange(TSPoint.create(line, colBytes),
28+
TSPoint.create(line, colBytes + 1))
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* This file is part of AndroidIDE.
3+
*
4+
* AndroidIDE is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* AndroidIDE is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.itsaky.androidide.treesitter
19+
20+
/**
21+
* The 0-based start line number.
22+
*/
23+
val TSRange.startLine: Int
24+
get() = getStartPoint().getRow()
25+
26+
/**
27+
* The 0-based end line number.
28+
*/
29+
val TSRange.endLine: Int
30+
get() = getEndPoint().getRow()

editor-treesitter/src/main/java/io/github/rosemoe/sora/editor/ts/TsAnalyzeManager.kt

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ open class TsAnalyzeManager(val languageSpec: TsLanguageSpec, var theme: TsTheme
3838

3939
open var styles = Styles()
4040

41-
private var analyzeWorker: TsAnalyzeWorker? = null
41+
private var _analyzeWorker: TsAnalyzeWorker? = null
42+
val analyzeWorker: TsAnalyzeWorker?
43+
get() = _analyzeWorker
4244

4345
open fun updateTheme(theme: TsTheme) {
4446
this.theme = theme
@@ -49,7 +51,7 @@ open class TsAnalyzeManager(val languageSpec: TsLanguageSpec, var theme: TsTheme
4951

5052
override fun setReceiver(receiver: StyleReceiver?) {
5153
stylesReceiver = receiver
52-
analyzeWorker?.stylesReceiver = receiver
54+
_analyzeWorker?.stylesReceiver = receiver
5355
}
5456

5557
override fun reset(content: ContentReference, extraArguments: Bundle) {
@@ -70,7 +72,13 @@ open class TsAnalyzeManager(val languageSpec: TsLanguageSpec, var theme: TsTheme
7072
lineCount = reference!!.lineCount
7173
edit(edit)
7274
}
73-
analyzeWorker?.onMod(Mod(TextMod(start.index, end.index, edit, insertedContent.toString())))
75+
_analyzeWorker?.onMod(Mod(TextMod(
76+
start.index,
77+
end.index,
78+
edit,
79+
insertedContent.toString(),
80+
reference?.documentVersion ?: 0
81+
)))
7482
}
7583

7684
override fun delete(start: CharPosition, end: CharPosition, deletedContent: CharSequence) {
@@ -86,30 +94,36 @@ open class TsAnalyzeManager(val languageSpec: TsLanguageSpec, var theme: TsTheme
8694
lineCount = reference!!.lineCount
8795
edit(edit)
8896
}
89-
analyzeWorker?.onMod(Mod(TextMod(start.index, end.index, edit, null)))
97+
_analyzeWorker?.onMod(Mod(TextMod(
98+
start.index,
99+
end.index,
100+
edit,
101+
null,
102+
reference?.documentVersion ?: 0
103+
)))
90104
}
91105

92106
override fun rerun() {
93-
analyzeWorker?.stop()
94-
analyzeWorker = null
107+
_analyzeWorker?.stop()
108+
_analyzeWorker = null
95109

96110
(styles.spans as LineSpansGenerator?)?.tree?.close()
97111
styles.spans = null
98112
styles = Styles()
99113

100114
val initText = reference?.reference?.toString() ?: ""
101115

102-
analyzeWorker = TsAnalyzeWorker(this, languageSpec, theme, styles, reference!!, spanFactory)
103-
analyzeWorker!!.apply {
116+
_analyzeWorker = TsAnalyzeWorker(this, languageSpec, theme, styles, reference!!, spanFactory)
117+
_analyzeWorker!!.apply {
104118
this.stylesReceiver = this@TsAnalyzeManager.stylesReceiver
105-
init(Init(initText))
119+
init(Init(TextInit(initText, reference?.documentVersion ?: 0)))
106120
start()
107121
}
108122
}
109123

110124
override fun destroy() {
111-
analyzeWorker?.stop()
112-
analyzeWorker = null
125+
_analyzeWorker?.stop()
126+
_analyzeWorker = null
113127

114128
(styles.spans as LineSpansGenerator?)?.tree?.close()
115129
styles.spans = null

editor-treesitter/src/main/java/io/github/rosemoe/sora/editor/ts/TsAnalyzeWorker.kt

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@
1818
package io.github.rosemoe.sora.editor.ts
1919

2020
import com.itsaky.androidide.treesitter.TSInputEdit
21-
import com.itsaky.androidide.treesitter.TSParser
2221
import com.itsaky.androidide.treesitter.TSQueryCursor
2322
import com.itsaky.androidide.treesitter.TSTree
2423
import com.itsaky.androidide.treesitter.api.TreeSitterInputEdit
2524
import com.itsaky.androidide.treesitter.api.TreeSitterQueryCapture
2625
import com.itsaky.androidide.treesitter.api.safeExecQueryCursor
27-
import com.itsaky.androidide.treesitter.string.UTF16StringFactory
26+
import com.itsaky.androidide.treesitter.string.UTF16String
2827
import io.github.rosemoe.sora.data.ObjectAllocator
2928
import io.github.rosemoe.sora.editor.ts.spans.TsSpanFactory
3029
import io.github.rosemoe.sora.lang.analysis.StyleReceiver
@@ -46,7 +45,7 @@ import java.util.concurrent.LinkedBlockingQueue
4645
/**
4746
* @author Akash Yadav
4847
*/
49-
internal class TsAnalyzeWorker(
48+
class TsAnalyzeWorker(
5049
private val analyzer: TsAnalyzeManager,
5150
private val languageSpec: TsLanguageSpec,
5251
private val theme: TsTheme,
@@ -72,13 +71,15 @@ internal class TsAnalyzeWorker(
7271
private var isInitialized = false
7372
private var isDestroyed = false
7473

75-
private var tree: TSTree? = null
76-
private val localText = UTF16StringFactory.newString()
77-
private val parser = TSParser.create().also {
78-
it.language = languageSpec.language
79-
}
74+
val document = TsTextDocument(languageSpec.language)
75+
76+
internal val tree: TSTree?
77+
get() = document.tree
78+
79+
internal val text: UTF16String
80+
get() = document.text
8081

81-
fun init(init: Init) {
82+
internal fun init(init: Init) {
8283
if (isDestroyed) {
8384
log.warn("Received Init after TsAnalyzeWorker has been destroyed. Ignoring...")
8485
return
@@ -87,7 +88,7 @@ internal class TsAnalyzeWorker(
8788
messageChannel.offer(init)
8889
}
8990

90-
fun onMod(mod: Mod) {
91+
internal fun onMod(mod: Mod) {
9192
if (isDestroyed) {
9293
log.warn("Received Mod after TsAnalyzeWorker has been destroyed. Ignoring...")
9394
return
@@ -100,17 +101,13 @@ internal class TsAnalyzeWorker(
100101
log.debug("Stopping TsAnalyzeWorker...")
101102
isDestroyed = true
102103

103-
if (parser.isParsing) {
104-
parser.requestCancellationAndWait()
105-
}
104+
document.requestCancellationAndWaitIfParsing()
106105

107106
analyzerContext.close()
108107
messageChannel.clear()
109108
analyzerJob?.cancel(CancellationException("Requested to be stopped"))
110109
analyzerScope.cancel(CancellationException("Requested to be stopped"))
111-
localText.close()
112-
tree?.close()
113-
parser.close()
110+
document.close()
114111
}
115112

116113
fun start() {
@@ -160,16 +157,14 @@ internal class TsAnalyzeWorker(
160157
}
161158

162159
private fun doInit(init: Init) {
163-
if (parser.isParsing) {
164-
parser.requestCancellationAndWait()
165-
}
160+
document.requestCancellationAndWaitIfParsing()
166161

167162
check(!isInitialized) {
168163
"'Init' must be the first message to TsAnalyzeWorker"
169164
}
170165

171-
localText.append(init.data)
172-
tree = parser.parseString(localText)
166+
document.doInit(init.data)
167+
document.reparse()
173168
updateStyles()
174169

175170
isInitialized = true
@@ -183,45 +178,35 @@ internal class TsAnalyzeWorker(
183178

184179
val textMod = mod.data
185180
val edit = textMod.edit
186-
val newText = textMod.changedText
187181

188182
val oldTree = tree!!
189183
oldTree.edit(edit)
190184

191-
if (newText == null) {
192-
localText.deleteBytes(edit.startByte, edit.oldEndByte)
193-
} else {
194-
if (textMod.start == localText.length) {
195-
localText.append(newText)
196-
} else {
197-
localText.insert(textMod.start, newText)
198-
}
199-
}
185+
document.doMod(textMod)
200186

201187
(edit as? TreeSitterInputEdit?)?.recycle()
202188

203-
if (parser.isParsing) {
204-
parser.requestCancellationAndWait()
205-
}
189+
document.requestCancellationAndWaitIfParsing()
206190

207191
if (isDestroyed) {
208192
return
209193
}
210194

211-
tree = parser.parseString(oldTree, localText)
195+
document.reparse(oldTree)
196+
212197
oldTree.close()
213198
updateStyles()
214199
}
215200

216201
private fun updateStyles() {
217-
if (isDestroyed || messageChannel.isNotEmpty() || this.tree?.canAccess() != true) {
202+
if (isDestroyed || messageChannel.isNotEmpty() || tree?.canAccess() != true) {
218203
// analyzer stopped or
219204
// more message need to be processed
220205
return
221206
}
222207

223-
val tree = this.tree!!
224-
val scopedVariables = TsScopedVariables(tree, localText, languageSpec)
208+
val tree = tree!!
209+
val scopedVariables = TsScopedVariables(tree, text, languageSpec)
225210
val oldTree = (styles.spans as? LineSpansGenerator?)?.tree
226211
val copied = tree.copy()
227212

@@ -267,7 +252,7 @@ internal class TsAnalyzeWorker(
267252
) { match ->
268253
if (!languageSpec.blocksPredicator.doPredicate(
269254
languageSpec.predicates,
270-
localText,
255+
text,
271256
match
272257
)
273258
) {
@@ -315,13 +300,19 @@ internal interface Message<T> {
315300
val data: T
316301
}
317302

318-
internal data class Init(override val data: String) : Message<String>
303+
internal data class Init(override val data: TextInit) : Message<TextInit>
319304

320305
internal data class Mod(override val data: TextMod) : Message<TextMod>
321306

307+
internal data class TextInit(
308+
val text: String,
309+
val contentVersion: Long
310+
)
311+
322312
internal data class TextMod(
323313
val start: Int,
324314
val end: Int,
325315
val edit: TSInputEdit,
326-
val changedText: String?
316+
val changedText: String?,
317+
val contentVersion: Long
327318
)

0 commit comments

Comments
 (0)