mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(editor): various improvements related to tree sitter code analysis
- Integrate the 'language-treesitter' module from sora-editor into the IDE itself for better control and customization of the tree sitter analysis. - Invalidate line span generator when tree is edited - This is required because TSTree instances are not thread-safe and the spans generator tries to query the tree after it is edited. This results in SEGV_MAPERR errors in native code because the TSNodes are invalidatd after TSTree.edit() calls and the cursor tries to query those invalidated nodes. - Recycle and reuse TS* instances - When analyzing the source code using Tree Sitter, a huge number of objects are created and deleted. This results in an increased of times the garbage collector is invoked.
This commit is contained in:
Generated
+12
@@ -147,6 +147,18 @@
|
||||
<module name="AndroidIDE.testing.tooling-api.test" target="11" />
|
||||
<module name="AndroidIDE.testing.unit.test" target="11" />
|
||||
<module name="AndroidIDE.treesitter.test" target="1.7" />
|
||||
<module name="ATS.android-tree-sitter" target="17" />
|
||||
<module name="ATS.app" target="17" />
|
||||
<module name="ATS.tree-sitter-aidl" target="17" />
|
||||
<module name="ATS.tree-sitter-java" target="17" />
|
||||
<module name="ATS.tree-sitter-json" target="17" />
|
||||
<module name="ATS.tree-sitter-kotlin" target="17" />
|
||||
<module name="ATS.tree-sitter-log" target="17" />
|
||||
<module name="ATS.tree-sitter-python" target="17" />
|
||||
<module name="ATS.tree-sitter-xml" target="17" />
|
||||
<module name="build-logic.ats" target="17" />
|
||||
<module name="build-logic.ats.main" target="17" />
|
||||
<module name="build-logic.ats.test" target="17" />
|
||||
<module name="composite-builds.plugins" target="17" />
|
||||
<module name="composite-builds.plugins.main" target="17" />
|
||||
<module name="composite-builds.plugins.test" target="17" />
|
||||
|
||||
Generated
+12
-5
@@ -1,10 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="app">
|
||||
<State />
|
||||
</entry>
|
||||
</value>
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Resizable_Experimental_API_33.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2023-10-14T04:10:20.140892617Z" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -53,6 +53,9 @@ android {
|
||||
buildTypes { release { isShrinkResources = true } }
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
keepDebugSymbols += "**.so"
|
||||
}
|
||||
resources.excludes.addAll(
|
||||
arrayOf(
|
||||
"META-INF/eclipse.inf",
|
||||
|
||||
@@ -53,6 +53,8 @@ import com.itsaky.androidide.stats.StatUploadWorker
|
||||
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
|
||||
import com.itsaky.androidide.tasks.executeAsync
|
||||
import com.itsaky.androidide.treesitter.TreeSitter
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterObjectFactory
|
||||
import com.itsaky.androidide.treesitter.util.TSObjectFactoryProvider
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
import com.itsaky.androidide.utils.VMUtils
|
||||
@@ -115,6 +117,8 @@ class IDEApplication : BaseApplication() {
|
||||
EditorColorScheme.setDefault(SchemeAndroidIDE.newInstance(null))
|
||||
|
||||
executeAsync { IDEColorSchemeProvider.init() }
|
||||
|
||||
TSObjectFactoryProvider.setFactory(TreeSitterObjectFactory())
|
||||
}
|
||||
|
||||
private fun handleCrash(thread: Thread, th: Throwable) {
|
||||
|
||||
@@ -25,6 +25,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.androidide.ts)
|
||||
api(projects.lsp.api)
|
||||
api(projects.lsp.models)
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
internal class DefaultSynchronized(
|
||||
// disabled by default
|
||||
private val isEnabled: Boolean = false
|
||||
) : TSSynchronized {
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
override fun lock(timeout: Long, unit: TimeUnit): Boolean {
|
||||
return if (isEnabled) {
|
||||
require(timeout > 0) { "Timeout must be greater than 0" }
|
||||
lock.tryLock() || lock.tryLock(timeout, unit)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun unlock() {
|
||||
if (isEnabled) {
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> withLock(timeout: Long, unit: TimeUnit, action: () -> T): T {
|
||||
if (!isEnabled) {
|
||||
return action()
|
||||
}
|
||||
|
||||
check(lock(timeout, unit)) { "Failed to acquire lock" }
|
||||
try {
|
||||
return action()
|
||||
} finally {
|
||||
unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Synchronization for tree sitter objects.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface TSSynchronized {
|
||||
|
||||
/**
|
||||
* Acquires the lock. Throws an [IllegalStateException] if the lock can not be acquired within
|
||||
* the given [timeout].
|
||||
*
|
||||
* @param timeout The timeout duration.
|
||||
* @param unit The time unit for the timeout duration.
|
||||
* @return Whether the lock was held or not.
|
||||
*/
|
||||
fun lock(timeout: Long = 3, unit: TimeUnit = TimeUnit.SECONDS): Boolean
|
||||
|
||||
/**
|
||||
* Releases the lock.
|
||||
*/
|
||||
fun unlock()
|
||||
|
||||
/**
|
||||
* Execute the given [action] with the lock. The lock will be held before executing the action
|
||||
* and finally released. Throws an [IllegalStateException] if the lock can not be acquired within
|
||||
* * the given [timeout].
|
||||
*
|
||||
* @param timeout The timeout duration.
|
||||
* @param unit The time unit for the timeout duration.
|
||||
* @param action The action to perform.
|
||||
*/
|
||||
fun <T : Any?> withLock(timeout: Long = 3, unit: TimeUnit = TimeUnit.SECONDS, action: () -> T): T
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSInputEdit
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
class TreeSitterInputEdit @JvmOverloads internal constructor(
|
||||
startByte: Int = 0,
|
||||
oldEndByte: Int = 0,
|
||||
newEndByte: Int = 0,
|
||||
startPoint: TSPoint? = null,
|
||||
oldEndPoint: TSPoint? = null,
|
||||
newEndPoint: TSPoint? = null
|
||||
) : TSInputEdit(startByte, oldEndByte, newEndByte, startPoint, oldEndPoint, newEndPoint),
|
||||
RecyclableObjectPool.Recyclable by DefaultRecyclable(), TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Obtain an instance of [TreeSitterInputEdit].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun obtain(startByte: Int,
|
||||
oldEndByte: Int,
|
||||
newEndByte: Int,
|
||||
startPoint: TSPoint?,
|
||||
oldEndPoint: TSPoint?,
|
||||
newEndPoint: TSPoint?): TreeSitterInputEdit {
|
||||
|
||||
return obtainFromPool<TreeSitterInputEdit>().apply {
|
||||
this.startByte = startByte
|
||||
this.oldEndByte = oldEndByte
|
||||
this.newEndByte = newEndByte
|
||||
this.startPoint = startPoint
|
||||
this.oldEndPoint = oldEndPoint
|
||||
this.newEndPoint = newEndPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
withLock {
|
||||
this.startByte = 0
|
||||
this.oldEndByte = 0
|
||||
this.newEndByte = 0
|
||||
this.startPoint = null
|
||||
this.oldEndPoint = null
|
||||
this.newEndPoint = null
|
||||
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSLanguage
|
||||
import com.itsaky.androidide.treesitter.TSLookaheadIterator
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterLookaheadIterator @JvmOverloads internal constructor(pointer: Long = 0) :
|
||||
TSLookaheadIterator(pointer), RecyclableObjectPool.Recyclable by DefaultRecyclable(),
|
||||
TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(pointer: Long): TreeSitterLookaheadIterator {
|
||||
return obtainFromPool<TreeSitterLookaheadIterator>().apply {
|
||||
nativeObject = pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun next(): Boolean {
|
||||
return withLock {
|
||||
super.next()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCurrentSymbol(): Short {
|
||||
return withLock {
|
||||
super.getCurrentSymbol()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCurrentSymbolName(): String {
|
||||
return withLock {
|
||||
super.getCurrentSymbolName()
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetState(stateId: Short): Boolean {
|
||||
return withLock {
|
||||
super.resetState(stateId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset(language: TSLanguage?, stateId: Short): Boolean {
|
||||
return withLock {
|
||||
super.reset(language, stateId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLanguage(): TSLanguage {
|
||||
return withLock {
|
||||
super.getLanguage()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
withLock {
|
||||
super.close()
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.nativeObject = 0
|
||||
this.langName = null
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSLanguage
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterNativeLanguage @JvmOverloads internal constructor(
|
||||
name: String? = null,
|
||||
pointers: LongArray? = longArrayOf(0, 0)
|
||||
) : TSLanguage(name, pointers), RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(name: String?, pointers: LongArray?): TreeSitterNativeLanguage {
|
||||
return obtainFromPool<TreeSitterNativeLanguage>().apply {
|
||||
this.name = name
|
||||
this.nativeObject = pointers?.get(0) ?: 0
|
||||
this.libHandle = pointers?.get(1) ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
super.close()
|
||||
recycle()
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.nativeObject = 0
|
||||
this.name = null
|
||||
this.libHandle = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.TSTreeCursor
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterNode @JvmOverloads internal constructor(
|
||||
context0: Int = 0,
|
||||
context1: Int = 0,
|
||||
context2: Int = 0,
|
||||
context3: Int = 0,
|
||||
id: Long = 0,
|
||||
tree: Long = 0
|
||||
) : TSNode(context0, context1, context2, context3, id, tree),
|
||||
RecyclableObjectPool.Recyclable by DefaultRecyclable(), TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(
|
||||
context0: Int, context1: Int, context2: Int, context3: Int, id: Long, tree: Long
|
||||
): TreeSitterNode {
|
||||
return obtainFromPool<TreeSitterNode>().apply {
|
||||
this.context0 = context0
|
||||
this.context1 = context1
|
||||
this.context2 = context2
|
||||
this.context3 = context3
|
||||
this.id = id
|
||||
this.tree = tree
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTree(): TSTree {
|
||||
return withLock { super.getTree() }
|
||||
}
|
||||
|
||||
override fun getChild(index: Int): TSNode {
|
||||
return withLock { super.getChild(index) }
|
||||
}
|
||||
|
||||
override fun getNamedChild(index: Int): TSNode {
|
||||
return withLock { super.getNamedChild(index) }
|
||||
}
|
||||
|
||||
override fun getChildByFieldName(fieldName: String?): TSNode {
|
||||
return withLock { super.getChildByFieldName(fieldName) }
|
||||
}
|
||||
|
||||
override fun walk(): TSTreeCursor {
|
||||
return withLock { super.walk() }
|
||||
}
|
||||
|
||||
override fun findNodeWithType(type: String?, namedOnly: Boolean): TSNode {
|
||||
return withLock { super.findNodeWithType(type, namedOnly) }
|
||||
}
|
||||
|
||||
override fun findChildrenWithType(type: String?, reverseSearch: Boolean,
|
||||
namedOnly: Boolean): MutableList<TSNode> {
|
||||
return withLock { super.findChildrenWithType(type, reverseSearch, namedOnly) }
|
||||
}
|
||||
|
||||
override fun findChildrenWithTypeReverse(type: String?, namedOnly: Boolean): MutableList<TSNode> {
|
||||
return withLock { super.findChildrenWithTypeReverse(type, namedOnly) }
|
||||
}
|
||||
|
||||
override fun getParent(): TSNode {
|
||||
return withLock { super.getParent() }
|
||||
}
|
||||
|
||||
override fun getFieldNameForChild(p0: Int): String {
|
||||
return withLock { super.getFieldNameForChild(p0) }
|
||||
}
|
||||
|
||||
override fun getChildByFieldId(p0: Int): TSNode {
|
||||
return withLock { super.getChildByFieldId(p0) }
|
||||
}
|
||||
|
||||
override fun getNextSibling(): TSNode {
|
||||
return withLock { super.getNextSibling() }
|
||||
}
|
||||
|
||||
override fun getPreviousSibling(): TSNode {
|
||||
return withLock { super.getPreviousSibling() }
|
||||
}
|
||||
|
||||
override fun getNextNamedSibling(): TSNode {
|
||||
return withLock { super.getNextNamedSibling() }
|
||||
}
|
||||
|
||||
override fun getPreviousNamedSibling(): TSNode {
|
||||
return withLock { super.getPreviousNamedSibling() }
|
||||
}
|
||||
|
||||
override fun getFirstChildForByte(p0: Int): TSNode {
|
||||
return withLock { super.getFirstChildForByte(p0) }
|
||||
}
|
||||
|
||||
override fun getFirstNamedChildForByte(p0: Int): TSNode {
|
||||
return withLock { super.getFirstNamedChildForByte(p0) }
|
||||
}
|
||||
|
||||
override fun getDescendantForByteRange(p0: Int, p1: Int): TSNode {
|
||||
return withLock { super.getDescendantForByteRange(p0, p1) }
|
||||
}
|
||||
|
||||
override fun getDescendantForPointRange(p0: TSPoint?, p1: TSPoint?): TSNode {
|
||||
return withLock { super.getDescendantForPointRange(p0, p1) }
|
||||
}
|
||||
|
||||
override fun getNamedDescendantForByteRange(p0: Int, p1: Int): TSNode {
|
||||
return withLock { super.getNamedDescendantForByteRange(p0, p1) }
|
||||
}
|
||||
|
||||
override fun getNamedDescendantForPointRange(p0: TSPoint?, p1: TSPoint?): TSNode? {
|
||||
return withLock { super.getNamedDescendantForPointRange(p0, p1) }
|
||||
}
|
||||
|
||||
override fun isEqualTo(p0: TSNode?): Boolean {
|
||||
return withLock { super.isEqualTo(p0) }
|
||||
}
|
||||
|
||||
override fun getChildCount(): Int {
|
||||
return withLock { super.getChildCount() }
|
||||
}
|
||||
|
||||
override fun getNamedChildCount(): Int {
|
||||
return withLock { super.getNamedChildCount() }
|
||||
}
|
||||
|
||||
override fun getNodeString(): String {
|
||||
return withLock { super.getNodeString() }
|
||||
}
|
||||
|
||||
override fun getStartByte(): Int {
|
||||
return withLock { super.getStartByte() }
|
||||
}
|
||||
|
||||
override fun getEndByte(): Int {
|
||||
return withLock { super.getEndByte() }
|
||||
}
|
||||
|
||||
override fun getStartPoint(): TSPoint {
|
||||
return withLock { super.getStartPoint() }
|
||||
}
|
||||
|
||||
override fun getEndPoint(): TSPoint {
|
||||
return withLock { super.getEndPoint() }
|
||||
}
|
||||
|
||||
override fun getType(): String {
|
||||
return withLock { super.getType() }
|
||||
}
|
||||
|
||||
override fun getSymbol(): Int {
|
||||
return withLock { super.getSymbol() }
|
||||
}
|
||||
|
||||
override fun isNull(): Boolean {
|
||||
return withLock { super.isNull() }
|
||||
}
|
||||
|
||||
override fun isNamed(): Boolean {
|
||||
return withLock { super.isNamed() }
|
||||
}
|
||||
|
||||
override fun isExtra(): Boolean {
|
||||
return withLock { super.isExtra() }
|
||||
}
|
||||
|
||||
override fun isMissing(): Boolean {
|
||||
return withLock { super.isMissing() }
|
||||
}
|
||||
|
||||
override fun hasChanges(): Boolean {
|
||||
return withLock { super.hasChanges() }
|
||||
}
|
||||
|
||||
override fun hasErrors(): Boolean {
|
||||
return withLock { super.hasErrors() }
|
||||
}
|
||||
|
||||
override fun isError(): Boolean {
|
||||
return withLock { super.isError() }
|
||||
}
|
||||
|
||||
override fun getParseState(): Short {
|
||||
return withLock { super.getParseState() }
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
withLock {
|
||||
this.context0 = 0
|
||||
this.context1 = 0
|
||||
this.context2 = 0
|
||||
this.context3 = 0
|
||||
this.id = 0
|
||||
this.tree = 0
|
||||
this.mTree = null
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSInputEdit
|
||||
import com.itsaky.androidide.treesitter.TSLanguage
|
||||
import com.itsaky.androidide.treesitter.TSLookaheadIterator
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSParser
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.treesitter.TSQuery
|
||||
import com.itsaky.androidide.treesitter.TSQueryCapture
|
||||
import com.itsaky.androidide.treesitter.TSQueryCursor
|
||||
import com.itsaky.androidide.treesitter.TSQueryMatch
|
||||
import com.itsaky.androidide.treesitter.TSQueryPredicateStep
|
||||
import com.itsaky.androidide.treesitter.TSRange
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.TSTreeCursor
|
||||
import com.itsaky.androidide.treesitter.TSTreeCursorNode
|
||||
import com.itsaky.androidide.treesitter.util.TSObjectFactory
|
||||
|
||||
/**
|
||||
* [TSObjectFactory] implementation for AndroidIDE.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterObjectFactory : TSObjectFactory {
|
||||
|
||||
override fun createInputEdit(startByte: Int, oldEndByte: Int, newEndByte: Int,
|
||||
startPoint: TSPoint?, oldEndPoint: TSPoint?, newEndPoint: TSPoint?): TSInputEdit {
|
||||
return TreeSitterInputEdit.obtain(
|
||||
startByte,
|
||||
oldEndByte,
|
||||
newEndByte,
|
||||
startPoint,
|
||||
oldEndPoint,
|
||||
newEndPoint
|
||||
)
|
||||
}
|
||||
|
||||
override fun createParser(parserPointer: Long): TSParser {
|
||||
return TreeSitterParser.obtain(parserPointer)
|
||||
}
|
||||
|
||||
override fun createQuery(queryPointer: Long): TSQuery {
|
||||
return TreeSitterQuery.obtain(queryPointer)
|
||||
}
|
||||
|
||||
override fun createQueryCursor(pointer: Long): TSQueryCursor {
|
||||
return TreeSitterQueryCursor.obtain(pointer)
|
||||
}
|
||||
|
||||
override fun createPoint(row: Int, column: Int): TSPoint {
|
||||
return TreeSitterPoint.obtain(row, column)
|
||||
}
|
||||
|
||||
override fun createRange(startByte: Int, endByte: Int, startPoint: TSPoint,
|
||||
endPoint: TSPoint?): TSRange {
|
||||
return TreeSitterRange.obtain(startByte, endByte, startPoint, endPoint)
|
||||
}
|
||||
|
||||
override fun createRangeArr(size: Int): Array<TSRange?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
override fun createTree(pointer: Long): TSTree {
|
||||
return TreeSitterTree.obtain(pointer)
|
||||
}
|
||||
|
||||
override fun createTreeCursor(pointer: Long): TSTreeCursor {
|
||||
return TreeSitterTreeCursor.obtain(pointer)
|
||||
}
|
||||
|
||||
override fun createNode(context0: Int, context1: Int, context2: Int, context3: Int, id: Long,
|
||||
treePointer: Long): TSNode {
|
||||
return TreeSitterNode.obtain(context0, context1, context2, context3, id, treePointer)
|
||||
}
|
||||
|
||||
override fun createQueryCapture(node: TSNode?, index: Int): TSQueryCapture {
|
||||
return TreeSitterQueryCapture.obtain(node, index)
|
||||
}
|
||||
|
||||
override fun createQueryMatch(id: Int, patternIndex: Int,
|
||||
captures: Array<TSQueryCapture?>?): TSQueryMatch {
|
||||
return TreeSitterQueryMatch.obtain(id, patternIndex, captures)
|
||||
}
|
||||
|
||||
override fun createQueryPredicateStep(type: Int, valueId: Int): TSQueryPredicateStep {
|
||||
return TreeSitterQueryPredicateStep.obtain(type, valueId)
|
||||
}
|
||||
|
||||
override fun createQueryPredicateStepArr(size: Int): Array<TSQueryPredicateStep?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
override fun createTreeCursorNode(
|
||||
type: String?,
|
||||
name: String?,
|
||||
startByte: Int,
|
||||
endByte: Int
|
||||
): TSTreeCursorNode {
|
||||
return TreeSitterTreeCursorNode.obtain(type, name, startByte, endByte)
|
||||
}
|
||||
|
||||
override fun createLookaheadIterator(pointer: Long): TSLookaheadIterator {
|
||||
return TreeSitterLookaheadIterator.obtain(pointer)
|
||||
}
|
||||
|
||||
override fun createLanguage(name: String?, pointers: LongArray?): TSLanguage {
|
||||
return TreeSitterNativeLanguage.obtain(name, pointers)
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool.Companion.CAPACITY_DEFAULT
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool.Companion.CAPACITY_MINI
|
||||
import com.itsaky.androidide.utils.newRecyclableObjectPool
|
||||
import com.itsaky.androidide.utils.uncheckedCast
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Provides instances of [RecyclableObjectPool] for tree sitter objects.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
internal object TreeSitterObjectPoolProvider {
|
||||
|
||||
private val caches = ConcurrentHashMap<Class<out RecyclableObjectPool.Recyclable>, RecyclableObjectPool<*>>()
|
||||
|
||||
init {
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterInputEdit)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterLookaheadIterator)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterNativeLanguage)
|
||||
putPool(capacity = 1_00_000, fillFirst = 1000, factory = ::TreeSitterNode)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterParser)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterPoint)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterQuery)
|
||||
putPool(capacity = 1_00_000, fillFirst = 1000, factory = ::TreeSitterQueryCapture)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterQueryCursor)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterQueryMatch)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterQueryPredicateStep)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterRange)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterTree)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterTreeCursor)
|
||||
putPool(capacity = CAPACITY_MINI, factory = ::TreeSitterTreeCursorNode)
|
||||
}
|
||||
|
||||
fun logMetrics() {
|
||||
caches.forEach { (_, pool) ->
|
||||
pool.logMetrics()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : RecyclableObjectPool.Recyclable> getPool(): RecyclableObjectPool<T>? {
|
||||
return getPool(T::class.java)
|
||||
}
|
||||
|
||||
inline fun <reified T : RecyclableObjectPool.Recyclable> requirePool(): RecyclableObjectPool<T> {
|
||||
return requireNotNull(getPool())
|
||||
}
|
||||
|
||||
fun <T : RecyclableObjectPool.Recyclable> getPool(klass: Class<T>): RecyclableObjectPool<T>? {
|
||||
return caches[klass]?.let { uncheckedCast(it) }
|
||||
}
|
||||
|
||||
fun <T : RecyclableObjectPool.Recyclable> requirePool(klass: Class<T>): RecyclableObjectPool<T> {
|
||||
return requireNotNull(getPool(klass))
|
||||
}
|
||||
|
||||
private inline fun <reified T : RecyclableObjectPool.Recyclable> putPool(
|
||||
capacity: Int = CAPACITY_DEFAULT,
|
||||
fillFirst: Int = 0,
|
||||
factory: RecyclableObjectPool.Factory<T>
|
||||
) {
|
||||
if (caches[T::class.java] != null) {
|
||||
throw IllegalArgumentException("Attempt to replace object pool for ${T::class.java}")
|
||||
}
|
||||
|
||||
// disable metrics logging for tree sitter objects
|
||||
// this is required because the log views also use tree sitter to highlight the lines
|
||||
// if we enable metrics, it would result in an infite loop and 'analyze and log' in some cases
|
||||
caches[T::class.java] = newRecyclableObjectPool(capacity, fillFirst, false, factory)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSLanguage
|
||||
import com.itsaky.androidide.treesitter.TSParser
|
||||
import com.itsaky.androidide.treesitter.TSRange
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.string.UTF16String
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterParser @JvmOverloads internal constructor(
|
||||
pointer: Long = 0
|
||||
) : TSParser(pointer), RecyclableObjectPool.Recyclable by DefaultRecyclable(),
|
||||
TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(
|
||||
pointer: Long
|
||||
): TreeSitterParser {
|
||||
return obtainFromPool<TreeSitterParser>().apply {
|
||||
this.nativeObject = pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
withLock {
|
||||
super.close()
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setLanguage(language: TSLanguage?) {
|
||||
withLock { super.setLanguage(language) }
|
||||
}
|
||||
|
||||
override fun parseString(oldTree: TSTree?, source: UTF16String?): TSTree {
|
||||
return withLock { super.parseString(oldTree, source) }
|
||||
}
|
||||
|
||||
override fun setTimeout(microseconds: Long) {
|
||||
withLock { super.setTimeout(microseconds) }
|
||||
}
|
||||
|
||||
override fun getTimeout(): Long {
|
||||
return withLock { super.getTimeout() }
|
||||
}
|
||||
|
||||
override fun setIncludedRanges(ranges: Array<out TSRange>?): Boolean {
|
||||
return withLock { super.setIncludedRanges(ranges) }
|
||||
}
|
||||
|
||||
override fun getIncludedRanges(): Array<TSRange> {
|
||||
return withLock { super.getIncludedRanges() }
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
withLock { super.reset() }
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.nativeObject = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterPoint @JvmOverloads internal constructor(
|
||||
row: Int = 0,
|
||||
column: Int = 0
|
||||
) : TSPoint(row, column), RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(row: Int, column: Int): TreeSitterPoint {
|
||||
return obtainFromPool<TreeSitterPoint>().apply {
|
||||
this.row = row
|
||||
this.column = column
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.row = 0
|
||||
this.column = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSQuantifier
|
||||
import com.itsaky.androidide.treesitter.TSQuery
|
||||
import com.itsaky.androidide.treesitter.TSQueryPredicateStep
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterQuery @JvmOverloads internal constructor(
|
||||
pointer: Long = 0
|
||||
) : TSQuery(pointer), RecyclableObjectPool.Recyclable by DefaultRecyclable(),
|
||||
TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(pointer: Long): TreeSitterQuery {
|
||||
return obtainFromPool<TreeSitterQuery>().apply {
|
||||
this.nativeObject = pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCaptureCount(): Int {
|
||||
return withLock { super.getCaptureCount() }
|
||||
}
|
||||
|
||||
override fun getPatternCount(): Int {
|
||||
return withLock { super.getPatternCount() }
|
||||
}
|
||||
|
||||
override fun getStringCount(): Int {
|
||||
return withLock { super.getStringCount() }
|
||||
}
|
||||
|
||||
override fun getCaptureNames(): Array<String> {
|
||||
return withLock { super.getCaptureNames() }
|
||||
}
|
||||
|
||||
override fun getStartByteForPattern(pattern: Int): Int {
|
||||
return withLock { super.getStartByteForPattern(pattern) }
|
||||
}
|
||||
|
||||
override fun getPredicatesForPattern(pattern: Int): Array<TSQueryPredicateStep> {
|
||||
return withLock { super.getPredicatesForPattern(pattern) }
|
||||
}
|
||||
|
||||
override fun isPatternRooted(pattern: Int): Boolean {
|
||||
return withLock { super.isPatternRooted(pattern) }
|
||||
}
|
||||
|
||||
override fun isPatternNonLocal(pattern: Int): Boolean {
|
||||
return withLock { super.isPatternNonLocal(pattern) }
|
||||
}
|
||||
|
||||
override fun isPatternGuaranteedAtStep(offset: Int): Boolean {
|
||||
return withLock { super.isPatternGuaranteedAtStep(offset) }
|
||||
}
|
||||
|
||||
override fun getCaptureNameForId(id: Int): String {
|
||||
return withLock { super.getCaptureNameForId(id) }
|
||||
}
|
||||
|
||||
override fun getStringValueForId(id: Int): String {
|
||||
return withLock { super.getStringValueForId(id) }
|
||||
}
|
||||
|
||||
override fun getCaptureQuantifierForId(pattern: Int, capture: Int): TSQuantifier {
|
||||
return withLock { super.getCaptureQuantifierForId(pattern, capture) }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
withLock {
|
||||
super.close()
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.nativeObject = 0
|
||||
this.captureNames = null
|
||||
this.errorOffset = 0
|
||||
this.errorType = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSQueryCapture
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterQueryCapture @JvmOverloads internal constructor(
|
||||
node: TSNode? = null,
|
||||
index: Int = 0
|
||||
) : TSQueryCapture(node, index), RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(node: TSNode?, index: Int): TreeSitterQueryCapture {
|
||||
return obtainFromPool<TreeSitterQueryCapture>().apply {
|
||||
this.node = node
|
||||
this.index = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
(this.node as? TreeSitterNode?)?.recycle()
|
||||
this.index = 0
|
||||
this.node = null
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.treesitter.TSQuery
|
||||
import com.itsaky.androidide.treesitter.TSQueryCursor
|
||||
import com.itsaky.androidide.treesitter.TSQueryMatch
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterQueryCursor(
|
||||
pointer: Long = 0
|
||||
) : TSQueryCursor(pointer), RecyclableObjectPool.Recyclable by DefaultRecyclable(),
|
||||
TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(pointer: Long): TreeSitterQueryCursor {
|
||||
return obtainFromPool<TreeSitterQueryCursor>().apply {
|
||||
this.nativeObject = pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun exec(query: TSQuery?, node: TSNode?) {
|
||||
withLock { super.exec(query, node) }
|
||||
}
|
||||
|
||||
override fun didExceedMatchLimit(): Boolean {
|
||||
return withLock { super.didExceedMatchLimit() }
|
||||
}
|
||||
|
||||
override fun getMatchLimit(): Int {
|
||||
return withLock { super.getMatchLimit() }
|
||||
}
|
||||
|
||||
override fun setMatchLimit(newLimit: Int) {
|
||||
withLock { super.setMatchLimit(newLimit) }
|
||||
}
|
||||
|
||||
override fun setByteRange(start: Int, end: Int) {
|
||||
withLock { super.setByteRange(start, end) }
|
||||
}
|
||||
|
||||
override fun setPointRange(start: TSPoint?, end: TSPoint?) {
|
||||
withLock { super.setPointRange(start, end) }
|
||||
}
|
||||
|
||||
override fun nextMatch(): TSQueryMatch? {
|
||||
return withLock { super.nextMatch() }
|
||||
}
|
||||
|
||||
override fun removeMatch(id: Int) {
|
||||
withLock { super.removeMatch(id) }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
withLock {
|
||||
super.close()
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.nativeObject = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSQueryCapture
|
||||
import com.itsaky.androidide.treesitter.TSQueryMatch
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterQueryMatch @JvmOverloads internal constructor(
|
||||
id: Int = 0,
|
||||
patternIndex: Int = 0,
|
||||
captures: Array<out TSQueryCapture?>? = null
|
||||
) : TSQueryMatch(id, patternIndex, captures),
|
||||
RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(id: Int, patternIndex: Int,
|
||||
captures: Array<out TSQueryCapture?>?): TreeSitterQueryMatch {
|
||||
return obtainFromPool<TreeSitterQueryMatch>().apply {
|
||||
this.id = id
|
||||
this.patternIndex = patternIndex
|
||||
this.captures = captures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.id = 0
|
||||
this.patternIndex = 0
|
||||
this.captures = null
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSQueryPredicateStep
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterQueryPredicateStep @JvmOverloads internal constructor(
|
||||
type: Int = 0,
|
||||
valueId: Int = 0
|
||||
) : TSQueryPredicateStep(type, valueId), RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(type: Int, valueId: Int): TreeSitterQueryPredicateStep {
|
||||
return obtainFromPool<TreeSitterQueryPredicateStep>().apply {
|
||||
this.type = type
|
||||
this.valueId = valueId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.type = 0
|
||||
this.valueId = 0
|
||||
this.cachedType = null
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.treesitter.TSRange
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterRange @JvmOverloads internal constructor(
|
||||
startByte: Int = 0,
|
||||
endByte: Int = 0,
|
||||
startPoint: TSPoint? = null,
|
||||
endPoint: TSPoint? = null
|
||||
) : TSRange(startByte, endByte, startPoint, endPoint),
|
||||
RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(startByte: Int, endByte: Int, startPoint: TSPoint?,
|
||||
endPoint: TSPoint?): TreeSitterRange {
|
||||
return obtainFromPool<TreeSitterRange>().apply {
|
||||
this.startByte = startByte
|
||||
this.endByte = endByte
|
||||
this.startPoint = startPoint
|
||||
this.endPoint = endPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
(this.startPoint as? TreeSitterPoint?)?.recycle()
|
||||
(this.endPoint as? TreeSitterPoint?)?.recycle()
|
||||
|
||||
this.startByte = 0
|
||||
this.endByte = 0
|
||||
this.startPoint = null
|
||||
this.endPoint = null
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSInputEdit
|
||||
import com.itsaky.androidide.treesitter.TSLanguage
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSRange
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterTree @JvmOverloads internal constructor(
|
||||
pointer: Long = 0,
|
||||
private val synchronized: DefaultSynchronized = DefaultSynchronized()
|
||||
) : TSTree(pointer), RecyclableObjectPool.Recyclable by DefaultRecyclable(),
|
||||
TSSynchronized by synchronized {
|
||||
|
||||
private var ownerThread: Thread? = null
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(pointer: Long): TreeSitterTree {
|
||||
return obtainFromPool<TreeSitterTree>().apply {
|
||||
this.nativeObject = pointer
|
||||
this.ownerThread = Thread.currentThread()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRootNode(): TSNode {
|
||||
return withLock { super.getRootNode() }
|
||||
}
|
||||
|
||||
override fun getChangedRanges(oldTree: TSTree?): Array<TSRange> {
|
||||
return withLock { super.getChangedRanges(oldTree) }
|
||||
}
|
||||
|
||||
override fun copy(): TSTree {
|
||||
return withLock { super.copy() }
|
||||
}
|
||||
|
||||
override fun edit(edit: TSInputEdit?) {
|
||||
withLock { super.edit(edit) }
|
||||
}
|
||||
|
||||
override fun getLanguage(): TSLanguage {
|
||||
return withLock { super.getLanguage() }
|
||||
}
|
||||
|
||||
override fun <T> withLock(timeout: Long, unit: TimeUnit, action: () -> T): T {
|
||||
// check(ownerThread != null && Thread.currentThread() == ownerThread) {
|
||||
// "TSTree instances are not thread-safe. Copy the tree if you need to use it on multiple threads." +
|
||||
// "currentThread=${Thread.currentThread()} initialThread=$ownerThread"
|
||||
// }
|
||||
|
||||
return synchronized.withLock(timeout, unit, action)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
withLock {
|
||||
super.close()
|
||||
this.nativeObject = 0
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.ownerThread = null
|
||||
if (!canAccess()) {
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSPoint
|
||||
import com.itsaky.androidide.treesitter.TSTreeCursor
|
||||
import com.itsaky.androidide.treesitter.TSTreeCursorNode
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterTreeCursor @JvmOverloads internal constructor(
|
||||
pointer: Long = 0
|
||||
) : TSTreeCursor(pointer), RecyclableObjectPool.Recyclable by DefaultRecyclable(),
|
||||
TSSynchronized by DefaultSynchronized() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(pointer: Long): TreeSitterTreeCursor {
|
||||
return obtainFromPool<TreeSitterTreeCursor>().apply {
|
||||
this.nativeObject = pointer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCurrentNode(): TSNode {
|
||||
return withLock { super.getCurrentNode() }
|
||||
}
|
||||
|
||||
override fun getCurrentFieldName(): String {
|
||||
return withLock { super.getCurrentFieldName() }
|
||||
}
|
||||
|
||||
override fun getCurrentFieldId(): Short {
|
||||
return withLock { super.getCurrentFieldId() }
|
||||
}
|
||||
|
||||
override fun getCurrentTreeCursorNode(): TSTreeCursorNode {
|
||||
return withLock { super.getCurrentTreeCursorNode() }
|
||||
}
|
||||
|
||||
override fun gotoFirstChild(): Boolean {
|
||||
return withLock { super.gotoFirstChild() }
|
||||
}
|
||||
|
||||
override fun gotoFirstChildForByte(byteIndex: Int): Long {
|
||||
return withLock { super.gotoFirstChildForByte(byteIndex) }
|
||||
}
|
||||
|
||||
override fun gotoFirstChildForPoint(point: TSPoint?): Boolean {
|
||||
return withLock { super.gotoFirstChildForPoint(point) }
|
||||
}
|
||||
|
||||
override fun gotoLastChild(): Boolean {
|
||||
return withLock { super.gotoLastChild() }
|
||||
}
|
||||
|
||||
override fun gotoNextSibling(): Boolean {
|
||||
return withLock { super.gotoNextSibling() }
|
||||
}
|
||||
|
||||
override fun gotoPreviousSibling(): Boolean {
|
||||
return withLock { super.gotoPreviousSibling() }
|
||||
}
|
||||
|
||||
override fun gotoParent(): Boolean {
|
||||
return withLock { super.gotoParent() }
|
||||
}
|
||||
|
||||
override fun gotoDescendant(descendantIndex: Int) {
|
||||
withLock { super.gotoDescendant(descendantIndex) }
|
||||
}
|
||||
|
||||
override fun getCurrentDescendantIndex(): Int {
|
||||
return withLock { super.getCurrentDescendantIndex() }
|
||||
}
|
||||
|
||||
override fun getDepth(): Int {
|
||||
return withLock { super.getDepth() }
|
||||
}
|
||||
|
||||
override fun reset(node: TSNode?) {
|
||||
withLock { super.reset(node) }
|
||||
}
|
||||
|
||||
override fun resetTo(another: TSTreeCursor?) {
|
||||
withLock { super.resetTo(another) }
|
||||
}
|
||||
|
||||
override fun copy(): TSTreeCursor {
|
||||
return withLock { super.copy() }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
withLock {
|
||||
super.close()
|
||||
recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.nativeObject = 0
|
||||
this.context0 = 0
|
||||
this.context1 = 0
|
||||
this.id = 0
|
||||
this.tree = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
+56
@@ -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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSTreeCursorNode
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class TreeSitterTreeCursorNode @JvmOverloads internal constructor(
|
||||
type: String? = null,
|
||||
name: String? = null,
|
||||
startByte: Int = 0,
|
||||
endByte: Int = 0
|
||||
) : TSTreeCursorNode(type, name, startByte, endByte),
|
||||
RecyclableObjectPool.Recyclable by DefaultRecyclable() {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun obtain(type: String?, name: String?, startByte: Int,
|
||||
endByte: Int): TreeSitterTreeCursorNode {
|
||||
return obtainFromPool<TreeSitterTreeCursorNode>().apply {
|
||||
this.type = type
|
||||
this.name = name
|
||||
this.startByte = startByte
|
||||
this.endByte = endByte
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
this.type = null
|
||||
this.name = null
|
||||
this.startByte = 0
|
||||
this.endByte = 0
|
||||
returnToPool()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.treesitter.api
|
||||
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
|
||||
/**
|
||||
* Returns this [RecyclableObjectPool.Recyclable] object to the pool from the [TreeSitterObjectPoolProvider].
|
||||
*
|
||||
* @see TreeSitterObjectPoolProvider.requirePool
|
||||
*/
|
||||
internal inline fun <reified T : RecyclableObjectPool.Recyclable> T.returnToPool() {
|
||||
TreeSitterObjectPoolProvider.requirePool<T>().recycle(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of [T] from the [RecyclableObjectPool] using [TreeSitterObjectPoolProvider].
|
||||
*
|
||||
* @see TreeSitterObjectPoolProvider.requirePool
|
||||
*/
|
||||
internal inline fun <reified T : RecyclableObjectPool.Recyclable> obtainFromPool(): T {
|
||||
return TreeSitterObjectPoolProvider.requirePool<T>().obtain()
|
||||
}
|
||||
@@ -33,5 +33,10 @@ android {
|
||||
|
||||
dependencies {
|
||||
api(libs.common.editor)
|
||||
api(libs.common.kotlin.coroutines.android)
|
||||
api(libs.androidide.ts)
|
||||
|
||||
api(projects.common)
|
||||
api(projects.editorApi)
|
||||
api(projects.logger)
|
||||
}
|
||||
|
||||
+235
-173
@@ -24,9 +24,13 @@
|
||||
|
||||
package io.github.rosemoe.sora.editor.ts
|
||||
|
||||
import com.itsaky.androidide.treesitter.TSInputEdit
|
||||
import com.itsaky.androidide.treesitter.TSQueryCapture
|
||||
import com.itsaky.androidide.treesitter.TSQueryCursor
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterNode
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryCapture
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryMatch
|
||||
import io.github.rosemoe.sora.editor.ts.spans.TsSpanFactory
|
||||
import io.github.rosemoe.sora.lang.styling.Span
|
||||
import io.github.rosemoe.sora.lang.styling.Spans
|
||||
@@ -34,6 +38,7 @@ import io.github.rosemoe.sora.lang.styling.TextStyle
|
||||
import io.github.rosemoe.sora.text.CharPosition
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Spans generator for tree-sitter. Results are cached.
|
||||
@@ -43,196 +48,253 @@ import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
|
||||
* @author Rosemoe
|
||||
*/
|
||||
class LineSpansGenerator(
|
||||
internal var tree: TSTree, internal var lineCount: Int,
|
||||
private val content: Content, internal var theme: TsTheme,
|
||||
private val languageSpec: TsLanguageSpec, var scopedVariables: TsScopedVariables,
|
||||
private val spanFactory: TsSpanFactory
|
||||
internal var tree: TSTree, internal var lineCount: Int,
|
||||
private val content: Content, internal var theme: TsTheme,
|
||||
private val languageSpec: TsLanguageSpec, var scopedVariables: TsScopedVariables,
|
||||
private val spanFactory: TsSpanFactory
|
||||
) : Spans {
|
||||
|
||||
companion object {
|
||||
const val CACHE_THRESHOLD = 60
|
||||
private val isValid = AtomicBoolean(true)
|
||||
|
||||
companion object {
|
||||
|
||||
const val CACHE_THRESHOLD = 60
|
||||
}
|
||||
|
||||
private val caches = mutableListOf<SpanCache>()
|
||||
|
||||
fun edit(edit: TSInputEdit) {
|
||||
tree.edit(edit)
|
||||
|
||||
// node ranges change when the tree is edited
|
||||
// in this case, the query cursor should be stopped in order to prevent SEGV_MAPERR
|
||||
// when accessing the query matches
|
||||
isValid.set(false)
|
||||
}
|
||||
|
||||
fun queryCache(line: Int): MutableList<Span>? {
|
||||
for (i in 0 until caches.size) {
|
||||
val cache = caches[i]
|
||||
if (cache.line == line) {
|
||||
caches.removeAt(i)
|
||||
caches.add(0, cache)
|
||||
return cache.spans
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun pushCache(line: Int, spans: MutableList<Span>) {
|
||||
while (caches.size >= CACHE_THRESHOLD) {
|
||||
caches.removeAt(caches.size - 1)
|
||||
}
|
||||
caches.add(0, SpanCache(spans, line))
|
||||
}
|
||||
|
||||
fun captureRegion(startIndex: Int, endIndex: Int): MutableList<Span> {
|
||||
val list = mutableListOf<Span>()
|
||||
|
||||
if (!isValid.get()) {
|
||||
list.add(emptySpan(0))
|
||||
return list
|
||||
}
|
||||
|
||||
private val caches = mutableListOf<SpanCache>()
|
||||
val captures = mutableListOf<TSQueryCapture>()
|
||||
TSQueryCursor.create().use { cursor ->
|
||||
cursor.setByteRange(startIndex * 2, endIndex * 2)
|
||||
|
||||
fun queryCache(line: Int): MutableList<Span>? {
|
||||
for (i in 0 until caches.size) {
|
||||
val cache = caches[i]
|
||||
if (cache.line == line) {
|
||||
caches.removeAt(i)
|
||||
caches.add(0, cache)
|
||||
return cache.spans
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (!tree.canAccess()) {
|
||||
throw IllegalStateException("Cannot access tree")
|
||||
}
|
||||
|
||||
fun pushCache(line: Int, spans: MutableList<Span>) {
|
||||
while (caches.size >= CACHE_THRESHOLD && caches.size > 0) {
|
||||
caches.removeAt(caches.size - 1)
|
||||
}
|
||||
caches.add(0, SpanCache(spans, line))
|
||||
}
|
||||
// TSTree.getRootNode() always returns a new TSNode instance
|
||||
// make sure to recycle the instance
|
||||
val rootNode = tree.rootNode
|
||||
cursor.exec(languageSpec.tsQuery, rootNode)
|
||||
|
||||
fun captureRegion(startIndex: Int, endIndex: Int): MutableList<Span> {
|
||||
val list = mutableListOf<Span>()
|
||||
val captures = mutableListOf<TSQueryCapture>()
|
||||
TSQueryCursor.create().use { cursor ->
|
||||
cursor.setByteRange(startIndex * 2, endIndex * 2)
|
||||
cursor.exec(languageSpec.tsQuery, tree.rootNode)
|
||||
var match = cursor.nextMatch()
|
||||
while (match != null) {
|
||||
if (languageSpec.queryPredicator.doPredicate(
|
||||
languageSpec.predicates,
|
||||
content,
|
||||
match
|
||||
)
|
||||
) {
|
||||
captures.addAll(match.captures)
|
||||
}
|
||||
match = cursor.nextMatch()
|
||||
}
|
||||
captures.sortBy { it.node.startByte }
|
||||
var lastIndex = 0
|
||||
captures.forEach { capture ->
|
||||
val startByte = capture.node.startByte
|
||||
val endByte = capture.node.endByte
|
||||
val start = (startByte / 2 - startIndex).coerceAtLeast(0)
|
||||
val pattern = capture.index
|
||||
// Do not add span for overlapping regions and out-of-bounds regions
|
||||
if (start >= lastIndex && endByte / 2 >= startIndex && startByte / 2 < endIndex
|
||||
&& (pattern !in languageSpec.localsScopeIndices && pattern !in languageSpec.localsDefinitionIndices
|
||||
&& pattern !in languageSpec.localsDefinitionValueIndices && pattern !in languageSpec.localsMembersScopeIndices)
|
||||
) {
|
||||
if (start != lastIndex) {
|
||||
list.addAll(
|
||||
createSpans(
|
||||
capture,
|
||||
lastIndex,
|
||||
start - 1,
|
||||
theme.normalTextStyle
|
||||
)
|
||||
)
|
||||
}
|
||||
var style = 0L
|
||||
if (capture.index in languageSpec.localsReferenceIndices) {
|
||||
val def = scopedVariables.findDefinition(
|
||||
startByte / 2,
|
||||
endByte / 2,
|
||||
content.substring(startByte / 2, endByte / 2)
|
||||
)
|
||||
if (def != null && def.matchedHighlightPattern != -1) {
|
||||
style = theme.resolveStyleForPattern(def.matchedHighlightPattern)
|
||||
}
|
||||
// This reference can not be resolved to its definition
|
||||
// but it can have its own fallback color by other captures
|
||||
// so continue to next capture
|
||||
if (style == 0L) {
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
if (style == 0L) {
|
||||
style = theme.resolveStyleForPattern(capture.index)
|
||||
}
|
||||
if (style == 0L) {
|
||||
style = theme.normalTextStyle
|
||||
}
|
||||
val end = (endByte / 2 - startIndex).coerceAtMost(endIndex)
|
||||
list.addAll(createSpans(capture, start, end, style))
|
||||
lastIndex = end
|
||||
}
|
||||
}
|
||||
if (lastIndex != endIndex) {
|
||||
list.add(emptySpan(lastIndex))
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list.add(emptySpan(0))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun createSpans(
|
||||
capture: TSQueryCapture,
|
||||
startColumn: Int,
|
||||
endColumn: Int,
|
||||
style: Long
|
||||
): List<Span> {
|
||||
val spans = spanFactory.createSpans(capture, startColumn, style)
|
||||
if (spans.size > 1) {
|
||||
var prevCol = spans[0].column
|
||||
if (prevCol > endColumn) {
|
||||
throw IndexOutOfBoundsException("Span's column is out of bounds! column=$prevCol, endColumn=$endColumn")
|
||||
}
|
||||
for (i in 1..spans.lastIndex) {
|
||||
val col = spans[i].column
|
||||
if (col <= prevCol) {
|
||||
throw IllegalStateException("Spans must not overlap! prevCol=$prevCol, col=$col")
|
||||
}
|
||||
if (col > endColumn) {
|
||||
throw IndexOutOfBoundsException("Span's column is out of bounds! column=$col, endColumn=$endColumn")
|
||||
}
|
||||
prevCol = col
|
||||
}
|
||||
}
|
||||
return spans
|
||||
}
|
||||
|
||||
private fun emptySpan(column: Int): Span {
|
||||
return Span.obtain(column, TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL))
|
||||
}
|
||||
|
||||
override fun adjustOnInsert(start: CharPosition, end: CharPosition) {
|
||||
|
||||
}
|
||||
|
||||
override fun adjustOnDelete(start: CharPosition, end: CharPosition) {
|
||||
|
||||
}
|
||||
|
||||
override fun read() = object : Spans.Reader {
|
||||
|
||||
private var spans = mutableListOf<Span>()
|
||||
|
||||
override fun moveToLine(line: Int) {
|
||||
if (line < 0 || line >= lineCount) {
|
||||
spans = mutableListOf()
|
||||
return
|
||||
}
|
||||
val cached = queryCache(line)
|
||||
if (cached != null) {
|
||||
spans = cached
|
||||
return
|
||||
}
|
||||
val start = content.indexer.getCharPosition(line, 0).index
|
||||
val end = start + content.getColumnCount(line)
|
||||
spans = captureRegion(start, end)
|
||||
pushCache(line, spans)
|
||||
var match = cursor.nextMatch()
|
||||
while (match != null && isValid.get() && tree.canAccess()) {
|
||||
if (languageSpec.queryPredicator.doPredicate(
|
||||
languageSpec.predicates,
|
||||
content,
|
||||
match
|
||||
)
|
||||
) {
|
||||
captures.addAll(match.captures)
|
||||
}
|
||||
|
||||
override fun getSpanCount() = spans.size
|
||||
(match as? TreeSitterQueryMatch?)?.recycle()
|
||||
|
||||
override fun getSpanAt(index: Int) = spans[index]
|
||||
|
||||
override fun getSpansOnLine(line: Int): MutableList<Span> {
|
||||
val cached = queryCache(line)
|
||||
if (cached != null) {
|
||||
return ArrayList(cached)
|
||||
}
|
||||
val start = content.indexer.getCharPosition(line, 0).index
|
||||
val end = start + content.getColumnCount(line)
|
||||
return captureRegion(start, end)
|
||||
if (!isValid.get()) {
|
||||
captures.clear()
|
||||
break
|
||||
}
|
||||
|
||||
if (!tree.canAccess() || rootNode.hasChanges()) {
|
||||
val hasChanges = rootNode.hasChanges()
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
throw IllegalStateException(
|
||||
"Tree closed while querying, rootNode.hasChanges=$hasChanges, start=$startIndex, end=$endIndex")
|
||||
}
|
||||
|
||||
match = cursor.nextMatch()
|
||||
}
|
||||
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
|
||||
captures.sortBy { it.node.startByte }
|
||||
var lastIndex = 0
|
||||
|
||||
for (capture in captures) {
|
||||
if (!isValid.get()) {
|
||||
list.clear()
|
||||
break
|
||||
}
|
||||
|
||||
val startByte = capture.node.startByte
|
||||
val endByte = capture.node.endByte
|
||||
val start = (startByte / 2 - startIndex).coerceAtLeast(0)
|
||||
val pattern = capture.index
|
||||
// Do not add span for overlapping regions and out-of-bounds regions
|
||||
if (start >= lastIndex && endByte / 2 >= startIndex && startByte / 2 < endIndex
|
||||
&& (pattern !in languageSpec.localsScopeIndices && pattern !in languageSpec.localsDefinitionIndices
|
||||
&& pattern !in languageSpec.localsDefinitionValueIndices && pattern !in languageSpec.localsMembersScopeIndices)
|
||||
) {
|
||||
if (start != lastIndex) {
|
||||
list.addAll(
|
||||
createSpans(
|
||||
capture,
|
||||
lastIndex,
|
||||
start - 1,
|
||||
theme.normalTextStyle
|
||||
)
|
||||
)
|
||||
}
|
||||
var style = 0L
|
||||
if (capture.index in languageSpec.localsReferenceIndices) {
|
||||
val def = scopedVariables.findDefinition(
|
||||
startByte / 2,
|
||||
endByte / 2,
|
||||
content.substring(startByte / 2, endByte / 2)
|
||||
)
|
||||
if (def != null && def.matchedHighlightPattern != -1) {
|
||||
style = theme.resolveStyleForPattern(def.matchedHighlightPattern)
|
||||
}
|
||||
// This reference can not be resolved to its definition
|
||||
// but it can have its own fallback color by other captures
|
||||
// so continue to next capture
|
||||
if (style == 0L) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (style == 0L) {
|
||||
style = theme.resolveStyleForPattern(capture.index)
|
||||
}
|
||||
if (style == 0L) {
|
||||
style = theme.normalTextStyle
|
||||
}
|
||||
val end = (endByte / 2 - startIndex).coerceAtMost(endIndex)
|
||||
list.addAll(createSpans(capture, start, end, style))
|
||||
lastIndex = end
|
||||
}
|
||||
|
||||
(capture as? TreeSitterQueryCapture?)?.recycle()
|
||||
}
|
||||
|
||||
|
||||
if (lastIndex != endIndex) {
|
||||
list.add(emptySpan(lastIndex))
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
list.add(emptySpan(0))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
private fun createSpans(
|
||||
capture: TSQueryCapture,
|
||||
startColumn: Int,
|
||||
endColumn: Int,
|
||||
style: Long
|
||||
): List<Span> {
|
||||
val spans = spanFactory.createSpans(capture, startColumn, style)
|
||||
if (spans.size > 1) {
|
||||
var prevCol = spans[0].column
|
||||
if (prevCol > endColumn) {
|
||||
throw IndexOutOfBoundsException(
|
||||
"Span's column is out of bounds! column=$prevCol, endColumn=$endColumn")
|
||||
}
|
||||
for (i in 1..spans.lastIndex) {
|
||||
val col = spans[i].column
|
||||
if (col <= prevCol) {
|
||||
throw IllegalStateException("Spans must not overlap! prevCol=$prevCol, col=$col")
|
||||
}
|
||||
if (col > endColumn) {
|
||||
throw IndexOutOfBoundsException(
|
||||
"Span's column is out of bounds! column=$col, endColumn=$endColumn")
|
||||
}
|
||||
prevCol = col
|
||||
}
|
||||
}
|
||||
return spans
|
||||
}
|
||||
|
||||
private fun emptySpan(column: Int): Span {
|
||||
return Span.obtain(column, TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL))
|
||||
}
|
||||
|
||||
override fun adjustOnInsert(start: CharPosition, end: CharPosition) {
|
||||
|
||||
}
|
||||
|
||||
override fun adjustOnDelete(start: CharPosition, end: CharPosition) {
|
||||
|
||||
}
|
||||
|
||||
override fun read() = object : Spans.Reader {
|
||||
|
||||
private var spans = mutableListOf<Span>()
|
||||
|
||||
override fun moveToLine(line: Int) {
|
||||
if (line < 0 || line >= lineCount) {
|
||||
spans = mutableListOf()
|
||||
return
|
||||
}
|
||||
val cached = queryCache(line)
|
||||
if (cached != null) {
|
||||
spans = cached
|
||||
return
|
||||
}
|
||||
val start = content.indexer.getCharPosition(line, 0).index
|
||||
val end = start + content.getColumnCount(line)
|
||||
spans = captureRegion(start, end)
|
||||
pushCache(line, spans)
|
||||
}
|
||||
|
||||
override fun supportsModify() = false
|
||||
override fun getSpanCount() = spans.size
|
||||
|
||||
override fun modify(): Spans.Modifier {
|
||||
throw UnsupportedOperationException()
|
||||
override fun getSpanAt(index: Int) = spans[index]
|
||||
|
||||
override fun getSpansOnLine(line: Int): MutableList<Span> {
|
||||
val cached = queryCache(line)
|
||||
if (cached != null) {
|
||||
return ArrayList(cached)
|
||||
}
|
||||
val start = content.indexer.getCharPosition(line, 0).index
|
||||
val end = start + content.getColumnCount(line)
|
||||
return captureRegion(start, end)
|
||||
}
|
||||
|
||||
override fun getLineCount() = lineCount
|
||||
}
|
||||
|
||||
override fun supportsModify() = false
|
||||
|
||||
override fun modify(): Spans.Modifier {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getLineCount() = lineCount
|
||||
}
|
||||
|
||||
data class SpanCache(val spans: MutableList<Span>, val line: Int)
|
||||
|
||||
+15
-14
@@ -31,24 +31,25 @@ package io.github.rosemoe.sora.editor.ts
|
||||
*/
|
||||
open class LocalsCaptureSpec {
|
||||
|
||||
companion object {
|
||||
val DEFAULT = LocalsCaptureSpec()
|
||||
}
|
||||
companion object {
|
||||
|
||||
open fun isDefinitionValueCapture(captureName: String) = captureName == "local.definition-value"
|
||||
val DEFAULT = LocalsCaptureSpec()
|
||||
}
|
||||
|
||||
open fun isDefinitionCapture(captureName: String) = captureName == "local.definition"
|
||||
open fun isDefinitionValueCapture(captureName: String) = captureName == "local.definition-value"
|
||||
|
||||
open fun isReferenceCapture(captureName: String) = captureName == "local.reference"
|
||||
open fun isDefinitionCapture(captureName: String) = captureName == "local.definition"
|
||||
|
||||
open fun isScopeCapture(captureName: String) = captureName == "local.scope"
|
||||
open fun isReferenceCapture(captureName: String) = captureName == "local.reference"
|
||||
|
||||
/**
|
||||
* Usually, variables in a scope take effect after their declarations. This special scope
|
||||
* indicates that, all variables in this scope (but not in its sub-scope), take effect in this
|
||||
* scope, no matter where they are.
|
||||
* For example, class member fields.
|
||||
*/
|
||||
open fun isMembersScopeCapture(captureName: String) = captureName == "local.scope.members"
|
||||
open fun isScopeCapture(captureName: String) = captureName == "local.scope"
|
||||
|
||||
/**
|
||||
* Usually, variables in a scope take effect after their declarations. This special scope
|
||||
* indicates that, all variables in this scope (but not in its sub-scope), take effect in this
|
||||
* scope, no matter where they are.
|
||||
* For example, class member fields.
|
||||
*/
|
||||
open fun isMembersScopeCapture(captureName: String) = captureName == "local.scope.members"
|
||||
|
||||
}
|
||||
+321
-300
@@ -31,6 +31,10 @@ import com.itsaky.androidide.treesitter.TSInputEdit
|
||||
import com.itsaky.androidide.treesitter.TSParser
|
||||
import com.itsaky.androidide.treesitter.TSQueryCursor
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterInputEdit
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterNode
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryCapture
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryMatch
|
||||
import com.itsaky.androidide.treesitter.string.UTF16String
|
||||
import com.itsaky.androidide.treesitter.string.UTF16StringFactory
|
||||
import io.github.rosemoe.sora.data.ObjectAllocator
|
||||
@@ -45,312 +49,329 @@ import io.github.rosemoe.sora.text.ContentReference
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
|
||||
open class TsAnalyzeManager(val languageSpec: TsLanguageSpec, var theme: TsTheme) :
|
||||
AnalyzeManager {
|
||||
AnalyzeManager {
|
||||
|
||||
var currentReceiver: StyleReceiver? = null
|
||||
var reference: ContentReference? = null
|
||||
var extraArguments: Bundle? = null
|
||||
var thread: TsLooperThread? = null
|
||||
var spanFactory : TsSpanFactory = DefaultSpanFactory()
|
||||
var currentReceiver: StyleReceiver? = null
|
||||
var reference: ContentReference? = null
|
||||
var extraArguments: Bundle? = null
|
||||
var thread: TsLooperThread? = null
|
||||
var spanFactory: TsSpanFactory = DefaultSpanFactory()
|
||||
|
||||
open var styles = Styles()
|
||||
open var styles = Styles()
|
||||
|
||||
fun updateTheme(theme: TsTheme) {
|
||||
this.theme = theme
|
||||
(styles.spans as LineSpansGenerator?)?.let {
|
||||
it.theme = theme
|
||||
}
|
||||
fun updateTheme(theme: TsTheme) {
|
||||
this.theme = theme
|
||||
(styles.spans as LineSpansGenerator?)?.let {
|
||||
it.theme = theme
|
||||
}
|
||||
}
|
||||
|
||||
override fun setReceiver(receiver: StyleReceiver?) {
|
||||
currentReceiver = receiver
|
||||
}
|
||||
override fun setReceiver(receiver: StyleReceiver?) {
|
||||
currentReceiver = receiver
|
||||
}
|
||||
|
||||
override fun reset(content: ContentReference, extraArguments: Bundle) {
|
||||
reference = content
|
||||
this.extraArguments = extraArguments
|
||||
rerun()
|
||||
}
|
||||
override fun reset(content: ContentReference, extraArguments: Bundle) {
|
||||
reference = content
|
||||
this.extraArguments = extraArguments
|
||||
rerun()
|
||||
}
|
||||
|
||||
override fun insert(start: CharPosition, end: CharPosition, insertedContent: CharSequence) {
|
||||
thread?.offerMessage(
|
||||
MSG_MOD,
|
||||
TextModification(
|
||||
start.index,
|
||||
end.index,
|
||||
TSInputEdit.create(
|
||||
start.index * 2,
|
||||
start.index * 2,
|
||||
end.index * 2,
|
||||
start.toTSPoint(),
|
||||
start.toTSPoint(),
|
||||
end.toTSPoint()
|
||||
),
|
||||
insertedContent.toString()
|
||||
)
|
||||
)
|
||||
(styles.spans as LineSpansGenerator?)?.apply {
|
||||
lineCount = reference!!.lineCount
|
||||
tree.edit(
|
||||
TSInputEdit.create(
|
||||
start.index * 2,
|
||||
start.index * 2,
|
||||
end.index * 2,
|
||||
start.toTSPoint(),
|
||||
start.toTSPoint(),
|
||||
end.toTSPoint()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(start: CharPosition, end: CharPosition, deletedContent: CharSequence) {
|
||||
thread?.offerMessage(
|
||||
MSG_MOD,
|
||||
TextModification(
|
||||
start.index,
|
||||
end.index,
|
||||
TSInputEdit.create(
|
||||
start.index * 2,
|
||||
end.index * 2,
|
||||
start.index * 2,
|
||||
start.toTSPoint(),
|
||||
end.toTSPoint(),
|
||||
start.toTSPoint()
|
||||
),
|
||||
null
|
||||
)
|
||||
)
|
||||
(styles.spans as LineSpansGenerator?)?.apply {
|
||||
lineCount = reference!!.lineCount
|
||||
tree.edit(
|
||||
TSInputEdit.create(
|
||||
start.index * 2,
|
||||
end.index * 2,
|
||||
start.index * 2,
|
||||
start.toTSPoint(),
|
||||
end.toTSPoint(),
|
||||
start.toTSPoint()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun rerun() {
|
||||
thread?.let {
|
||||
if (it.isAlive) {
|
||||
it.interrupt()
|
||||
it.abort = true
|
||||
}
|
||||
}
|
||||
(styles.spans as LineSpansGenerator?)?.tree?.close()
|
||||
styles.spans = null
|
||||
val initText = reference?.reference?.toString() ?: ""
|
||||
thread = TsLooperThread().also {
|
||||
it.name = "TsDaemon-${nextThreadId()}"
|
||||
styles = Styles()
|
||||
it.offerMessage(MSG_INIT, initText)
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
thread?.let {
|
||||
if (it.isAlive) {
|
||||
it.interrupt()
|
||||
it.abort = true
|
||||
}
|
||||
}
|
||||
(styles.spans as LineSpansGenerator?)?.tree?.close()
|
||||
spanFactory.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MSG_BASE = 11451400
|
||||
private const val MSG_INIT = MSG_BASE + 1
|
||||
private const val MSG_MOD = MSG_BASE + 2
|
||||
|
||||
@Volatile
|
||||
private var threadId = 0
|
||||
|
||||
@Synchronized
|
||||
fun nextThreadId() = ++threadId
|
||||
}
|
||||
|
||||
inner class TsLooperThread : Thread() {
|
||||
|
||||
private val messageQueue = LinkedBlockingQueue<Message>()
|
||||
|
||||
@Volatile
|
||||
var abort: Boolean = false
|
||||
val localText: UTF16String = UTF16StringFactory.newString()
|
||||
private val parser = TSParser.create().also {
|
||||
it.language = languageSpec.language
|
||||
}
|
||||
var tree: TSTree? = null
|
||||
|
||||
fun offerMessage(what: Int, obj: Any?) {
|
||||
val msg = Message.obtain()
|
||||
msg.what = what
|
||||
msg.obj = obj
|
||||
offerMessage(msg)
|
||||
}
|
||||
|
||||
fun offerMessage(msg: Message) {
|
||||
// Result ignored: capacity is enough as it is INT_MAX
|
||||
messageQueue.offer(msg)
|
||||
}
|
||||
|
||||
fun updateStyles() {
|
||||
val scopedVariables = TsScopedVariables(tree!!, localText, languageSpec)
|
||||
if (thread == this && messageQueue.isEmpty()) {
|
||||
val oldTree = (styles.spans as LineSpansGenerator?)?.tree
|
||||
val copied = tree!!.copy()
|
||||
styles.spans = LineSpansGenerator(
|
||||
copied,
|
||||
reference!!.lineCount,
|
||||
reference!!.reference,
|
||||
theme,
|
||||
languageSpec,
|
||||
scopedVariables,
|
||||
spanFactory
|
||||
)
|
||||
val oldBlocks = styles.blocks
|
||||
updateCodeBlocks()
|
||||
if (oldBlocks != null) {
|
||||
ObjectAllocator.recycleBlockLines(oldBlocks)
|
||||
}
|
||||
currentReceiver?.setStyles(this@TsAnalyzeManager, styles) {
|
||||
oldTree?.close()
|
||||
}
|
||||
currentReceiver?.updateBracketProvider(
|
||||
this@TsAnalyzeManager,
|
||||
TsBracketPairs(copied, languageSpec)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCodeBlocks() {
|
||||
if (languageSpec.blocksQuery.patternCount == 0 || !languageSpec.blocksQuery.canAccess()) {
|
||||
return
|
||||
}
|
||||
val blocks = mutableListOf<CodeBlock>()
|
||||
TSQueryCursor.create().use {
|
||||
it.exec(languageSpec.blocksQuery, tree!!.rootNode)
|
||||
var match = it.nextMatch()
|
||||
while (match != null) {
|
||||
if (languageSpec.blocksPredicator.doPredicate(
|
||||
languageSpec.predicates,
|
||||
localText,
|
||||
match
|
||||
)
|
||||
) {
|
||||
match.captures.forEach {
|
||||
val block = ObjectAllocator.obtainBlockLine().also { block ->
|
||||
var node = it.node
|
||||
val start = node.startPoint
|
||||
block.startLine = start.row
|
||||
block.startColumn = start.column / 2
|
||||
val end = if (languageSpec.blocksQuery.getCaptureNameForId(it.index)
|
||||
.endsWith(".marked")
|
||||
) {
|
||||
// Goto last terminal element
|
||||
while (node.childCount > 0) {
|
||||
node = node.getChild(node.childCount - 1)
|
||||
}
|
||||
node.startPoint
|
||||
} else {
|
||||
node.endPoint
|
||||
}
|
||||
block.endLine = end.row
|
||||
block.endColumn = end.column / 2
|
||||
}
|
||||
if (block.endLine - block.startLine > 1) {
|
||||
blocks.add(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
match = it.nextMatch()
|
||||
}
|
||||
}
|
||||
val distinct = blocks.distinct().toMutableList()
|
||||
styles.blocks = distinct
|
||||
styles.finishBuilding()
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
while (!abort && !isInterrupted) {
|
||||
val msg = messageQueue.take()
|
||||
if (!handleMessage(msg)) {
|
||||
break
|
||||
}
|
||||
msg.recycle()
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
// ignored
|
||||
}
|
||||
releaseThreadResources()
|
||||
}
|
||||
|
||||
fun handleMessage(msg: Message): Boolean {
|
||||
try {
|
||||
when (msg.what) {
|
||||
MSG_INIT -> {
|
||||
localText.append(msg.obj!! as String)
|
||||
if (!abort && !isInterrupted) {
|
||||
tree = parser.parseString(localText)
|
||||
updateStyles()
|
||||
}
|
||||
}
|
||||
|
||||
MSG_MOD -> {
|
||||
if (!abort && !isInterrupted) {
|
||||
val modification = msg.obj!! as TextModification
|
||||
val newText = modification.changedText
|
||||
val t = tree!!
|
||||
t.edit(modification.tsEdition)
|
||||
if (newText == null) {
|
||||
localText.delete(modification.start, modification.end)
|
||||
} else {
|
||||
if (modification.start == localText.length) {
|
||||
localText.append(newText)
|
||||
} else {
|
||||
localText.insert(modification.start, newText)
|
||||
}
|
||||
}
|
||||
tree = parser.parseString(t, localText)
|
||||
t.close()
|
||||
updateStyles()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.w(
|
||||
"TsAnalyzeManager",
|
||||
"Thread $name exited with an error",
|
||||
e
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun releaseThreadResources() {
|
||||
parser.close()
|
||||
tree?.close()
|
||||
localText.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private data class TextModification(
|
||||
val start: Int,
|
||||
val end: Int,
|
||||
val tsEdition: TSInputEdit,
|
||||
/**
|
||||
* null for deletion
|
||||
*/
|
||||
val changedText: String?
|
||||
override fun insert(start: CharPosition, end: CharPosition, insertedContent: CharSequence) {
|
||||
thread?.offerMessage(
|
||||
MSG_MOD,
|
||||
TextModification(
|
||||
start.index,
|
||||
end.index,
|
||||
TSInputEdit.create(
|
||||
start.index * 2,
|
||||
start.index * 2,
|
||||
end.index * 2,
|
||||
start.toTSPoint(),
|
||||
start.toTSPoint(),
|
||||
end.toTSPoint()
|
||||
),
|
||||
insertedContent.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun delete(start: CharPosition, end: CharPosition, deletedContent: CharSequence) {
|
||||
thread?.offerMessage(
|
||||
MSG_MOD,
|
||||
TextModification(
|
||||
start.index,
|
||||
end.index,
|
||||
TSInputEdit.create(
|
||||
start.index * 2,
|
||||
end.index * 2,
|
||||
start.index * 2,
|
||||
start.toTSPoint(),
|
||||
end.toTSPoint(),
|
||||
start.toTSPoint()
|
||||
),
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun rerun() {
|
||||
thread?.let {
|
||||
if (it.isAlive) {
|
||||
it.interrupt()
|
||||
it.abort = true
|
||||
}
|
||||
}
|
||||
|
||||
// thread should handle this
|
||||
// (styles.spans as LineSpansGenerator?)?.tree?.close()
|
||||
|
||||
styles.spans = null
|
||||
val initText = reference?.reference?.toString() ?: ""
|
||||
thread = TsLooperThread().also {
|
||||
it.name = "TsDaemon-${nextThreadId()}"
|
||||
styles = Styles()
|
||||
it.offerMessage(MSG_INIT, initText)
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun destroy() {
|
||||
thread?.let {
|
||||
if (it.isAlive) {
|
||||
it.interrupt()
|
||||
it.abort = true
|
||||
}
|
||||
}
|
||||
// thread should handle this
|
||||
// (styles.spans as LineSpansGenerator?)?.tree?.close()
|
||||
spanFactory.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val MSG_BASE = 11451400
|
||||
private const val MSG_INIT = MSG_BASE + 1
|
||||
private const val MSG_MOD = MSG_BASE + 2
|
||||
|
||||
@Volatile
|
||||
private var threadId = 0
|
||||
|
||||
@Synchronized
|
||||
fun nextThreadId() = ++threadId
|
||||
}
|
||||
|
||||
inner class TsLooperThread : Thread() {
|
||||
|
||||
private val messageQueue = LinkedBlockingQueue<Message>()
|
||||
|
||||
@Volatile
|
||||
var abort: Boolean = false
|
||||
val localText: UTF16String = UTF16StringFactory.newString()
|
||||
private val parser = TSParser.create().also {
|
||||
it.language = languageSpec.language
|
||||
}
|
||||
var tree: TSTree? = null
|
||||
var spansGenerator: LineSpansGenerator? = null
|
||||
|
||||
fun offerMessage(what: Int, obj: Any?) {
|
||||
val msg = Message.obtain()
|
||||
msg.what = what
|
||||
msg.obj = obj
|
||||
offerMessage(msg)
|
||||
}
|
||||
|
||||
fun offerMessage(msg: Message) {
|
||||
// Result ignored: capacity is enough as it is INT_MAX
|
||||
messageQueue.offer(msg)
|
||||
}
|
||||
|
||||
fun updateStyles() {
|
||||
val scopedVariables = TsScopedVariables(tree!!, localText, languageSpec)
|
||||
if (thread != this || !messageQueue.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val oldTree = (styles.spans as LineSpansGenerator?)?.tree
|
||||
val copied = tree!!.copy()
|
||||
styles.spans = LineSpansGenerator(
|
||||
copied,
|
||||
reference!!.lineCount,
|
||||
reference!!.reference,
|
||||
theme,
|
||||
languageSpec,
|
||||
scopedVariables,
|
||||
spanFactory
|
||||
).also {
|
||||
spansGenerator = it
|
||||
}
|
||||
val oldBlocks = styles.blocks
|
||||
updateCodeBlocks()
|
||||
if (oldBlocks != null) {
|
||||
ObjectAllocator.recycleBlockLines(oldBlocks)
|
||||
}
|
||||
currentReceiver?.setStyles(this@TsAnalyzeManager, styles) {
|
||||
oldTree?.close()
|
||||
}
|
||||
currentReceiver?.updateBracketProvider(
|
||||
this@TsAnalyzeManager,
|
||||
TsBracketPairs(copied, languageSpec)
|
||||
)
|
||||
}
|
||||
|
||||
fun updateCodeBlocks() {
|
||||
if (languageSpec.blocksQuery.patternCount == 0 || !languageSpec.blocksQuery.canAccess()) {
|
||||
return
|
||||
}
|
||||
val blocks = mutableListOf<CodeBlock>()
|
||||
TSQueryCursor.create().use {
|
||||
if (tree?.canAccess() != true) {
|
||||
throw IllegalStateException("Cannot access tree")
|
||||
}
|
||||
|
||||
val rootNode = tree!!.rootNode
|
||||
it.exec(languageSpec.blocksQuery, rootNode)
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
|
||||
var match = it.nextMatch()
|
||||
while (match != null) {
|
||||
if (languageSpec.blocksPredicator.doPredicate(
|
||||
languageSpec.predicates,
|
||||
localText,
|
||||
match
|
||||
)
|
||||
) {
|
||||
match.captures.forEach { capture ->
|
||||
val block = ObjectAllocator.obtainBlockLine().also { block ->
|
||||
var node = capture.node
|
||||
val start = node.startPoint
|
||||
block.startLine = start.row
|
||||
block.startColumn = start.column / 2
|
||||
val end = if (languageSpec.blocksQuery.getCaptureNameForId(capture.index)
|
||||
.endsWith(".marked")
|
||||
) {
|
||||
// Goto last terminal element
|
||||
while (node.childCount > 0) {
|
||||
node = node.getChild(node.childCount - 1)
|
||||
}
|
||||
node.startPoint
|
||||
} else {
|
||||
node.endPoint
|
||||
}
|
||||
block.endLine = end.row
|
||||
block.endColumn = end.column / 2
|
||||
}
|
||||
if (block.endLine - block.startLine > 1) {
|
||||
blocks.add(block)
|
||||
}
|
||||
|
||||
(capture as? TreeSitterQueryCapture?)?.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
(match as? TreeSitterQueryMatch?)?.recycle()
|
||||
|
||||
if (tree?.canAccess() != true || rootNode.hasChanges()) {
|
||||
val hasChanges = rootNode.hasChanges()
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
blocks.clear()
|
||||
throw IllegalStateException(
|
||||
"Tree closed while querying, rootNode.hasChanges=$hasChanges")
|
||||
}
|
||||
|
||||
match = it.nextMatch()
|
||||
}
|
||||
}
|
||||
val distinct = blocks.distinct().toMutableList()
|
||||
styles.blocks = distinct
|
||||
styles.finishBuilding()
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
while (!abort && !isInterrupted) {
|
||||
val msg = messageQueue.take()
|
||||
if (!handleMessage(msg)) {
|
||||
break
|
||||
}
|
||||
msg.recycle()
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
// ignored
|
||||
}
|
||||
releaseThreadResources()
|
||||
}
|
||||
|
||||
fun handleMessage(msg: Message): Boolean {
|
||||
try {
|
||||
when (msg.what) {
|
||||
MSG_INIT -> {
|
||||
localText.append(msg.obj!! as String)
|
||||
if (!abort && !isInterrupted) {
|
||||
tree = parser.parseString(localText)
|
||||
updateStyles()
|
||||
}
|
||||
}
|
||||
|
||||
MSG_MOD -> {
|
||||
if (!abort && !isInterrupted) {
|
||||
val modification = msg.obj!! as TextModification
|
||||
spansGenerator?.apply {
|
||||
lineCount = reference!!.lineCount
|
||||
edit(modification.tsEdition)
|
||||
}
|
||||
val newText = modification.changedText
|
||||
val t = tree!!
|
||||
t.edit(modification.tsEdition)
|
||||
if (newText == null) {
|
||||
localText.delete(modification.start, modification.end)
|
||||
} else {
|
||||
if (modification.start == localText.length) {
|
||||
localText.append(newText)
|
||||
} else {
|
||||
localText.insert(modification.start, newText)
|
||||
}
|
||||
}
|
||||
(modification.tsEdition as? TreeSitterInputEdit?)?.recycle()
|
||||
tree = parser.parseString(t, localText)
|
||||
t.close()
|
||||
updateStyles()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.w(
|
||||
"TsAnalyzeManager",
|
||||
"Thread $name exited with an error",
|
||||
e
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun releaseThreadResources() {
|
||||
parser.close()
|
||||
tree?.close()
|
||||
localText.close()
|
||||
|
||||
try {
|
||||
spansGenerator?.tree?.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
spansGenerator = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private data class TextModification(
|
||||
val start: Int,
|
||||
val end: Int,
|
||||
val tsEdition: TSInputEdit,
|
||||
/**
|
||||
* null for deletion
|
||||
*/
|
||||
val changedText: String?
|
||||
)
|
||||
}
|
||||
+68
-50
@@ -27,68 +27,86 @@ package io.github.rosemoe.sora.editor.ts
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSQueryCursor
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterNode
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryCapture
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryMatch
|
||||
import io.github.rosemoe.sora.lang.brackets.BracketsProvider
|
||||
import io.github.rosemoe.sora.lang.brackets.PairedBracket
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
import java.lang.Math.max
|
||||
|
||||
class TsBracketPairs(
|
||||
private val tree: TSTree,
|
||||
private val languageSpec: TsLanguageSpec
|
||||
private val tree: TSTree,
|
||||
private val languageSpec: TsLanguageSpec
|
||||
) : BracketsProvider {
|
||||
|
||||
companion object {
|
||||
companion object {
|
||||
|
||||
val OPEN_NAME = "editor.brackets.open"
|
||||
val CLOSE_NAME = "editor.brackets.close"
|
||||
val OPEN_NAME = "editor.brackets.open"
|
||||
val CLOSE_NAME = "editor.brackets.close"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPairedBracketAt(text: Content, index: Int): PairedBracket? {
|
||||
if (languageSpec.bracketsQuery.patternCount > 0 && languageSpec.bracketsQuery.canAccess() && tree.canAccess()) {
|
||||
TSQueryCursor.create().use { cursor ->
|
||||
cursor.setByteRange(max(0, index - 1) * 2, index * 2 + 1)
|
||||
cursor.exec(languageSpec.bracketsQuery, tree.rootNode)
|
||||
var match = cursor.nextMatch()
|
||||
var matched = false
|
||||
while (match != null && !matched) {
|
||||
if (languageSpec.bracketsPredicator.doPredicate(
|
||||
languageSpec.predicates,
|
||||
text,
|
||||
match
|
||||
)
|
||||
) {
|
||||
var startNode: TSNode? = null
|
||||
var endNode: TSNode? = null
|
||||
for (capture in match.captures) {
|
||||
val captureName =
|
||||
languageSpec.bracketsQuery.getCaptureNameForId(capture.index)
|
||||
if (captureName == OPEN_NAME || captureName == CLOSE_NAME) {
|
||||
val node = capture.node
|
||||
if (index >= node.startByte / 2 && index <= node.endByte / 2) {
|
||||
matched = true
|
||||
}
|
||||
if (captureName == OPEN_NAME) {
|
||||
startNode = node
|
||||
} else {
|
||||
endNode = node
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matched && startNode != null && endNode != null) {
|
||||
return PairedBracket(
|
||||
startNode.startByte / 2,
|
||||
(startNode.endByte - startNode.startByte) / 2,
|
||||
endNode.startByte / 2,
|
||||
(endNode.endByte - endNode.startByte) / 2
|
||||
)
|
||||
}
|
||||
}
|
||||
match = cursor.nextMatch()
|
||||
}
|
||||
}
|
||||
override fun getPairedBracketAt(text: Content, index: Int): PairedBracket? {
|
||||
if (languageSpec.bracketsQuery.patternCount > 0 && languageSpec.bracketsQuery.canAccess() && tree.canAccess()) {
|
||||
TSQueryCursor.create().use { cursor ->
|
||||
cursor.setByteRange(max(0, index - 1) * 2, index * 2 + 1)
|
||||
if (!tree.canAccess()) {
|
||||
throw IllegalStateException("Cannot access tree")
|
||||
}
|
||||
return null
|
||||
val rootNode = tree.rootNode
|
||||
cursor.exec(languageSpec.bracketsQuery, rootNode)
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
var match = cursor.nextMatch()
|
||||
var matched = false
|
||||
while (match != null && !matched) {
|
||||
if (languageSpec.bracketsPredicator.doPredicate(
|
||||
languageSpec.predicates,
|
||||
text,
|
||||
match
|
||||
)
|
||||
) {
|
||||
var startNode: TSNode? = null
|
||||
var endNode: TSNode? = null
|
||||
for (capture in match.captures) {
|
||||
val captureName =
|
||||
languageSpec.bracketsQuery.getCaptureNameForId(capture.index)
|
||||
if (captureName == OPEN_NAME || captureName == CLOSE_NAME) {
|
||||
val node = capture.node
|
||||
if (index >= node.startByte / 2 && index <= node.endByte / 2) {
|
||||
matched = true
|
||||
}
|
||||
if (captureName == OPEN_NAME) {
|
||||
startNode = node
|
||||
} else {
|
||||
endNode = node
|
||||
}
|
||||
}
|
||||
|
||||
(capture as? TreeSitterQueryCapture?)?.recycle()
|
||||
}
|
||||
if (matched && startNode != null && endNode != null) {
|
||||
return PairedBracket(
|
||||
startNode.startByte / 2,
|
||||
(startNode.endByte - startNode.startByte) / 2,
|
||||
endNode.startByte / 2,
|
||||
(endNode.endByte - endNode.startByte) / 2
|
||||
)
|
||||
}
|
||||
}
|
||||
(match as? TreeSitterQueryMatch?)?.recycle()
|
||||
|
||||
if (!tree.canAccess() || rootNode.hasChanges()) {
|
||||
val hasChanges = rootNode.hasChanges()
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
throw IllegalStateException(
|
||||
"Tree closed or edited while querying, rootNode.hasChanges=$hasChanges")
|
||||
}
|
||||
match = cursor.nextMatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,66 +47,66 @@ import io.github.rosemoe.sora.widget.SymbolPairMatch
|
||||
* @author Rosemoe
|
||||
*/
|
||||
open class TsLanguage(
|
||||
val languageSpec: TsLanguageSpec,
|
||||
val tab: Boolean = false,
|
||||
themeDescription: TsThemeBuilder.() -> Unit
|
||||
val languageSpec: TsLanguageSpec,
|
||||
val tab: Boolean = false,
|
||||
themeDescription: TsThemeBuilder.() -> Unit
|
||||
) : Language {
|
||||
|
||||
init {
|
||||
if (languageSpec.closed) {
|
||||
throw IllegalStateException("spec is closed")
|
||||
}
|
||||
init {
|
||||
if (languageSpec.closed) {
|
||||
throw IllegalStateException("spec is closed")
|
||||
}
|
||||
}
|
||||
|
||||
protected var tsTheme = TsThemeBuilder(languageSpec.tsQuery).apply { themeDescription() }.theme
|
||||
protected var tsTheme = TsThemeBuilder(languageSpec.tsQuery).apply { themeDescription() }.theme
|
||||
|
||||
open val analyzer by lazy {
|
||||
TsAnalyzeManager(languageSpec, tsTheme)
|
||||
open val analyzer by lazy {
|
||||
TsAnalyzeManager(languageSpec, tsTheme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tree-sitter colorizing theme with the given description
|
||||
*/
|
||||
fun updateTheme(themeDescription: TsThemeBuilder.() -> Unit) = languageSpec.let {
|
||||
if (it.closed) {
|
||||
throw IllegalStateException("spec is closed")
|
||||
}
|
||||
updateTheme(TsThemeBuilder(languageSpec.tsQuery).apply { themeDescription() }.theme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tree-sitter colorizing theme with the given description
|
||||
*/
|
||||
fun updateTheme(themeDescription: TsThemeBuilder.() -> Unit) = languageSpec.let {
|
||||
if (it.closed) {
|
||||
throw IllegalStateException("spec is closed")
|
||||
}
|
||||
updateTheme(TsThemeBuilder(languageSpec.tsQuery).apply { themeDescription() }.theme)
|
||||
}
|
||||
/**
|
||||
* Update tree-sitter colorizing theme
|
||||
*/
|
||||
fun updateTheme(theme: TsTheme) {
|
||||
this.tsTheme = theme
|
||||
analyzer.updateTheme(theme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tree-sitter colorizing theme
|
||||
*/
|
||||
fun updateTheme(theme: TsTheme) {
|
||||
this.tsTheme = theme
|
||||
analyzer.updateTheme(theme)
|
||||
}
|
||||
override fun getAnalyzeManager() = analyzer
|
||||
|
||||
override fun getAnalyzeManager() = analyzer
|
||||
override fun getInterruptionLevel() = Language.INTERRUPTION_LEVEL_STRONG
|
||||
|
||||
override fun getInterruptionLevel() = Language.INTERRUPTION_LEVEL_STRONG
|
||||
override fun requireAutoComplete(
|
||||
content: ContentReference,
|
||||
position: CharPosition,
|
||||
publisher: CompletionPublisher,
|
||||
extraArguments: Bundle
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
override fun requireAutoComplete(
|
||||
content: ContentReference,
|
||||
position: CharPosition,
|
||||
publisher: CompletionPublisher,
|
||||
extraArguments: Bundle
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
override fun getIndentAdvance(content: ContentReference, line: Int, column: Int) = 0
|
||||
|
||||
override fun getIndentAdvance(content: ContentReference, line: Int, column: Int) = 0
|
||||
override fun useTab() = tab
|
||||
|
||||
override fun useTab() = tab
|
||||
override fun getFormatter(): Formatter = EmptyLanguage.EmptyFormatter.INSTANCE
|
||||
|
||||
override fun getFormatter(): Formatter = EmptyLanguage.EmptyFormatter.INSTANCE
|
||||
override fun getSymbolPairs(): SymbolPairMatch = EmptyLanguage.EMPTY_SYMBOL_PAIRS
|
||||
|
||||
override fun getSymbolPairs(): SymbolPairMatch = EmptyLanguage.EMPTY_SYMBOL_PAIRS
|
||||
override fun getNewlineHandlers() = emptyArray<NewlineHandler>()
|
||||
|
||||
override fun getNewlineHandlers() = emptyArray<NewlineHandler>()
|
||||
|
||||
override fun destroy() {
|
||||
languageSpec.close()
|
||||
}
|
||||
override fun destroy() {
|
||||
languageSpec.close()
|
||||
}
|
||||
|
||||
}
|
||||
+112
-111
@@ -52,138 +52,139 @@ import java.io.Closeable
|
||||
* @param predicates Client custom predicate implementations
|
||||
*/
|
||||
open class TsLanguageSpec(
|
||||
val language: TSLanguage,
|
||||
highlightScmSource: String,
|
||||
codeBlocksScmSource: String = "",
|
||||
bracketsScmSource: String = "",
|
||||
localsScmSource: String = "",
|
||||
localsCaptureSpec: LocalsCaptureSpec = LocalsCaptureSpec.DEFAULT,
|
||||
val predicates: List<TsPredicate> = listOf(MatchPredicate)
|
||||
val language: TSLanguage,
|
||||
highlightScmSource: String,
|
||||
codeBlocksScmSource: String = "",
|
||||
bracketsScmSource: String = "",
|
||||
localsScmSource: String = "",
|
||||
localsCaptureSpec: LocalsCaptureSpec = LocalsCaptureSpec.DEFAULT,
|
||||
val predicates: List<TsPredicate> = listOf(MatchPredicate)
|
||||
) : Closeable {
|
||||
|
||||
/**
|
||||
* The generated scm source code for querying
|
||||
*/
|
||||
val querySource = localsScmSource + "\n" + highlightScmSource
|
||||
/**
|
||||
* The generated scm source code for querying
|
||||
*/
|
||||
val querySource = localsScmSource + "\n" + highlightScmSource
|
||||
|
||||
/**
|
||||
* Offset of highlighting scm source code in [querySource]
|
||||
*/
|
||||
val highlightScmOffset = localsScmSource.encodeToByteArray().size + 1
|
||||
/**
|
||||
* Offset of highlighting scm source code in [querySource]
|
||||
*/
|
||||
val highlightScmOffset = localsScmSource.encodeToByteArray().size + 1
|
||||
|
||||
/**
|
||||
* The actual [TSQuery] object
|
||||
*/
|
||||
val tsQuery = TSQuery.create(language, querySource)
|
||||
/**
|
||||
* The actual [TSQuery] object
|
||||
*/
|
||||
val tsQuery = TSQuery.create(language, querySource)
|
||||
|
||||
/**
|
||||
* The first index of highlighting pattern
|
||||
*/
|
||||
val highlightPatternOffset: Int
|
||||
/**
|
||||
* The first index of highlighting pattern
|
||||
*/
|
||||
val highlightPatternOffset: Int
|
||||
|
||||
/**
|
||||
* Indices of variable definition patterns
|
||||
*/
|
||||
val localsDefinitionIndices = mutableListOf<Int>()
|
||||
/**
|
||||
* Indices of variable definition patterns
|
||||
*/
|
||||
val localsDefinitionIndices = mutableListOf<Int>()
|
||||
|
||||
/**
|
||||
* Indices of variable reference patterns
|
||||
*/
|
||||
val localsReferenceIndices = mutableListOf<Int>()
|
||||
/**
|
||||
* Indices of variable reference patterns
|
||||
*/
|
||||
val localsReferenceIndices = mutableListOf<Int>()
|
||||
|
||||
/**
|
||||
* Indices of variable scope patterns
|
||||
*/
|
||||
val localsScopeIndices = mutableListOf<Int>()
|
||||
/**
|
||||
* Indices of variable scope patterns
|
||||
*/
|
||||
val localsScopeIndices = mutableListOf<Int>()
|
||||
|
||||
/**
|
||||
* Indices of weak variable scope patterns
|
||||
* @see [LocalsCaptureSpec.isMembersScopeCapture] for more information
|
||||
*/
|
||||
val localsMembersScopeIndices = mutableListOf<Int>()
|
||||
/**
|
||||
* Indices of weak variable scope patterns
|
||||
* @see [LocalsCaptureSpec.isMembersScopeCapture] for more information
|
||||
*/
|
||||
val localsMembersScopeIndices = mutableListOf<Int>()
|
||||
|
||||
/**
|
||||
* Indices of variable definition-value patterns. Currently unused in analysis.
|
||||
*/
|
||||
val localsDefinitionValueIndices = mutableListOf<Int>()
|
||||
/**
|
||||
* Indices of variable definition-value patterns. Currently unused in analysis.
|
||||
*/
|
||||
val localsDefinitionValueIndices = mutableListOf<Int>()
|
||||
|
||||
val blocksQuery = if (codeBlocksScmSource.isBlank()) {
|
||||
TSQuery.EMPTY
|
||||
} else TSQuery.create(language, codeBlocksScmSource)
|
||||
val blocksQuery = if (codeBlocksScmSource.isBlank()) {
|
||||
TSQuery.EMPTY
|
||||
} else TSQuery.create(language, codeBlocksScmSource)
|
||||
|
||||
val bracketsQuery = if (bracketsScmSource.isBlank()) {
|
||||
TSQuery.EMPTY
|
||||
} else TSQuery.create(language, bracketsScmSource)
|
||||
val bracketsQuery = if (bracketsScmSource.isBlank()) {
|
||||
TSQuery.EMPTY
|
||||
} else TSQuery.create(language, bracketsScmSource)
|
||||
|
||||
init {
|
||||
// Check the queries before access
|
||||
try {
|
||||
blocksQuery.validateOrThrow("code-blocks")
|
||||
bracketsQuery.validateOrThrow("brackets")
|
||||
querySource.forEach {
|
||||
if (it > 0xFF.toChar()) {
|
||||
throw IllegalArgumentException("use non-ASCII characters in scm source is unexpected")
|
||||
}
|
||||
}
|
||||
if (!tsQuery.canAccess()) {
|
||||
throw IllegalArgumentException("Syntax highlights query is invalid")
|
||||
}
|
||||
if (tsQuery.errorType != TSQueryError.None) {
|
||||
val region = if (tsQuery.errorOffset < highlightScmOffset) "locals" else "highlight"
|
||||
val offset =
|
||||
if (tsQuery.errorOffset < highlightScmOffset) tsQuery.errorOffset else tsQuery.errorOffset - highlightScmOffset
|
||||
throw IllegalArgumentException("bad scm sources: error ${tsQuery.errorType.name} occurs in $region range at offset $offset")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
tsQuery.close()
|
||||
blocksQuery.close()
|
||||
bracketsQuery.close()
|
||||
throw e
|
||||
init {
|
||||
// Check the queries before access
|
||||
try {
|
||||
blocksQuery.validateOrThrow("code-blocks")
|
||||
bracketsQuery.validateOrThrow("brackets")
|
||||
querySource.forEach {
|
||||
if (it > 0xFF.toChar()) {
|
||||
throw IllegalArgumentException("use non-ASCII characters in scm source is unexpected")
|
||||
}
|
||||
}
|
||||
if (!tsQuery.canAccess()) {
|
||||
throw IllegalArgumentException("Syntax highlights query is invalid")
|
||||
}
|
||||
if (tsQuery.errorType != TSQueryError.None) {
|
||||
val region = if (tsQuery.errorOffset < highlightScmOffset) "locals" else "highlight"
|
||||
val offset =
|
||||
if (tsQuery.errorOffset < highlightScmOffset) tsQuery.errorOffset else tsQuery.errorOffset - highlightScmOffset
|
||||
throw IllegalArgumentException(
|
||||
"bad scm sources: error ${tsQuery.errorType.name} occurs in $region range at offset $offset")
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
tsQuery.close()
|
||||
blocksQuery.close()
|
||||
bracketsQuery.close()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
val queryPredicator = Predicator(tsQuery)
|
||||
val queryPredicator = Predicator(tsQuery)
|
||||
|
||||
val blocksPredicator = Predicator(blocksQuery)
|
||||
val blocksPredicator = Predicator(blocksQuery)
|
||||
|
||||
val bracketsPredicator = Predicator(bracketsQuery)
|
||||
val bracketsPredicator = Predicator(bracketsQuery)
|
||||
|
||||
/**
|
||||
* Close flag
|
||||
*/
|
||||
var closed = false
|
||||
private set
|
||||
/**
|
||||
* Close flag
|
||||
*/
|
||||
var closed = false
|
||||
private set
|
||||
|
||||
init {
|
||||
var highlightOffset = 0
|
||||
for (i in 0 until tsQuery.captureCount) {
|
||||
// Only locals in localsScm are taken down
|
||||
val name = tsQuery.getCaptureNameForId(i)
|
||||
if (localsCaptureSpec.isDefinitionCapture(name)) {
|
||||
localsDefinitionIndices.add(i)
|
||||
} else if (localsCaptureSpec.isReferenceCapture(name)) {
|
||||
localsReferenceIndices.add(i)
|
||||
} else if (localsCaptureSpec.isScopeCapture(name)) {
|
||||
localsScopeIndices.add(i)
|
||||
} else if (localsCaptureSpec.isDefinitionValueCapture(name)) {
|
||||
localsDefinitionValueIndices.add(i)
|
||||
} else if (localsCaptureSpec.isMembersScopeCapture(name)) {
|
||||
localsMembersScopeIndices.add(i)
|
||||
}
|
||||
}
|
||||
for (i in 0 until tsQuery.patternCount) {
|
||||
if (tsQuery.getStartByteForPattern(i) < highlightScmOffset) {
|
||||
highlightOffset++
|
||||
}
|
||||
}
|
||||
highlightPatternOffset = highlightOffset
|
||||
init {
|
||||
var highlightOffset = 0
|
||||
for (i in 0 until tsQuery.captureCount) {
|
||||
// Only locals in localsScm are taken down
|
||||
val name = tsQuery.getCaptureNameForId(i)
|
||||
if (localsCaptureSpec.isDefinitionCapture(name)) {
|
||||
localsDefinitionIndices.add(i)
|
||||
} else if (localsCaptureSpec.isReferenceCapture(name)) {
|
||||
localsReferenceIndices.add(i)
|
||||
} else if (localsCaptureSpec.isScopeCapture(name)) {
|
||||
localsScopeIndices.add(i)
|
||||
} else if (localsCaptureSpec.isDefinitionValueCapture(name)) {
|
||||
localsDefinitionValueIndices.add(i)
|
||||
} else if (localsCaptureSpec.isMembersScopeCapture(name)) {
|
||||
localsMembersScopeIndices.add(i)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
tsQuery.close()
|
||||
blocksQuery.close()
|
||||
bracketsQuery.close()
|
||||
closed = true
|
||||
for (i in 0 until tsQuery.patternCount) {
|
||||
if (tsQuery.getStartByteForPattern(i) < highlightScmOffset) {
|
||||
highlightOffset++
|
||||
}
|
||||
}
|
||||
highlightPatternOffset = highlightOffset
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
tsQuery.close()
|
||||
blocksQuery.close()
|
||||
bracketsQuery.close()
|
||||
closed = true
|
||||
}
|
||||
|
||||
}
|
||||
+108
-86
@@ -28,6 +28,9 @@ import com.itsaky.androidide.treesitter.TSNode
|
||||
import com.itsaky.androidide.treesitter.TSQueryCapture
|
||||
import com.itsaky.androidide.treesitter.TSQueryCursor
|
||||
import com.itsaky.androidide.treesitter.TSTree
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterNode
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryCapture
|
||||
import com.itsaky.androidide.treesitter.api.TreeSitterQueryMatch
|
||||
import com.itsaky.androidide.treesitter.string.UTF16String
|
||||
import java.util.Stack
|
||||
|
||||
@@ -42,98 +45,117 @@ import java.util.Stack
|
||||
*/
|
||||
class TsScopedVariables(tree: TSTree, text: UTF16String, val spec: TsLanguageSpec) {
|
||||
|
||||
private val rootScope: Scope
|
||||
private val rootScope: Scope
|
||||
|
||||
init {
|
||||
rootScope = Scope(0, tree.rootNode.endByte / 2)
|
||||
if (spec.localsDefinitionIndices.isNotEmpty()) {
|
||||
TSQueryCursor.create().use { cursor ->
|
||||
cursor.exec(spec.tsQuery, tree.rootNode)
|
||||
var match = cursor.nextMatch()
|
||||
val captures = mutableListOf<TSQueryCapture>()
|
||||
while (match != null) {
|
||||
if (spec.queryPredicator.doPredicate(spec.predicates, text, match)) {
|
||||
captures.addAll(match.captures)
|
||||
}
|
||||
match = cursor.nextMatch()
|
||||
}
|
||||
captures.sortBy { it.node.startByte }
|
||||
val scopeStack = Stack<Scope>()
|
||||
var lastAddedVariableNode: TSNode? = null
|
||||
scopeStack.push(rootScope)
|
||||
for (capture in captures) {
|
||||
val startIndex = capture.node.startByte / 2
|
||||
val endIndex = capture.node.endByte / 2
|
||||
while (startIndex >= scopeStack.peek().endIndex) {
|
||||
scopeStack.pop()
|
||||
}
|
||||
val pattern = capture.index
|
||||
if (pattern in spec.localsScopeIndices) {
|
||||
val newScope = Scope(startIndex, endIndex)
|
||||
scopeStack.peek().childScopes.add(newScope)
|
||||
scopeStack.push(newScope)
|
||||
} else if (pattern in spec.localsMembersScopeIndices) {
|
||||
val newScope = Scope(startIndex, endIndex, true)
|
||||
scopeStack.peek().childScopes.add(newScope)
|
||||
scopeStack.push(newScope)
|
||||
} else if (pattern in spec.localsDefinitionIndices) {
|
||||
val scope = scopeStack.peek()
|
||||
val utf16Name = text.subseqChars(startIndex, endIndex)
|
||||
val name = utf16Name.toString()
|
||||
utf16Name.close()
|
||||
val scopedVar = ScopedVariable(
|
||||
name,
|
||||
if (scope.forMembers) scope.startIndex else startIndex,
|
||||
scope.endIndex
|
||||
)
|
||||
scope.variables.add(scopedVar)
|
||||
lastAddedVariableNode = capture.node
|
||||
} else if (pattern !in spec.localsDefinitionValueIndices && pattern !in spec.localsReferenceIndices && lastAddedVariableNode != null) {
|
||||
val topVariables = scopeStack.peek().variables
|
||||
if (topVariables.isNotEmpty()) {
|
||||
val topVariable = topVariables.last()
|
||||
if (lastAddedVariableNode.startByte / 2 == startIndex && lastAddedVariableNode.endByte / 2 == endIndex && topVariable.matchedHighlightPattern == -1) {
|
||||
topVariable.matchedHighlightPattern = pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
init {
|
||||
if (!tree.canAccess()) {
|
||||
throw IllegalStateException("Cannot access tree")
|
||||
}
|
||||
|
||||
data class Scope(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
val forMembers: Boolean = false,
|
||||
val variables: MutableList<ScopedVariable> = mutableListOf(),
|
||||
val childScopes: MutableList<Scope> = mutableListOf()
|
||||
)
|
||||
val rootNode = tree.rootNode
|
||||
rootScope = Scope(0, rootNode.endByte / 2)
|
||||
if (spec.localsDefinitionIndices.isNotEmpty()) {
|
||||
TSQueryCursor.create().use { cursor ->
|
||||
cursor.exec(spec.tsQuery, rootNode)
|
||||
var match = cursor.nextMatch()
|
||||
val captures = mutableListOf<TSQueryCapture>()
|
||||
while (match != null) {
|
||||
if (spec.queryPredicator.doPredicate(spec.predicates, text, match)) {
|
||||
captures.addAll(match.captures)
|
||||
}
|
||||
(match as? TreeSitterQueryMatch?)?.recycle()
|
||||
|
||||
data class ScopedVariable(
|
||||
var name: String,
|
||||
var scopeStartIndex: Int,
|
||||
var scopeEndIndex: Int,
|
||||
var matchedHighlightPattern: Int = -1
|
||||
)
|
||||
if (!tree.canAccess()) {
|
||||
val hasChanges = rootNode.hasChanges()
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
captures.clear()
|
||||
throw IllegalStateException(
|
||||
"Tree closed while querying, rootNode.hasChanges=$hasChanges")
|
||||
}
|
||||
|
||||
fun findDefinition(startIndex: Int, endIndex: Int, name: String): ScopedVariable? {
|
||||
var definition: ScopedVariable? = null
|
||||
var currentScope: Scope? = rootScope
|
||||
while (currentScope != null) {
|
||||
for (variable in currentScope.variables) {
|
||||
if (variable.scopeStartIndex > startIndex) {
|
||||
break
|
||||
}
|
||||
if (variable.scopeStartIndex <= startIndex && variable.scopeEndIndex >= endIndex && variable.name == name) {
|
||||
definition = variable
|
||||
// Do not break here: name can be shadowed in some languages
|
||||
}
|
||||
}
|
||||
currentScope =
|
||||
currentScope.childScopes.firstOrNull { scope -> scope.startIndex <= startIndex && scope.endIndex >= endIndex }
|
||||
match = cursor.nextMatch()
|
||||
}
|
||||
return definition
|
||||
captures.sortBy { it.node.startByte }
|
||||
val scopeStack = Stack<Scope>()
|
||||
var lastAddedVariableNode: TSNode? = null
|
||||
scopeStack.push(rootScope)
|
||||
for (capture in captures) {
|
||||
val startIndex = capture.node.startByte / 2
|
||||
val endIndex = capture.node.endByte / 2
|
||||
while (startIndex >= scopeStack.peek().endIndex) {
|
||||
scopeStack.pop()
|
||||
}
|
||||
val pattern = capture.index
|
||||
if (pattern in spec.localsScopeIndices) {
|
||||
val newScope = Scope(startIndex, endIndex)
|
||||
scopeStack.peek().childScopes.add(newScope)
|
||||
scopeStack.push(newScope)
|
||||
} else if (pattern in spec.localsMembersScopeIndices) {
|
||||
val newScope = Scope(startIndex, endIndex, true)
|
||||
scopeStack.peek().childScopes.add(newScope)
|
||||
scopeStack.push(newScope)
|
||||
} else if (pattern in spec.localsDefinitionIndices) {
|
||||
val scope = scopeStack.peek()
|
||||
val utf16Name = text.subseqChars(startIndex, endIndex)
|
||||
val name = utf16Name.toString()
|
||||
utf16Name.close()
|
||||
val scopedVar = ScopedVariable(
|
||||
name,
|
||||
if (scope.forMembers) scope.startIndex else startIndex,
|
||||
scope.endIndex
|
||||
)
|
||||
scope.variables.add(scopedVar)
|
||||
lastAddedVariableNode = capture.node
|
||||
} else if (pattern !in spec.localsDefinitionValueIndices && pattern !in spec.localsReferenceIndices && lastAddedVariableNode != null) {
|
||||
val topVariables = scopeStack.peek().variables
|
||||
if (topVariables.isNotEmpty()) {
|
||||
val topVariable = topVariables.last()
|
||||
if (lastAddedVariableNode.startByte / 2 == startIndex && lastAddedVariableNode.endByte / 2 == endIndex && topVariable.matchedHighlightPattern == -1) {
|
||||
topVariable.matchedHighlightPattern = pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(capture as? TreeSitterQueryCapture?)?.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(rootNode as? TreeSitterNode?)?.recycle()
|
||||
}
|
||||
|
||||
data class Scope(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
val forMembers: Boolean = false,
|
||||
val variables: MutableList<ScopedVariable> = mutableListOf(),
|
||||
val childScopes: MutableList<Scope> = mutableListOf()
|
||||
)
|
||||
|
||||
data class ScopedVariable(
|
||||
var name: String,
|
||||
var scopeStartIndex: Int,
|
||||
var scopeEndIndex: Int,
|
||||
var matchedHighlightPattern: Int = -1
|
||||
)
|
||||
|
||||
fun findDefinition(startIndex: Int, endIndex: Int, name: String): ScopedVariable? {
|
||||
var definition: ScopedVariable? = null
|
||||
var currentScope: Scope? = rootScope
|
||||
while (currentScope != null) {
|
||||
for (variable in currentScope.variables) {
|
||||
if (variable.scopeStartIndex > startIndex) {
|
||||
break
|
||||
}
|
||||
if (variable.scopeStartIndex <= startIndex && variable.scopeEndIndex >= endIndex && variable.name == name) {
|
||||
definition = variable
|
||||
// Do not break here: name can be shadowed in some languages
|
||||
}
|
||||
}
|
||||
currentScope =
|
||||
currentScope.childScopes.firstOrNull { scope -> scope.startIndex <= startIndex && scope.endIndex >= endIndex }
|
||||
}
|
||||
return definition
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,47 +44,47 @@ import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
|
||||
*/
|
||||
class TsTheme(private val tsQuery: TSQuery) {
|
||||
|
||||
private val styles = mutableMapOf<String, Long>()
|
||||
private val mapping = SparseLongArray()
|
||||
private val styles = mutableMapOf<String, Long>()
|
||||
private val mapping = SparseLongArray()
|
||||
|
||||
/**
|
||||
* The text style for normal texts
|
||||
*/
|
||||
var normalTextStyle = TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL)
|
||||
/**
|
||||
* The text style for normal texts
|
||||
*/
|
||||
var normalTextStyle = TextStyle.makeStyle(EditorColorScheme.TEXT_NORMAL)
|
||||
|
||||
/**
|
||||
* Set text style for the given rule string.
|
||||
*
|
||||
* @param rule The rule for locating nodes
|
||||
* @param style The style value for those nodes
|
||||
* @see io.github.rosemoe.sora.lang.styling.TextStyle
|
||||
*/
|
||||
fun putStyleRule(rule: String, style: Long) {
|
||||
styles[rule] = style
|
||||
mapping.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove rule
|
||||
* @param rule The rule for locating nodes
|
||||
*/
|
||||
fun eraseStyleRule(rule: String) = putStyleRule(rule, 0L)
|
||||
|
||||
fun resolveStyleForPattern(pattern: Int): Long {
|
||||
val index = mapping.indexOfKey(pattern)
|
||||
return if (index >= 0) {
|
||||
mapping.valueAt(index)
|
||||
} else {
|
||||
var mappedName = tsQuery.getCaptureNameForId(pattern)
|
||||
var style = styles[mappedName] ?: 0L
|
||||
while (style == 0L && mappedName.isNotEmpty()) {
|
||||
mappedName = mappedName.substringBeforeLast('.', "")
|
||||
style = styles[mappedName] ?: 0L
|
||||
}
|
||||
mapping.put(pattern, style)
|
||||
style
|
||||
}
|
||||
/**
|
||||
* Set text style for the given rule string.
|
||||
*
|
||||
* @param rule The rule for locating nodes
|
||||
* @param style The style value for those nodes
|
||||
* @see io.github.rosemoe.sora.lang.styling.TextStyle
|
||||
*/
|
||||
fun putStyleRule(rule: String, style: Long) {
|
||||
styles[rule] = style
|
||||
mapping.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove rule
|
||||
* @param rule The rule for locating nodes
|
||||
*/
|
||||
fun eraseStyleRule(rule: String) = putStyleRule(rule, 0L)
|
||||
|
||||
fun resolveStyleForPattern(pattern: Int): Long {
|
||||
val index = mapping.indexOfKey(pattern)
|
||||
return if (index >= 0) {
|
||||
mapping.valueAt(index)
|
||||
} else {
|
||||
var mappedName = tsQuery.getCaptureNameForId(pattern)
|
||||
var style = styles[mappedName] ?: 0L
|
||||
while (style == 0L && mappedName.isNotEmpty()) {
|
||||
mappedName = mappedName.substringBeforeLast('.', "")
|
||||
style = styles[mappedName] ?: 0L
|
||||
}
|
||||
mapping.put(pattern, style)
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -93,17 +93,17 @@ class TsTheme(private val tsQuery: TSQuery) {
|
||||
*/
|
||||
class TsThemeBuilder(tsQuery: TSQuery) {
|
||||
|
||||
internal val theme = TsTheme(tsQuery)
|
||||
internal val theme = TsTheme(tsQuery)
|
||||
|
||||
infix fun Long.applyTo(targetRule: String) {
|
||||
theme.putStyleRule(targetRule, this)
|
||||
}
|
||||
infix fun Long.applyTo(targetRule: String) {
|
||||
theme.putStyleRule(targetRule, this)
|
||||
}
|
||||
|
||||
infix fun Long.applyTo(targetRules: Array<String>) {
|
||||
targetRules.forEach {
|
||||
applyTo(it)
|
||||
}
|
||||
infix fun Long.applyTo(targetRules: Array<String>) {
|
||||
targetRules.forEach {
|
||||
applyTo(it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -111,4 +111,4 @@ class TsThemeBuilder(tsQuery: TSQuery) {
|
||||
* Build tree-sitter theme
|
||||
*/
|
||||
fun tsTheme(tsQuery: TSQuery, description: TsThemeBuilder.() -> Unit) =
|
||||
TsThemeBuilder(tsQuery).also { it.description() }.theme
|
||||
TsThemeBuilder(tsQuery).also { it.description() }.theme
|
||||
@@ -35,7 +35,8 @@ import io.github.rosemoe.sora.text.CharPosition
|
||||
fun CharPosition.toTSPoint() = TSPoint.create(line, column * 2)
|
||||
|
||||
fun TSQuery.validateOrThrow(name: String = "unknown") {
|
||||
if (errorType != TSQueryError.None) {
|
||||
throw IllegalArgumentException("query(name:$name) parsing failed: ${errorType.name} at text offset $errorOffset")
|
||||
}
|
||||
if (errorType != TSQueryError.None) {
|
||||
throw IllegalArgumentException(
|
||||
"query(name:$name) parsing failed: ${errorType.name} at text offset $errorOffset")
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -26,12 +26,13 @@ package io.github.rosemoe.sora.editor.ts.multilang
|
||||
|
||||
interface LanguagePriorityCheck {
|
||||
|
||||
companion object {
|
||||
val PRIORITY_NEVER = 0
|
||||
val PRIORITY_AS_FALLBACK = 1
|
||||
val PRIORITY_ALWAYS = 1000
|
||||
}
|
||||
companion object {
|
||||
|
||||
fun getPriorityByName(name: String) : Int
|
||||
val PRIORITY_NEVER = 0
|
||||
val PRIORITY_AS_FALLBACK = 1
|
||||
val PRIORITY_ALWAYS = 1000
|
||||
}
|
||||
|
||||
fun getPriorityByName(name: String): Int
|
||||
|
||||
}
|
||||
+18
-17
@@ -32,25 +32,26 @@ import io.github.rosemoe.sora.editor.ts.predicate.TsPredicate
|
||||
import io.github.rosemoe.sora.editor.ts.predicate.builtin.MatchPredicate
|
||||
|
||||
class TsInjectableLanguageSpec(
|
||||
language: TSLanguage,
|
||||
highlightScmSource: String,
|
||||
themeDescription: TsThemeBuilder.() -> Unit,
|
||||
val languageName: LanguagePriorityCheck,
|
||||
val indentHelper: TsIndentHelper? = null,
|
||||
codeBlocksScmSource: String = "",
|
||||
bracketsScmSource: String = "",
|
||||
localsScmSource: String = "",
|
||||
localsCaptureSpec: LocalsCaptureSpec = LocalsCaptureSpec.DEFAULT,
|
||||
predicates: List<TsPredicate> = listOf(MatchPredicate)
|
||||
) : TsLanguageSpec(language, highlightScmSource, codeBlocksScmSource, bracketsScmSource, localsScmSource, localsCaptureSpec, predicates) {
|
||||
language: TSLanguage,
|
||||
highlightScmSource: String,
|
||||
themeDescription: TsThemeBuilder.() -> Unit,
|
||||
val languageName: LanguagePriorityCheck,
|
||||
val indentHelper: TsIndentHelper? = null,
|
||||
codeBlocksScmSource: String = "",
|
||||
bracketsScmSource: String = "",
|
||||
localsScmSource: String = "",
|
||||
localsCaptureSpec: LocalsCaptureSpec = LocalsCaptureSpec.DEFAULT,
|
||||
predicates: List<TsPredicate> = listOf(MatchPredicate)
|
||||
) : TsLanguageSpec(language, highlightScmSource, codeBlocksScmSource, bracketsScmSource,
|
||||
localsScmSource, localsCaptureSpec, predicates) {
|
||||
|
||||
var theme = TsThemeBuilder(tsQuery).apply { themeDescription() }.theme
|
||||
var theme = TsThemeBuilder(tsQuery).apply { themeDescription() }.theme
|
||||
|
||||
fun updateTheme(themeDescription: TsThemeBuilder.() -> Unit) = run {
|
||||
if (closed) {
|
||||
throw IllegalStateException("spec is closed")
|
||||
}
|
||||
theme = TsThemeBuilder(tsQuery).apply { themeDescription() }.theme
|
||||
fun updateTheme(themeDescription: TsThemeBuilder.() -> Unit) = run {
|
||||
if (closed) {
|
||||
throw IllegalStateException("spec is closed")
|
||||
}
|
||||
theme = TsThemeBuilder(tsQuery).apply { themeDescription() }.theme
|
||||
}
|
||||
|
||||
}
|
||||
+14
-13
@@ -28,19 +28,20 @@ package io.github.rosemoe.sora.editor.ts.predicate
|
||||
* Predicate result for [TsPredicate]
|
||||
*/
|
||||
enum class PredicateResult {
|
||||
/**
|
||||
* The given predicate is not handled by this [TsPredicate].
|
||||
* The [com.itsaky.androidide.treesitter.TSQueryMatch] object will be passed to other [TsPredicate].
|
||||
*/
|
||||
UNHANDLED,
|
||||
|
||||
/**
|
||||
* The given predicate is accepted by the [TsPredicate]
|
||||
*/
|
||||
ACCEPT,
|
||||
/**
|
||||
* The given predicate is not handled by this [TsPredicate].
|
||||
* The [com.itsaky.androidide.treesitter.TSQueryMatch] object will be passed to other [TsPredicate].
|
||||
*/
|
||||
UNHANDLED,
|
||||
|
||||
/**
|
||||
* The given predicate is not accepted by the [TsPredicate]
|
||||
*/
|
||||
REJECT
|
||||
/**
|
||||
* The given predicate is accepted by the [TsPredicate]
|
||||
*/
|
||||
ACCEPT,
|
||||
|
||||
/**
|
||||
* The given predicate is not accepted by the [TsPredicate]
|
||||
*/
|
||||
REJECT
|
||||
}
|
||||
+50
-49
@@ -35,60 +35,61 @@ import com.itsaky.androidide.treesitter.TSQueryPredicateStep
|
||||
*/
|
||||
class Predicator(private val query: TSQuery) {
|
||||
|
||||
/**
|
||||
* Predicates for patterns
|
||||
*/
|
||||
private val patternPredicates = mutableListOf<List<TsClientPredicateStep>>()
|
||||
/**
|
||||
* Predicates for patterns
|
||||
*/
|
||||
private val patternPredicates = mutableListOf<List<TsClientPredicateStep>>()
|
||||
|
||||
init {
|
||||
for (i in 0 until query.patternCount) {
|
||||
patternPredicates.add(query.getPredicatesForPattern(i).map {
|
||||
when (it.type) {
|
||||
TSQueryPredicateStep.Type.String -> TsClientPredicateStep(
|
||||
it.type,
|
||||
query.getStringValueForId(it.valueId)
|
||||
)
|
||||
init {
|
||||
for (i in 0 until query.patternCount) {
|
||||
patternPredicates.add(query.getPredicatesForPattern(i).map {
|
||||
when (it.type) {
|
||||
TSQueryPredicateStep.Type.String -> TsClientPredicateStep(
|
||||
it.type,
|
||||
query.getStringValueForId(it.valueId)
|
||||
)
|
||||
|
||||
TSQueryPredicateStep.Type.Capture -> TsClientPredicateStep(
|
||||
it.type,
|
||||
query.getCaptureNameForId(it.valueId)
|
||||
)
|
||||
TSQueryPredicateStep.Type.Capture -> TsClientPredicateStep(
|
||||
it.type,
|
||||
query.getCaptureNameForId(it.valueId)
|
||||
)
|
||||
|
||||
else -> TsClientPredicateStep(it.type, "")
|
||||
}
|
||||
})
|
||||
else -> TsClientPredicateStep(it.type, "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun doPredicate(
|
||||
predicates: List<TsPredicate>,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
syntheticCaptureContainer: TsSyntheticCaptureContainer = TsSyntheticCaptureContainer.EMPTY_IMMUTABLE_CONTAINER
|
||||
): Boolean {
|
||||
val description = patternPredicates[match.patternIndex]
|
||||
var tail = 0
|
||||
for (i in description.indices) {
|
||||
if (description[i].predicateType == TSQueryPredicateStep.Type.Done) {
|
||||
// Avoid allocating sublist if possible
|
||||
val subPredicateStep =
|
||||
if (tail == 0 && i + 1 == description.size) description else description.subList(
|
||||
tail,
|
||||
i + 1
|
||||
)
|
||||
for (j in predicates.indices) {
|
||||
val predicate = predicates[j]
|
||||
when (predicate.doPredicate(query, text, match, subPredicateStep,
|
||||
syntheticCaptureContainer)) {
|
||||
PredicateResult.ACCEPT -> break
|
||||
PredicateResult.REJECT -> return false
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
tail = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
fun doPredicate(
|
||||
predicates: List<TsPredicate>,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
syntheticCaptureContainer: TsSyntheticCaptureContainer = TsSyntheticCaptureContainer.EMPTY_IMMUTABLE_CONTAINER
|
||||
): Boolean {
|
||||
val description = patternPredicates[match.patternIndex]
|
||||
var tail = 0
|
||||
for (i in description.indices) {
|
||||
if (description[i].predicateType == TSQueryPredicateStep.Type.Done) {
|
||||
// Avoid allocating sublist if possible
|
||||
val subPredicateStep =
|
||||
if (tail == 0 && i + 1 == description.size) description else description.subList(
|
||||
tail,
|
||||
i + 1
|
||||
)
|
||||
for (j in predicates.indices) {
|
||||
val predicate = predicates[j]
|
||||
when (predicate.doPredicate(query, text, match, subPredicateStep, syntheticCaptureContainer)) {
|
||||
PredicateResult.ACCEPT -> break
|
||||
PredicateResult.REJECT -> return false
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
tail = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -27,6 +27,6 @@ package io.github.rosemoe.sora.editor.ts.predicate
|
||||
import com.itsaky.androidide.treesitter.TSQueryPredicateStep
|
||||
|
||||
data class TsClientPredicateStep(
|
||||
val predicateType: TSQueryPredicateStep.Type,
|
||||
val content: String
|
||||
val predicateType: TSQueryPredicateStep.Type,
|
||||
val content: String
|
||||
)
|
||||
|
||||
+12
-12
@@ -32,17 +32,17 @@ import com.itsaky.androidide.treesitter.TSQueryMatch
|
||||
*/
|
||||
interface TsPredicate {
|
||||
|
||||
/**
|
||||
* Run the predicate on the given [TSQueryMatch]
|
||||
* @see TSQueryMatch
|
||||
* @see PredicateResult
|
||||
*/
|
||||
fun doPredicate(
|
||||
tsQuery: TSQuery,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
predicateSteps: List<TsClientPredicateStep>,
|
||||
syntheticCaptures: TsSyntheticCaptureContainer
|
||||
): PredicateResult
|
||||
/**
|
||||
* Run the predicate on the given [TSQueryMatch]
|
||||
* @see TSQueryMatch
|
||||
* @see PredicateResult
|
||||
*/
|
||||
fun doPredicate(
|
||||
tsQuery: TSQuery,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
predicateSteps: List<TsClientPredicateStep>,
|
||||
syntheticCaptures: TsSyntheticCaptureContainer
|
||||
): PredicateResult
|
||||
|
||||
}
|
||||
+3
-3
@@ -27,7 +27,7 @@ package io.github.rosemoe.sora.editor.ts.predicate
|
||||
import com.itsaky.androidide.treesitter.TSNode
|
||||
|
||||
data class TsSyntheticCapture(
|
||||
val captureName: String,
|
||||
val captureText: String? = null,
|
||||
val captureNode: TSNode? = null
|
||||
val captureName: String,
|
||||
val captureText: String? = null,
|
||||
val captureNode: TSNode? = null
|
||||
)
|
||||
+26
-24
@@ -26,32 +26,34 @@ package io.github.rosemoe.sora.editor.ts.predicate
|
||||
|
||||
class TsSyntheticCaptureContainer(private val noAddOperation: Boolean = false) {
|
||||
|
||||
companion object {
|
||||
val EMPTY_IMMUTABLE_CONTAINER = TsSyntheticCaptureContainer(true)
|
||||
companion object {
|
||||
|
||||
val EMPTY_IMMUTABLE_CONTAINER = TsSyntheticCaptureContainer(true)
|
||||
}
|
||||
|
||||
private val syntheticCaptures = mutableListOf<TsSyntheticCapture>()
|
||||
|
||||
val indices
|
||||
get() = syntheticCaptures.indices
|
||||
|
||||
val size
|
||||
get() = syntheticCaptures.size
|
||||
|
||||
operator fun get(index: Int) = syntheticCaptures[index]
|
||||
|
||||
fun addSyntheticCapture(syntheticCapture: TsSyntheticCapture) {
|
||||
if (syntheticCapture.captureNode == null && syntheticCapture.captureText == null) {
|
||||
throw IllegalArgumentException(
|
||||
"at least one field between 'captureText' and 'captureNode' should be non-null")
|
||||
}
|
||||
|
||||
private val syntheticCaptures = mutableListOf<TsSyntheticCapture>()
|
||||
|
||||
val indices
|
||||
get() = syntheticCaptures.indices
|
||||
|
||||
val size
|
||||
get() = syntheticCaptures.size
|
||||
|
||||
operator fun get(index: Int) = syntheticCaptures[index]
|
||||
|
||||
fun addSyntheticCapture(syntheticCapture: TsSyntheticCapture) {
|
||||
if (syntheticCapture.captureNode == null && syntheticCapture.captureText == null) {
|
||||
throw IllegalArgumentException("at least one field between 'captureText' and 'captureNode' should be non-null")
|
||||
}
|
||||
if (noAddOperation) {
|
||||
return
|
||||
}
|
||||
syntheticCaptures.add(syntheticCapture)
|
||||
if (noAddOperation) {
|
||||
return
|
||||
}
|
||||
syntheticCaptures.add(syntheticCapture)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
syntheticCaptures.clear()
|
||||
}
|
||||
fun clear() {
|
||||
syntheticCaptures.clear()
|
||||
}
|
||||
|
||||
}
|
||||
+29
-29
@@ -36,37 +36,37 @@ import java.util.regex.PatternSyntaxException
|
||||
|
||||
object MatchPredicate : TsPredicate {
|
||||
|
||||
private val PARAMETERS = arrayOf(Type.String, Type.Capture, Type.String, Type.Done)
|
||||
private val PARAMETERS = arrayOf(Type.String, Type.Capture, Type.String, Type.Done)
|
||||
|
||||
private val cache = ConcurrentHashMap<String, Regex>()
|
||||
private val cache = ConcurrentHashMap<String, Regex>()
|
||||
|
||||
override fun doPredicate(
|
||||
tsQuery: TSQuery,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
predicateSteps: List<TsClientPredicateStep>,
|
||||
syntheticCaptures: TsSyntheticCaptureContainer
|
||||
): PredicateResult {
|
||||
if (!parametersMatch(predicateSteps, PARAMETERS) || predicateSteps[0].content != "match?") {
|
||||
return PredicateResult.UNHANDLED
|
||||
}
|
||||
val captured = getCaptureContent(tsQuery, match, predicateSteps[1].content, text)
|
||||
try {
|
||||
var regex = cache[predicateSteps[2].content]
|
||||
if (regex == null) {
|
||||
regex = Regex(predicateSteps[2].content)
|
||||
cache[predicateSteps[2].content] = regex
|
||||
}
|
||||
for (str in captured) {
|
||||
if (regex.find(str) == null) {
|
||||
return PredicateResult.REJECT
|
||||
}
|
||||
}
|
||||
return PredicateResult.ACCEPT
|
||||
} catch (e: PatternSyntaxException) {
|
||||
e.printStackTrace()
|
||||
return PredicateResult.UNHANDLED
|
||||
}
|
||||
override fun doPredicate(
|
||||
tsQuery: TSQuery,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
predicateSteps: List<TsClientPredicateStep>,
|
||||
syntheticCaptures: TsSyntheticCaptureContainer
|
||||
): PredicateResult {
|
||||
if (!parametersMatch(predicateSteps, PARAMETERS) || predicateSteps[0].content != "match?") {
|
||||
return PredicateResult.UNHANDLED
|
||||
}
|
||||
val captured = getCaptureContent(tsQuery, match, predicateSteps[1].content, text)
|
||||
try {
|
||||
var regex = cache[predicateSteps[2].content]
|
||||
if (regex == null) {
|
||||
regex = Regex(predicateSteps[2].content)
|
||||
cache[predicateSteps[2].content] = regex
|
||||
}
|
||||
for (str in captured) {
|
||||
if (regex.find(str) == null) {
|
||||
return PredicateResult.REJECT
|
||||
}
|
||||
}
|
||||
return PredicateResult.ACCEPT
|
||||
} catch (e: PatternSyntaxException) {
|
||||
e.printStackTrace()
|
||||
return PredicateResult.UNHANDLED
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+40
-40
@@ -35,48 +35,48 @@ import io.github.rosemoe.sora.editor.ts.predicate.TsSyntheticCaptureContainer
|
||||
|
||||
object SetCapturePredicate : TsPredicate {
|
||||
|
||||
private val PARAMETERS_1 = arrayOf(
|
||||
TSQueryPredicateStep.Type.String,
|
||||
TSQueryPredicateStep.Type.Capture,
|
||||
TSQueryPredicateStep.Type.String,
|
||||
TSQueryPredicateStep.Type.Done
|
||||
)
|
||||
private val PARAMETERS_2 = arrayOf(
|
||||
TSQueryPredicateStep.Type.String,
|
||||
TSQueryPredicateStep.Type.Capture,
|
||||
TSQueryPredicateStep.Type.Capture,
|
||||
TSQueryPredicateStep.Type.Done
|
||||
)
|
||||
private val PARAMETERS_1 = arrayOf(
|
||||
TSQueryPredicateStep.Type.String,
|
||||
TSQueryPredicateStep.Type.Capture,
|
||||
TSQueryPredicateStep.Type.String,
|
||||
TSQueryPredicateStep.Type.Done
|
||||
)
|
||||
private val PARAMETERS_2 = arrayOf(
|
||||
TSQueryPredicateStep.Type.String,
|
||||
TSQueryPredicateStep.Type.Capture,
|
||||
TSQueryPredicateStep.Type.Capture,
|
||||
TSQueryPredicateStep.Type.Done
|
||||
)
|
||||
|
||||
override fun doPredicate(
|
||||
tsQuery: TSQuery,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
predicateSteps: List<TsClientPredicateStep>,
|
||||
syntheticCaptures: TsSyntheticCaptureContainer
|
||||
): PredicateResult {
|
||||
if (predicateSteps[0].content == "set!") {
|
||||
if (parametersMatch(predicateSteps, PARAMETERS_1)) {
|
||||
syntheticCaptures.addSyntheticCapture(
|
||||
TsSyntheticCapture(
|
||||
predicateSteps[1].content,
|
||||
predicateSteps[2].content
|
||||
)
|
||||
)
|
||||
} else if (parametersMatch(predicateSteps, PARAMETERS_2)) {
|
||||
val captureTexts = getCaptureContent(tsQuery, match, predicateSteps[2].content, text)
|
||||
if (captureTexts.size == 1) {
|
||||
syntheticCaptures.addSyntheticCapture(
|
||||
TsSyntheticCapture(
|
||||
predicateSteps[1].content,
|
||||
captureTexts[0]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun doPredicate(
|
||||
tsQuery: TSQuery,
|
||||
text: CharSequence,
|
||||
match: TSQueryMatch,
|
||||
predicateSteps: List<TsClientPredicateStep>,
|
||||
syntheticCaptures: TsSyntheticCaptureContainer
|
||||
): PredicateResult {
|
||||
if (predicateSteps[0].content == "set!") {
|
||||
if (parametersMatch(predicateSteps, PARAMETERS_1)) {
|
||||
syntheticCaptures.addSyntheticCapture(
|
||||
TsSyntheticCapture(
|
||||
predicateSteps[1].content,
|
||||
predicateSteps[2].content
|
||||
)
|
||||
)
|
||||
} else if (parametersMatch(predicateSteps, PARAMETERS_2)) {
|
||||
val captureTexts = getCaptureContent(tsQuery, match, predicateSteps[2].content, text)
|
||||
if (captureTexts.size == 1) {
|
||||
syntheticCaptures.addSyntheticCapture(
|
||||
TsSyntheticCapture(
|
||||
predicateSteps[1].content,
|
||||
captureTexts[0]
|
||||
)
|
||||
)
|
||||
}
|
||||
// As this does not affect whether the match is actually valid, we always return UNHANDLED
|
||||
return PredicateResult.UNHANDLED
|
||||
}
|
||||
}
|
||||
// As this does not affect whether the match is actually valid, we always return UNHANDLED
|
||||
return PredicateResult.UNHANDLED
|
||||
}
|
||||
|
||||
}
|
||||
+26
-26
@@ -32,35 +32,35 @@ import io.github.rosemoe.sora.editor.ts.predicate.TsClientPredicateStep
|
||||
import io.github.rosemoe.sora.text.Content
|
||||
|
||||
fun parametersMatch(
|
||||
predicate: List<TsClientPredicateStep>,
|
||||
types: Array<Type>
|
||||
predicate: List<TsClientPredicateStep>,
|
||||
types: Array<Type>
|
||||
): Boolean {
|
||||
if (predicate.size == types.size) {
|
||||
for (i in types.indices) {
|
||||
if (predicate[i].predicateType != types[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
if (predicate.size == types.size) {
|
||||
for (i in types.indices) {
|
||||
if (predicate[i].predicateType != types[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getCaptureContent(
|
||||
tsQuery: TSQuery,
|
||||
match: TSQueryMatch,
|
||||
captureName: String,
|
||||
text: CharSequence
|
||||
tsQuery: TSQuery,
|
||||
match: TSQueryMatch,
|
||||
captureName: String,
|
||||
text: CharSequence
|
||||
) = match.captures.filter { tsQuery.getCaptureNameForId(it.index) == captureName }
|
||||
.map {
|
||||
if (text is UTF16String) {
|
||||
val utf16Name = text.subseqChars(it.node.startByte / 2, it.node.endByte / 2)
|
||||
val name = utf16Name.toString()
|
||||
utf16Name.close()
|
||||
name
|
||||
} else if(text is Content) {
|
||||
text.substring(it.node.startByte / 2, it.node.endByte / 2)
|
||||
} else {
|
||||
text.substring(it.node.startByte / 2, it.node.endByte / 2)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
if (text is UTF16String) {
|
||||
val utf16Name = text.subseqChars(it.node.startByte / 2, it.node.endByte / 2)
|
||||
val name = utf16Name.toString()
|
||||
utf16Name.close()
|
||||
name
|
||||
} else if (text is Content) {
|
||||
text.substring(it.node.startByte / 2, it.node.endByte / 2)
|
||||
} else {
|
||||
text.substring(it.node.startByte / 2, it.node.endByte / 2)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ gradle-tooling = "8.3"
|
||||
junit-jupiter = "5.10.0"
|
||||
kotlin = "1.9.10"
|
||||
kotlin-coroutines = "1.7.3"
|
||||
tree-sitter = "4.0.0-internal.1+branch.dev.commit.170fe25.timestamp.20231011182804-SNAPSHOT"
|
||||
tree-sitter = "4.0.0-internal.1+branch.dev.commit.c54287e.timestamp.20231015110809-SNAPSHOT"
|
||||
editor = "0.22.1-f7ee449-SNAPSHOT"
|
||||
glide = "4.16.0"
|
||||
androidx-vectordrawable = "1.1.0"
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* 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.models;
|
||||
|
||||
import static com.itsaky.androidide.utils.LogTagUtils.trimTagIfNeeded;
|
||||
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.ILogger.Priority;
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool;
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool.Recyclable;
|
||||
import java.util.Objects;
|
||||
|
||||
public class LogLine implements Recyclable {
|
||||
|
||||
// do not cache too many LogLine items
|
||||
// LogLines should be recycled as soon as they are appended to the log view
|
||||
private static final RecyclableObjectPool<LogLine> logLinePool = new RecyclableObjectPool<>(16, LogLine.class, LogLine::new);
|
||||
|
||||
public String unformatted;
|
||||
public String date;
|
||||
public String time;
|
||||
public String pid;
|
||||
public String tid;
|
||||
public String tag;
|
||||
public String message;
|
||||
public ILogger.Priority priority;
|
||||
public boolean formatted;
|
||||
|
||||
public static LogLine obtain(Priority priority, String tag, String message) {
|
||||
return obtain(priority, tag, message, true);
|
||||
}
|
||||
|
||||
public static LogLine obtain(Priority priority, String tag, String message, boolean formatted) {
|
||||
final var logLine = logLinePool.obtain();
|
||||
logLine.priority = priority;
|
||||
logLine.tag = tag;
|
||||
logLine.message = message;
|
||||
logLine.formatted = formatted;
|
||||
return logLine;
|
||||
}
|
||||
|
||||
public static LogLine forLogString(final String log) {
|
||||
if (log == null) {
|
||||
return null;
|
||||
}
|
||||
final var logLine = logLinePool.obtain();
|
||||
try {
|
||||
final var split = log.split("\\s", 7);
|
||||
logLine.priority = ILogger.priority(Character.toUpperCase(split[4].charAt(0)));
|
||||
logLine.date = split[0];
|
||||
logLine.time = split[1]; // time
|
||||
logLine.pid = split[2]; // process id
|
||||
logLine.tid = split[3]; // thread id
|
||||
logLine.tag = split[5]; // tag
|
||||
logLine.message = split[6]; // message
|
||||
logLine.formatted = true;
|
||||
} catch (Throwable th) { // do not log the exception with ILogger
|
||||
logLine.unformatted = log;
|
||||
logLine.formatted = false;
|
||||
}
|
||||
|
||||
return logLine;
|
||||
}
|
||||
|
||||
// For JSONRpc and Recyclable
|
||||
protected LogLine() {
|
||||
resetToDefault();
|
||||
}
|
||||
|
||||
public String toSimpleString() {
|
||||
return this.formatted
|
||||
? String.format(
|
||||
"%-25s %-2s %s", trimTagIfNeeded(tag, 25), ILogger.priorityChar(priority), message)
|
||||
: this.unformatted;
|
||||
}
|
||||
|
||||
|
||||
public String formattedTagAndMessage() {
|
||||
return this.formatted
|
||||
? String.format("%-25s %-2s", trimTagIfNeeded(tag, 25), message)
|
||||
: this.unformatted;
|
||||
}
|
||||
|
||||
protected void resetToDefault() {
|
||||
unformatted = date = time = pid = tid = tag = message = null;
|
||||
priority = Priority.DEBUG;
|
||||
formatted = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
resetToDefault();
|
||||
logLinePool.recycle(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(unformatted, date, time, pid, tid, tag, message, priority, formatted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final LogLine logLine = (LogLine) o;
|
||||
return formatted == logLine.formatted
|
||||
&& Objects.equals(unformatted, logLine.unformatted)
|
||||
&& Objects.equals(date, logLine.date)
|
||||
&& Objects.equals(time, logLine.time)
|
||||
&& Objects.equals(pid, logLine.pid)
|
||||
&& Objects.equals(tid, logLine.tid)
|
||||
&& Objects.equals(tag, logLine.tag)
|
||||
&& Objects.equals(message, logLine.message)
|
||||
&& priority == logLine.priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.formatted
|
||||
? String.format(
|
||||
"%s %s %s %s %-2s %-25s %s",
|
||||
date, time, pid, tid, ILogger.priorityChar(priority), trimTagIfNeeded(tag, 25), message)
|
||||
: this.unformatted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.itsaky.androidide.utils.DefaultRecyclable
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.LogTagUtils
|
||||
import com.itsaky.androidide.utils.RecyclableObjectPool
|
||||
import com.itsaky.androidide.utils.newRecyclableObjectPool
|
||||
|
||||
class LogLine private constructor() : DefaultRecyclable() {
|
||||
|
||||
var unformatted: String? = null
|
||||
var date: String? = null
|
||||
var time: String? = null
|
||||
var pid: String? = null
|
||||
var tid: String? = null
|
||||
var tag: String? = null
|
||||
var message: String? = null
|
||||
var priority: ILogger.Priority? = null
|
||||
var formatted = false
|
||||
|
||||
// For JSONRpc and Recyclable
|
||||
init {
|
||||
resetToDefault()
|
||||
}
|
||||
|
||||
fun toSimpleString(): String {
|
||||
return if (formatted) String.format(
|
||||
"%-25s %-2s %s", LogTagUtils.trimTagIfNeeded(tag, 25), ILogger.priorityChar(priority),
|
||||
message) else unformatted!!
|
||||
}
|
||||
|
||||
fun formattedTagAndMessage(): String {
|
||||
return if (formatted) String.format("%-25s %-2s", LogTagUtils.trimTagIfNeeded(tag, 25),
|
||||
message) else unformatted!!
|
||||
}
|
||||
|
||||
private fun resetToDefault() {
|
||||
message = null
|
||||
tag = null
|
||||
tid = null
|
||||
pid = null
|
||||
time = null
|
||||
date = null
|
||||
unformatted = null
|
||||
priority = ILogger.Priority.DEBUG
|
||||
formatted = false
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
resetToDefault()
|
||||
logLinePool.recycle(this)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return if (formatted) String.format(
|
||||
"%s %s %s %s %-2s %-25s %s",
|
||||
date, time, pid, tid, ILogger.priorityChar(priority), LogTagUtils.trimTagIfNeeded(tag, 25),
|
||||
message) else unformatted!!
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is LogLine) return false
|
||||
|
||||
if (unformatted != other.unformatted) return false
|
||||
if (date != other.date) return false
|
||||
if (time != other.time) return false
|
||||
if (pid != other.pid) return false
|
||||
if (tid != other.tid) return false
|
||||
if (tag != other.tag) return false
|
||||
if (message != other.message) return false
|
||||
if (priority != other.priority) return false
|
||||
if (formatted != other.formatted) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = unformatted?.hashCode() ?: 0
|
||||
result = 31 * result + (date?.hashCode() ?: 0)
|
||||
result = 31 * result + (time?.hashCode() ?: 0)
|
||||
result = 31 * result + (pid?.hashCode() ?: 0)
|
||||
result = 31 * result + (tid?.hashCode() ?: 0)
|
||||
result = 31 * result + (tag?.hashCode() ?: 0)
|
||||
result = 31 * result + (message?.hashCode() ?: 0)
|
||||
result = 31 * result + (priority?.hashCode() ?: 0)
|
||||
result = 31 * result + formatted.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// do not cache too many LogLine items
|
||||
// LogLines should be recycled as soon as they are appended to the log view
|
||||
private val logLinePool = newRecyclableObjectPool(
|
||||
capacity = 16,
|
||||
factory = ::LogLine
|
||||
)
|
||||
|
||||
@JvmOverloads
|
||||
@JvmStatic
|
||||
fun obtain(priority: ILogger.Priority?, tag: String?, message: String?,
|
||||
formatted: Boolean = true): LogLine {
|
||||
val logLine = logLinePool.obtain()
|
||||
logLine.priority = priority
|
||||
logLine.tag = tag
|
||||
logLine.message = message
|
||||
logLine.formatted = formatted
|
||||
return logLine
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun forLogString(log: String?): LogLine? {
|
||||
if (log == null) {
|
||||
return null
|
||||
}
|
||||
val logLine = logLinePool.obtain()
|
||||
try {
|
||||
val split = log.split("\\s".toRegex(), limit = 7).toTypedArray()
|
||||
logLine.priority = ILogger.priority(split[4][0].uppercaseChar())
|
||||
logLine.date = split[0]
|
||||
logLine.time = split[1] // time
|
||||
logLine.pid = split[2] // process id
|
||||
logLine.tid = split[3] // thread id
|
||||
logLine.tag = split[5] // tag
|
||||
logLine.message = split[6] // message
|
||||
logLine.formatted = true
|
||||
} catch (th: Throwable) { // do not log the exception with ILogger
|
||||
logLine.unformatted = log
|
||||
logLine.formatted = false
|
||||
}
|
||||
return logLine
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
open class DefaultRecyclable : RecyclableObjectPool.Recyclable {
|
||||
|
||||
private val _isRecycled = AtomicBoolean(false)
|
||||
|
||||
override var isRecycled: Boolean
|
||||
get() = _isRecycled.get()
|
||||
set(value) {
|
||||
_isRecycled.set(value)
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
}
|
||||
}
|
||||
@@ -20,19 +20,30 @@ package com.itsaky.androidide.utils
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlin.math.min
|
||||
|
||||
// TODO(itsaky): Automatically resize the pool based on the cache hit rate and the acess count
|
||||
|
||||
/**
|
||||
* An object pool allows to recycle and reuse objects of a specific type. Implmentations of this are
|
||||
* expected to be thread safe.
|
||||
*
|
||||
* @param capacity The maximum capacity of the pool.
|
||||
* @param fillFirst The number of items that should be created and put in the cache.
|
||||
* @param klass The class of the recyclable object. This is used for logging purposes.
|
||||
* @param objFactory The factory which will be used to create new instances of the object.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOverloads constructor(
|
||||
private val capacity: Int = CAPACITY_DEFAULT,
|
||||
klass: Class<RecyclableT>,
|
||||
private val objFactory: Factory<RecyclableT>
|
||||
private val objFactory: Factory<RecyclableT>,
|
||||
private val metricsEnabled: Boolean = true,
|
||||
fillFirst: Int = 0,
|
||||
klass: Class<RecyclableT>
|
||||
) {
|
||||
|
||||
private val lastMetricsLog = AtomicLong(0)
|
||||
|
||||
companion object {
|
||||
|
||||
var DEBUG = false
|
||||
@@ -47,6 +58,7 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
const val CAPACITY_DEFAULT: Int = 4096
|
||||
const val CAPACITY_LARGE: Int = CAPACITY_DEFAULT * 2
|
||||
const val CAPACITY_SMALL: Int = CAPACITY_DEFAULT / 2
|
||||
const val CAPACITY_MINI: Int = CAPACITY_SMALL / 2
|
||||
}
|
||||
|
||||
private val objName = klass.name
|
||||
@@ -64,12 +76,30 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
|
||||
private val cache = ArrayBlockingQueue<RecyclableT>(capacity)
|
||||
|
||||
init {
|
||||
this.fillFirst(fillFirst)
|
||||
}
|
||||
|
||||
private fun fillFirst(n: Int) {
|
||||
if (n < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
val count = min(n, capacity)
|
||||
for (i in 1..count) {
|
||||
cache.offer(objFactory.create())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycle the given object.
|
||||
*/
|
||||
fun recycle(obj: RecyclableT): Boolean {
|
||||
check(!obj.isRecycled) { "Object is already recycled" }
|
||||
|
||||
return cache.offer(obj).also { inserted ->
|
||||
if (inserted) {
|
||||
obj.isRecycled = true
|
||||
recCount.incrementAndGet()
|
||||
}
|
||||
}
|
||||
@@ -87,6 +117,7 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
accCount.set(IntPair.pack(++access, cacheHit))
|
||||
|
||||
val result = this.cache.poll()?.also {
|
||||
it.isRecycled = false
|
||||
|
||||
// increment the cache hit
|
||||
accCount.set(IntPair.pack(access, ++cacheHit))
|
||||
@@ -122,7 +153,7 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
}
|
||||
|
||||
fun logMetrics() {
|
||||
if (!DEBUG) {
|
||||
if ( !(metricsEnabled && DEBUG) || System.currentTimeMillis() - lastMetricsLog.get() < 1000) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,6 +173,7 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
log.debug(" Recycle rate : ${recycleRate()}%")
|
||||
log.debug(" Cache utilization rate : ${cacheUtilization()}%")
|
||||
log.debug(" Objects in cache : ${cache.size}")
|
||||
log.debug(" Total capacity : $capacity")
|
||||
|
||||
if (cacheHitRate < CACHE_HIT_WARNING_THRESHOLD) {
|
||||
// Cache hit rate is less than threshold
|
||||
@@ -152,6 +184,8 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
log.error("Make sure that instances of $objName are obtained using the object pool")
|
||||
log.error("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||
}
|
||||
|
||||
lastMetricsLog.set(System.currentTimeMillis())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +196,11 @@ class RecyclableObjectPool<RecyclableT : RecyclableObjectPool.Recyclable> @JvmOv
|
||||
*/
|
||||
interface Recyclable {
|
||||
|
||||
/**
|
||||
* Whether the object has been recycled. This property is supposed to be modified only by the pool.
|
||||
*/
|
||||
var isRecycled: Boolean
|
||||
|
||||
/**
|
||||
* Recycle the this object. Implementations should make sure that all the properties are resset
|
||||
* to their default values (as if the object was newly created).
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
/**
|
||||
* Create a new [RecyclableObjectPool] with the given [capacity] and the object [factory].
|
||||
*/
|
||||
inline fun <reified T : RecyclableObjectPool.Recyclable> newRecyclableObjectPool(
|
||||
capacity: Int = RecyclableObjectPool.CAPACITY_DEFAULT,
|
||||
fillFirst: Int = 0,
|
||||
metricsEnabled: Boolean = true,
|
||||
factory: RecyclableObjectPool.Factory<T>
|
||||
): RecyclableObjectPool<T> {
|
||||
return RecyclableObjectPool(
|
||||
capacity = capacity,
|
||||
fillFirst = fillFirst,
|
||||
metricsEnabled = metricsEnabled,
|
||||
klass = T::class.java,
|
||||
objFactory = factory
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycle all the recyclable objects in this iterable.
|
||||
*/
|
||||
fun <T : RecyclableObjectPool.Recyclable> Iterable<T>.recycleAll() {
|
||||
forEach { it.recycle() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycle all the recyclable objects in this array.
|
||||
*/
|
||||
fun <T : RecyclableObjectPool.Recyclable> Array<T>.recycleAll() {
|
||||
forEach { it.recycle() }
|
||||
}
|
||||
Reference in New Issue
Block a user