MainLanguageRegistry.kt
src/jvmMain/kotlin/afield/runtime/MainLanguageRegistry.kt · 96 lines
package afield.runtime
import afield.document.graph.Box
import afield.runtime.execution.BoxExecution
import afield.runtime.lifecycle.RunsAt
import afield.runtime.lifecycle.early
import afield.runtime.lifecycle.newRoot
import afield.app.notifications.notify
import afield.app.commands.Action
import afield.document.properties.Delegation
import afield.app.commands.actions
import kotlin.reflect.KClass
The registry that lets a box choose the language its children run in, such as Python instead of Kotlin.
A language runtime registers itself with registerMainLanguageRuntime. Every registered runtime
then gets a "Set Main Language: ..." command in the palette, next to "Set Main Language: Kotlin".
Running one on a selected box records the choice in the box's persistent mainLanguage property
and converts the box's children to that runtime. Choosing Kotlin clears the property and converts
them back.
data class MainLanguageRuntime
One language a box can give its children: an id, a palette label, and the hooks that convert them.
data class MainLanguageRuntime(
val id: String,
val label: String,
val runtimeClass: KClass<out BoxExecution>,
val setLegacyRoot: (Box, Boolean) -> Unit,
val applyTopology: (Collection<Box>) -> Unit,
)
var Box.mainLanguage by Delegation<String?>()
private val registeredMainLanguages = linkedMapOf<String, MainLanguageRuntime>()
fun registerMainLanguageRuntime(runtime: MainLanguageRuntime)
fun registerMainLanguageRuntime(runtime: MainLanguageRuntime) {
registeredMainLanguages[runtime.id] = runtime
}
fun refreshMainLanguageRuntimes(boxes: Collection<Box>)
Apply every registered language root synchronously.
The UI's TopologyWatcher normally does this on the runloop after a graph edit. Semantic automation can connect a language root and execute its child in the same macro batch, so it calls this dispatch barrier before execute/launch rather than racing the asynchronous watcher.
fun refreshMainLanguageRuntimes(boxes: Collection<Box>) {
registeredMainLanguages.values.forEach { it.applyTopology(boxes) }
}
fun Box.isMainLanguage(id: String): Boolean
fun Box.isMainLanguage(id: String): Boolean =
mainLanguage.equals(id, ignoreCase = true)
fun installMainLanguageProperty()
@RunsAt(early)
fun installMainLanguageProperty() {
Box.ioPersistentProperties.add("mainLanguage")
}
fun installMainLanguageActions(root: Box)
@RunsAt(newRoot)
fun installMainLanguageActions(root: Box) {
root.actions["set-main-language-kotlin"] = Action(
desc = "Set Main Language: Kotlin",
detail = "Use standard Kotlin execution for children of the selected language root box.",
) { selection ->
setMainLanguageFor(selection.ifEmpty { listOf(root) }, null)
}
registeredMainLanguages.values.forEach { runtime ->
root.actions["set-main-language-${runtime.id}"] = Action(
desc = "Set Main Language: ${runtime.label}",
detail = "Make children of the selected language root box run as ${runtime.label}.",
) { selection ->
setMainLanguageFor(selection.ifEmpty { listOf(root) }, runtime)
}
}
}
private fun setMainLanguageFor(roots: List<Box>, runtime: MainLanguageRuntime?) {
roots.forEach { root ->
registeredMainLanguages.values.forEach { it.setLegacyRoot(root, false) }
root.mainLanguage = runtime?.id
runtime?.setLegacyRoot?.invoke(root, true)
registeredMainLanguages.values.forEach { it.applyTopology(listOf(root)) }
notify(
if (runtime == null) {
"'${root.name}' children use Kotlin"
} else {
"'${root.name}' children use ${runtime.label}"
},
"language",
)
}
}