EmbeddedRectSL.kt
src/jvmMain/kotlin/afield/lang/rectsl/study/EmbeddedRectSL.kt · 74 lines
package afield.lang.rectsl.study
import afield.document.graph.Box
import afield.app.protocol.Completion
import afield.runtime.lifecycle.RunsAt
import afield.runtime.lifecycle.plugins
import afield.lang.rectsl.RectSLVocabulary
import afield.lang.stacksl.study.EmbeddedStackSL
import afield.ui.output.LineNotes
import java.io.File
import java.util.concurrent.ConcurrentHashMap
Completion and line notes for RectSL written inside a Kotlin box's code, as a string whose first
line is #rectsl (EmbeddedRectSL).
object EmbeddedRectSL
RectSL written inside a Kotlin box's code, as a string whose first line is #rectsl (the convention
the editor already highlights): the RectSL vocabulary when completion opens inside it, and each line's
scene-so-far as a line note — the help a rectSL property has, where the scene sits in Kotlin. The
StackSL counterpart, EmbeddedStackSL, also finds strings assigned to stack.auto slots; RectSL has
no such slot, so only the header marks it.
object EmbeddedRectSL {
private val header = Regex("""^(?:[ \t]*(?:\r?\n|$))*[ \t]*#rectsl\b[^\n]*(?:\r?\n|$)""", RegexOption.IGNORE_CASE)
fun regions(code: String): List<EmbeddedStackSL.Region>
Every #rectsl string in code: its body (after the header line) and where it starts.
fun regions(code: String): List<EmbeddedStackSL.Region> {
val out = mutableListOf<EmbeddedStackSL.Region>()
var search = 0
while (search < code.length) {
val open = code.indexOf("\"\"\"", search).takeIf { it >= 0 } ?: break
val start = open + 3
val close = code.indexOf("\"\"\"", start).takeIf { it >= 0 } ?: break
val content = code.substring(start, close)
header.find(content)?.let { out += EmbeddedStackSL.Region(start + it.value.length, content.substring(it.value.length), null) }
search = close + 3
}
return out
}
private fun regionAt(code: String, position: Int) = regions(code).firstOrNull { position in it.bodyStart..it.bodyEnd }
fun complete(code: String, position: Int): Completion?
Completion at position of code, when it is inside a #rectsl string: the vocabulary, with bounds in the code's offsets.
fun complete(code: String, position: Int): Completion? {
val region = regionAt(code, position) ?: return null
val inner = RectSLVocabulary.complete(region.text, position - region.bodyStart)
return inner.copy(boundsStart = inner.boundsStart + region.bodyStart, boundsEnd = inner.boundsEnd + region.bodyStart)
}
private val studies = ConcurrentHashMap<Box, RectSLStudy>()
fun lineNote(box: Box, code: String, line: Int): LineNotes.Note?
line (1-based) of code's #rectsl scene, as a note: the scene built up to it; null outside one.
fun lineNote(box: Box, code: String, line: Int): LineNotes.Note? {
val lineStart = code.lines().take(line - 1).sumOf { it.length + 1 }
val region = regionAt(code, lineStart) ?: regionAt(code, lineStart + (code.lines().getOrNull(line - 1)?.length ?: 0)) ?: return null
val relative = line - (code.substring(0, region.bodyStart).count { it == '\n' } + 1) + 1
val study = studies.getOrPut(box) {
RectSLStudy(File(System.getProperty("java.io.tmpdir"), "field-embedded-rectsl/${box.id}"), { "" }, { _, _ -> false })
}
return study.lineNote(region.text, relative, 0.0)
}
}
fun installEmbeddedRectSLLineNotes()
Line notes for a Kotlin box's code inside a #rectsl scene. A line outside one is left to the
other providers of code (EmbeddedStackSL's among them): LineNotes asks each in turn.
@RunsAt(plugins)
fun installEmbeddedRectSLLineNotes() {
LineNotes.register("code") { box, source, line -> EmbeddedRectSL.lineNote(box, source, line) }
}