1.5.0-RC2: improved lyngio.console, some polishing
This commit is contained in:
1 parent
d92309d76c
commit
2a79c718ba
10 files changed
+637
-656
No files matched your search
@@ -20,9 +20,11 @@ package net.sergeych.lyng.io.console
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeFacade
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.Source
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjBool
|
||||
import net.sergeych.lyng.obj.ObjEnumClass
|
||||
import net.sergeych.lyng.obj.ObjEnumEntry
|
||||
import net.sergeych.lyng.obj.ObjIterable
|
||||
import net.sergeych.lyng.obj.ObjIterationFinishedException
|
||||
import net.sergeych.lyng.obj.ObjIterator
|
||||
@@ -42,6 +44,9 @@ import net.sergeych.lyngio.console.getSystemConsole
|
||||
import net.sergeych.lyngio.console.security.ConsoleAccessDeniedException
|
||||
import net.sergeych.lyngio.console.security.ConsoleAccessPolicy
|
||||
import net.sergeych.lyngio.console.security.LyngConsoleSecured
|
||||
import net.sergeych.lyngio.stdlib_included.consoleLyng
|
||||
|
||||
private const val CONSOLE_MODULE_NAME = "lyng.io.console"
|
||||
|
||||
/**
|
||||
* Install Lyng module `lyng.io.console` into the given scope's ImportManager.
|
||||
@@ -53,10 +58,9 @@ fun createConsole(policy: ConsoleAccessPolicy, scope: Scope): Boolean = createCo
|
||||
|
||||
/** Same as [createConsoleModule] but with explicit [ImportManager]. */
|
||||
fun createConsoleModule(policy: ConsoleAccessPolicy, manager: ImportManager): Boolean {
|
||||
val name = "lyng.io.console"
|
||||
if (manager.packageNames.contains(name)) return false
|
||||
if (manager.packageNames.contains(CONSOLE_MODULE_NAME)) return false
|
||||
|
||||
manager.addPackage(name) { module ->
|
||||
manager.addPackage(CONSOLE_MODULE_NAME) { module ->
|
||||
buildConsoleModule(module, policy)
|
||||
}
|
||||
return true
|
||||
@@ -65,59 +69,37 @@ fun createConsoleModule(policy: ConsoleAccessPolicy, manager: ImportManager): Bo
|
||||
fun createConsole(policy: ConsoleAccessPolicy, manager: ImportManager): Boolean = createConsoleModule(policy, manager)
|
||||
|
||||
private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAccessPolicy) {
|
||||
// Load Lyng declarations for console enums/types first (module-local source of truth).
|
||||
module.eval(Source(CONSOLE_MODULE_NAME, consoleLyng))
|
||||
ConsoleEnums.initialize(module)
|
||||
val console: LyngConsole = LyngConsoleSecured(getSystemConsole(), policy)
|
||||
|
||||
val consoleType = object : net.sergeych.lyng.obj.ObjClass("Console") {}
|
||||
|
||||
consoleType.apply {
|
||||
addClassFnDoc(
|
||||
name = "isSupported",
|
||||
doc = "Whether console control API is supported on this platform.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("isSupported") {
|
||||
ObjBool(console.isSupported)
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "isTty",
|
||||
doc = "Whether current stdout is attached to an interactive TTY.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("isTty") {
|
||||
consoleGuard {
|
||||
ObjBool(console.isTty())
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "ansiLevel",
|
||||
doc = "Detected ANSI color capability: NONE, BASIC16, ANSI256, TRUECOLOR.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("ansiLevel") {
|
||||
consoleGuard {
|
||||
ObjString(console.ansiLevel().name)
|
||||
ConsoleEnums.ansiLevel(console.ansiLevel().name)
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "geometry",
|
||||
doc = "Current terminal geometry or null.",
|
||||
returns = type("ConsoleGeometry", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("geometry") {
|
||||
consoleGuard {
|
||||
console.geometry()?.let { ObjConsoleGeometry(it.columns, it.rows) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "details",
|
||||
doc = "Get consolidated console details.",
|
||||
returns = type("ConsoleDetails"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("details") {
|
||||
consoleGuard {
|
||||
val tty = console.isTty()
|
||||
val ansi = console.ansiLevel()
|
||||
@@ -125,18 +107,13 @@ private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAcces
|
||||
ObjConsoleDetails(
|
||||
supported = console.isSupported,
|
||||
isTty = tty,
|
||||
ansiLevel = ansi.name,
|
||||
ansiLevel = ConsoleEnums.ansiLevel(ansi.name),
|
||||
geometry = geometry?.let { ObjConsoleGeometry(it.columns, it.rows) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "write",
|
||||
doc = "Write text directly to console output.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("write") {
|
||||
consoleGuard {
|
||||
val text = requiredArg<ObjString>(0).value
|
||||
console.write(text)
|
||||
@@ -144,48 +121,28 @@ private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAcces
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "flush",
|
||||
doc = "Flush console output buffer.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("flush") {
|
||||
consoleGuard {
|
||||
console.flush()
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "home",
|
||||
doc = "Move cursor to home position (1,1).",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("home") {
|
||||
consoleGuard {
|
||||
console.write("\u001B[H")
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "clear",
|
||||
doc = "Clear the visible screen buffer.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("clear") {
|
||||
consoleGuard {
|
||||
console.write("\u001B[2J")
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "moveTo",
|
||||
doc = "Move cursor to 1-based row and column.",
|
||||
params = listOf(
|
||||
ParamDoc("row", type("lyng.Int")),
|
||||
ParamDoc("column", type("lyng.Int")),
|
||||
),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("moveTo") {
|
||||
consoleGuard {
|
||||
val row = requiredArg<net.sergeych.lyng.obj.ObjInt>(0).value
|
||||
val col = requiredArg<net.sergeych.lyng.obj.ObjInt>(1).value
|
||||
@@ -194,45 +151,28 @@ private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAcces
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "clearLine",
|
||||
doc = "Clear the current line.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("clearLine") {
|
||||
consoleGuard {
|
||||
console.write("\u001B[2K")
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "enterAltScreen",
|
||||
doc = "Switch to terminal alternate screen buffer.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("enterAltScreen") {
|
||||
consoleGuard {
|
||||
console.write("\u001B[?1049h")
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "leaveAltScreen",
|
||||
doc = "Return from alternate screen buffer to normal screen.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("leaveAltScreen") {
|
||||
consoleGuard {
|
||||
console.write("\u001B[?1049l")
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "setCursorVisible",
|
||||
doc = "Show or hide the terminal cursor.",
|
||||
params = listOf(ParamDoc("visible", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("setCursorVisible") {
|
||||
consoleGuard {
|
||||
val visible = requiredArg<ObjBool>(0).value
|
||||
console.write(if (visible) "\u001B[?25h" else "\u001B[?25l")
|
||||
@@ -240,24 +180,13 @@ private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAcces
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "events",
|
||||
doc = "Endless iterable console event source (resize, keydown, keyup). Use in a loop, often inside launch.",
|
||||
returns = type("ConsoleEventStream"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("events") {
|
||||
consoleGuard {
|
||||
console.events().toConsoleEventStream()
|
||||
}
|
||||
}
|
||||
|
||||
addClassFnDoc(
|
||||
name = "setRawMode",
|
||||
doc = "Enable or disable raw keyboard mode. Returns true if mode changed.",
|
||||
params = listOf(ParamDoc("enabled", type("lyng.Bool"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
addClassFn("setRawMode") {
|
||||
consoleGuard {
|
||||
val enabled = requiredArg<ObjBool>(0).value
|
||||
ObjBool(console.setRawMode(enabled))
|
||||
@@ -265,55 +194,13 @@ private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAcces
|
||||
}
|
||||
}
|
||||
|
||||
module.addConstDoc(
|
||||
name = "Console",
|
||||
value = consoleType,
|
||||
doc = "Console runtime API.",
|
||||
type = type("Console"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConstDoc(
|
||||
name = "ConsoleGeometry",
|
||||
value = ObjConsoleGeometry.type,
|
||||
doc = "Terminal geometry.",
|
||||
type = type("lyng.Class"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConstDoc(
|
||||
name = "ConsoleDetails",
|
||||
value = ObjConsoleDetails.type,
|
||||
doc = "Consolidated console capability details.",
|
||||
type = type("lyng.Class"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConstDoc(
|
||||
name = "ConsoleEvent",
|
||||
value = ObjConsoleEvent.type,
|
||||
doc = "Base class for console events.",
|
||||
type = type("lyng.Class"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConstDoc(
|
||||
name = "ConsoleResizeEvent",
|
||||
value = ObjConsoleResizeEvent.type,
|
||||
doc = "Terminal resize event.",
|
||||
type = type("lyng.Class"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConstDoc(
|
||||
name = "ConsoleKeyEvent",
|
||||
value = ObjConsoleKeyEvent.typeObj,
|
||||
doc = "Keyboard event.",
|
||||
type = type("lyng.Class"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConstDoc(
|
||||
name = "ConsoleEventStream",
|
||||
value = ObjConsoleEventStream.type,
|
||||
doc = "Endless iterable stream of console events.",
|
||||
type = type("lyng.Class"),
|
||||
moduleName = module.packageName
|
||||
)
|
||||
module.addConst("Console", consoleType)
|
||||
module.addConst("ConsoleGeometry", ObjConsoleGeometry.type)
|
||||
module.addConst("ConsoleDetails", ObjConsoleDetails.type)
|
||||
module.addConst("ConsoleEvent", ObjConsoleEvent.type)
|
||||
module.addConst("ConsoleResizeEvent", ObjConsoleResizeEvent.type)
|
||||
module.addConst("ConsoleKeyEvent", ObjConsoleKeyEvent.typeObj)
|
||||
module.addConst("ConsoleEventStream", ObjConsoleEventStream.type)
|
||||
}
|
||||
|
||||
private suspend inline fun ScopeFacade.consoleGuard(crossinline block: suspend () -> Obj): Obj {
|
||||
@@ -338,12 +225,7 @@ private class ObjConsoleEventStream(
|
||||
|
||||
companion object {
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleEventStream", ObjIterable).apply {
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Create an iterator over incoming console events.",
|
||||
returns = type("lyng.Iterator"),
|
||||
moduleName = "lyng.io.console",
|
||||
) {
|
||||
addFn("iterator") {
|
||||
val stream = thisAs<ObjConsoleEventStream>()
|
||||
ObjConsoleEventIterator(stream.source)
|
||||
}
|
||||
@@ -375,7 +257,10 @@ private class ObjConsoleEventIterator(
|
||||
private suspend fun closeSource() {
|
||||
if (closed) return
|
||||
closed = true
|
||||
source.close()
|
||||
// Do not close the underlying console source from VM iterator cancellation.
|
||||
// CmdFrame.cancelIterators() may call cancelIteration() while user code is still
|
||||
// expected to keep processing input (e.g. recover from app-level exceptions).
|
||||
// The source lifecycle is managed by the console runtime.
|
||||
}
|
||||
|
||||
suspend fun hasNext(): Boolean = ensureCached()
|
||||
@@ -391,28 +276,13 @@ private class ObjConsoleEventIterator(
|
||||
|
||||
companion object {
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleEventIterator", ObjIterator).apply {
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether another console event is available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
) {
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjConsoleEventIterator>().hasNext().toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next console event.",
|
||||
returns = type("ConsoleEvent"),
|
||||
moduleName = "lyng.io.console",
|
||||
) {
|
||||
addFn("next") {
|
||||
thisAs<ObjConsoleEventIterator>().next(requireScope())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop reading console events and release resources.",
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.io.console",
|
||||
) {
|
||||
addFn("cancelIteration") {
|
||||
thisAs<ObjConsoleEventIterator>().closeSource()
|
||||
ObjVoid
|
||||
}
|
||||
@@ -422,27 +292,113 @@ private class ObjConsoleEventIterator(
|
||||
|
||||
private fun ConsoleEvent.toObjEvent(): Obj = when (this) {
|
||||
is ConsoleEvent.Resize -> ObjConsoleResizeEvent(columns, rows)
|
||||
is ConsoleEvent.KeyDown -> ObjConsoleKeyEvent(type = "keydown", key = key, code = code, ctrl = ctrl, alt = alt, shift = shift, meta = meta)
|
||||
is ConsoleEvent.KeyUp -> ObjConsoleKeyEvent(type = "keyup", key = key, code = code, ctrl = ctrl, alt = alt, shift = shift, meta = meta)
|
||||
is ConsoleEvent.KeyDown -> ObjConsoleKeyEvent(type = ConsoleEnums.KEY_DOWN, key = key, codeName = code, ctrl = ctrl, alt = alt, shift = shift, meta = meta)
|
||||
is ConsoleEvent.KeyUp -> ObjConsoleKeyEvent(type = ConsoleEnums.KEY_UP, key = key, codeName = code, ctrl = ctrl, alt = alt, shift = shift, meta = meta)
|
||||
}
|
||||
|
||||
private object ConsoleEnums {
|
||||
lateinit var eventTypeClass: ObjEnumClass
|
||||
private set
|
||||
lateinit var keyCodeClass: ObjEnumClass
|
||||
private set
|
||||
lateinit var ansiLevelClass: ObjEnumClass
|
||||
private set
|
||||
|
||||
private lateinit var eventEntries: Map<String, ObjEnumEntry>
|
||||
private lateinit var keyCodeEntries: Map<String, ObjEnumEntry>
|
||||
private lateinit var ansiLevelEntries: Map<String, ObjEnumEntry>
|
||||
|
||||
val UNKNOWN: ObjEnumEntry get() = event("UNKNOWN")
|
||||
val RESIZE: ObjEnumEntry get() = event("RESIZE")
|
||||
val KEY_DOWN: ObjEnumEntry get() = event("KEY_DOWN")
|
||||
val KEY_UP: ObjEnumEntry get() = event("KEY_UP")
|
||||
val CODE_UNKNOWN: ObjEnumEntry get() = code("UNKNOWN")
|
||||
val CHARACTER: ObjEnumEntry get() = code("CHARACTER")
|
||||
|
||||
fun initialize(module: ModuleScope) {
|
||||
eventTypeClass = resolveEnum(module, "ConsoleEventType")
|
||||
keyCodeClass = resolveEnum(module, "ConsoleKeyCode")
|
||||
ansiLevelClass = resolveEnum(module, "ConsoleAnsiLevel")
|
||||
eventEntries = resolveEntries(
|
||||
eventTypeClass,
|
||||
listOf("UNKNOWN", "RESIZE", "KEY_DOWN", "KEY_UP")
|
||||
)
|
||||
keyCodeEntries = resolveEntries(
|
||||
keyCodeClass,
|
||||
listOf(
|
||||
"UNKNOWN", "CHARACTER", "ARROW_UP", "ARROW_DOWN", "ARROW_LEFT", "ARROW_RIGHT",
|
||||
"HOME", "END", "INSERT", "DELETE", "PAGE_UP", "PAGE_DOWN",
|
||||
"ESCAPE", "ENTER", "TAB", "BACKSPACE", "SPACE"
|
||||
)
|
||||
)
|
||||
ansiLevelEntries = resolveEntries(
|
||||
ansiLevelClass,
|
||||
listOf("NONE", "BASIC16", "ANSI256", "TRUECOLOR")
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveEnum(module: ModuleScope, enumName: String): ObjEnumClass {
|
||||
val local = module.get(enumName)?.value as? ObjEnumClass
|
||||
if (local != null) return local
|
||||
val root = module.importProvider.rootScope.get(enumName)?.value as? ObjEnumClass
|
||||
return root ?: error("lyng.io.console declaration enum is missing: $enumName")
|
||||
}
|
||||
|
||||
private fun resolveEntries(enumClass: ObjEnumClass, names: List<String>): Map<String, ObjEnumEntry> {
|
||||
return names.associateWith { name ->
|
||||
(enumClass.byName[ObjString(name)] as? ObjEnumEntry)
|
||||
?: error("lyng.io.console enum entry is missing: ${enumClass.className}.$name")
|
||||
}
|
||||
}
|
||||
|
||||
fun event(name: String): ObjEnumEntry = eventEntries[name]
|
||||
?: error("lyng.io.console enum entry is missing: ${eventTypeClass.className}.$name")
|
||||
|
||||
fun code(name: String): ObjEnumEntry = keyCodeEntries[name]
|
||||
?: error("lyng.io.console enum entry is missing: ${keyCodeClass.className}.$name")
|
||||
|
||||
fun ansiLevel(name: String): ObjEnumEntry = ansiLevelEntries[name]
|
||||
?: error("lyng.io.console enum entry is missing: ${ansiLevelClass.className}.$name")
|
||||
}
|
||||
|
||||
private val KEY_CODE_BY_KEY_NAME = mapOf(
|
||||
"ArrowUp" to "ARROW_UP",
|
||||
"ArrowDown" to "ARROW_DOWN",
|
||||
"ArrowLeft" to "ARROW_LEFT",
|
||||
"ArrowRight" to "ARROW_RIGHT",
|
||||
"Home" to "HOME",
|
||||
"End" to "END",
|
||||
"Insert" to "INSERT",
|
||||
"Delete" to "DELETE",
|
||||
"PageUp" to "PAGE_UP",
|
||||
"PageDown" to "PAGE_DOWN",
|
||||
"Escape" to "ESCAPE",
|
||||
"Enter" to "ENTER",
|
||||
"Tab" to "TAB",
|
||||
"Backspace" to "BACKSPACE",
|
||||
" " to "SPACE",
|
||||
)
|
||||
|
||||
private fun codeFrom(key: String, codeName: String?): ObjEnumEntry {
|
||||
val resolved = KEY_CODE_BY_KEY_NAME[codeName ?: key]
|
||||
return when {
|
||||
resolved != null -> ConsoleEnums.code(resolved)
|
||||
key.length == 1 -> ConsoleEnums.CHARACTER
|
||||
else -> ConsoleEnums.CODE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class ObjConsoleEventBase(
|
||||
private val eventType: String,
|
||||
private val type: ObjEnumEntry,
|
||||
final override val objClass: net.sergeych.lyng.obj.ObjClass,
|
||||
) : Obj() {
|
||||
fun eventTypeName(): String = eventType
|
||||
fun type(): ObjEnumEntry = type
|
||||
}
|
||||
|
||||
private class ObjConsoleEvent : ObjConsoleEventBase("event", type) {
|
||||
private class ObjConsoleEvent : ObjConsoleEventBase(ConsoleEnums.UNKNOWN, type) {
|
||||
companion object {
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleEvent").apply {
|
||||
addPropertyDoc(
|
||||
name = "type",
|
||||
doc = "Event type string: resize, keydown, keyup.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { ObjString((this.thisObj as ObjConsoleEventBase).eventTypeName()) }
|
||||
)
|
||||
addProperty(name = "type", getter = { (this.thisObj as ObjConsoleEventBase).type() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,83 +406,40 @@ private class ObjConsoleEvent : ObjConsoleEventBase("event", type) {
|
||||
private class ObjConsoleResizeEvent(
|
||||
val columns: Int,
|
||||
val rows: Int,
|
||||
) : ObjConsoleEventBase("resize", type) {
|
||||
) : ObjConsoleEventBase(ConsoleEnums.RESIZE, type) {
|
||||
companion object {
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleResizeEvent", ObjConsoleEvent.type).apply {
|
||||
addPropertyDoc(
|
||||
name = "columns",
|
||||
doc = "Terminal width in character cells.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleResizeEvent).columns.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "rows",
|
||||
doc = "Terminal height in character cells.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleResizeEvent).rows.toObj() }
|
||||
)
|
||||
addProperty(name = "columns", getter = { (this.thisObj as ObjConsoleResizeEvent).columns.toObj() })
|
||||
addProperty(name = "rows", getter = { (this.thisObj as ObjConsoleResizeEvent).rows.toObj() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ObjConsoleKeyEvent(
|
||||
type: String,
|
||||
type: ObjEnumEntry,
|
||||
val key: String,
|
||||
val code: String?,
|
||||
val codeName: String?,
|
||||
val ctrl: Boolean,
|
||||
val alt: Boolean,
|
||||
val shift: Boolean,
|
||||
val meta: Boolean,
|
||||
) : ObjConsoleEventBase(type, typeObj) {
|
||||
init {
|
||||
require(key.isNotEmpty()) { "ConsoleKeyEvent.key must never be empty" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val typeObj = net.sergeych.lyng.obj.ObjClass("ConsoleKeyEvent", ObjConsoleEvent.type).apply {
|
||||
addPropertyDoc(
|
||||
name = "key",
|
||||
doc = "Logical key name (e.g. ArrowLeft, a, Escape).",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { ObjString((this.thisObj as ObjConsoleKeyEvent).key) }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "code",
|
||||
doc = "Optional hardware/code identifier.",
|
||||
type = type("lyng.String", nullable = true),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = {
|
||||
val code = (this.thisObj as ObjConsoleKeyEvent).code
|
||||
code?.let(::ObjString) ?: ObjNull
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "ctrl",
|
||||
doc = "Whether Ctrl modifier is pressed.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleKeyEvent).ctrl.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "alt",
|
||||
doc = "Whether Alt modifier is pressed.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleKeyEvent).alt.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "shift",
|
||||
doc = "Whether Shift modifier is pressed.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleKeyEvent).shift.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "meta",
|
||||
doc = "Whether Meta/Super modifier is pressed.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleKeyEvent).meta.toObj() }
|
||||
)
|
||||
addProperty(name = "key", getter = { ObjString((this.thisObj as ObjConsoleKeyEvent).key) })
|
||||
addProperty(name = "code", getter = { codeFrom((this.thisObj as ObjConsoleKeyEvent).key, (this.thisObj as ObjConsoleKeyEvent).codeName) })
|
||||
addProperty(name = "codeName", getter = {
|
||||
val code = (this.thisObj as ObjConsoleKeyEvent).codeName
|
||||
code?.let(::ObjString) ?: ObjNull
|
||||
})
|
||||
addProperty(name = "ctrl", getter = { (this.thisObj as ObjConsoleKeyEvent).ctrl.toObj() })
|
||||
addProperty(name = "alt", getter = { (this.thisObj as ObjConsoleKeyEvent).alt.toObj() })
|
||||
addProperty(name = "shift", getter = { (this.thisObj as ObjConsoleKeyEvent).shift.toObj() })
|
||||
addProperty(name = "meta", getter = { (this.thisObj as ObjConsoleKeyEvent).meta.toObj() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -539,20 +452,8 @@ private class ObjConsoleGeometry(
|
||||
|
||||
companion object {
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleGeometry").apply {
|
||||
addPropertyDoc(
|
||||
name = "columns",
|
||||
doc = "Terminal width in character cells.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleGeometry).columns.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "rows",
|
||||
doc = "Terminal height in character cells.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleGeometry).rows.toObj() }
|
||||
)
|
||||
addProperty(name = "columns", getter = { (this.thisObj as ObjConsoleGeometry).columns.toObj() })
|
||||
addProperty(name = "rows", getter = { (this.thisObj as ObjConsoleGeometry).rows.toObj() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,41 +461,17 @@ private class ObjConsoleGeometry(
|
||||
private class ObjConsoleDetails(
|
||||
val supported: Boolean,
|
||||
val isTty: Boolean,
|
||||
val ansiLevel: String,
|
||||
val ansiLevel: ObjEnumEntry,
|
||||
val geometry: ObjConsoleGeometry?,
|
||||
) : Obj() {
|
||||
override val objClass: net.sergeych.lyng.obj.ObjClass get() = type
|
||||
|
||||
companion object {
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleDetails").apply {
|
||||
addPropertyDoc(
|
||||
name = "supported",
|
||||
doc = "Whether console API is supported.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleDetails).supported.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isTty",
|
||||
doc = "Whether output is connected to a TTY.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleDetails).isTty.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "ansiLevel",
|
||||
doc = "Detected ANSI color capability level.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { ObjString((this.thisObj as ObjConsoleDetails).ansiLevel) }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "geometry",
|
||||
doc = "Current terminal geometry or null.",
|
||||
type = type("ConsoleGeometry", nullable = true),
|
||||
moduleName = "lyng.io.console",
|
||||
getter = { (this.thisObj as ObjConsoleDetails).geometry ?: ObjNull }
|
||||
)
|
||||
addProperty(name = "supported", getter = { (this.thisObj as ObjConsoleDetails).supported.toObj() })
|
||||
addProperty(name = "isTty", getter = { (this.thisObj as ObjConsoleDetails).isTty.toObj() })
|
||||
addProperty(name = "ansiLevel", getter = { (this.thisObj as ObjConsoleDetails).ansiLevel })
|
||||
addProperty(name = "geometry", getter = { (this.thisObj as ObjConsoleDetails).geometry ?: ObjNull })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,248 +17,12 @@
|
||||
|
||||
package net.sergeych.lyngio.docs
|
||||
|
||||
import net.sergeych.lyng.miniast.BuiltinDocRegistry
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
/**
|
||||
* Console docs are declared in `lyngio/stdlib/lyng/io/console.lyng`.
|
||||
* Keep this shim for compatibility with reflective loaders.
|
||||
*/
|
||||
object ConsoleBuiltinDocs {
|
||||
private var registered = false
|
||||
|
||||
fun ensure() {
|
||||
if (registered) return
|
||||
BuiltinDocRegistry.module("lyng.io.console") {
|
||||
classDoc(
|
||||
name = "Console",
|
||||
doc = "Console runtime API."
|
||||
) {
|
||||
method(
|
||||
name = "isSupported",
|
||||
doc = "Whether console control API is supported on this platform.",
|
||||
returns = type("lyng.Bool"),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "isTty",
|
||||
doc = "Whether stdout is attached to an interactive TTY.",
|
||||
returns = type("lyng.Bool"),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "ansiLevel",
|
||||
doc = "Detected ANSI color capability: NONE, BASIC16, ANSI256, TRUECOLOR.",
|
||||
returns = type("lyng.String"),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "geometry",
|
||||
doc = "Current terminal geometry or null.",
|
||||
returns = type("ConsoleGeometry", nullable = true),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "details",
|
||||
doc = "Get consolidated console details.",
|
||||
returns = type("ConsoleDetails"),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "write",
|
||||
doc = "Write text directly to console output.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "flush",
|
||||
doc = "Flush console output buffer.",
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "home",
|
||||
doc = "Move cursor to home position (1,1).",
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "clear",
|
||||
doc = "Clear the visible screen buffer.",
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "moveTo",
|
||||
doc = "Move cursor to 1-based row and column.",
|
||||
params = listOf(
|
||||
ParamDoc("row", type("lyng.Int")),
|
||||
ParamDoc("column", type("lyng.Int")),
|
||||
),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "clearLine",
|
||||
doc = "Clear the current line.",
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "enterAltScreen",
|
||||
doc = "Switch to terminal alternate screen buffer.",
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "leaveAltScreen",
|
||||
doc = "Return from alternate screen buffer to normal screen.",
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "setCursorVisible",
|
||||
doc = "Show or hide the terminal cursor.",
|
||||
params = listOf(ParamDoc("visible", type("lyng.Bool"))),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "events",
|
||||
doc = "Endless iterable console event source (resize, keydown, keyup). Use in a loop, often inside launch.",
|
||||
returns = type("ConsoleEventStream"),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "setRawMode",
|
||||
doc = "Enable or disable raw keyboard mode. Returns true if mode changed.",
|
||||
params = listOf(ParamDoc("enabled", type("lyng.Bool"))),
|
||||
returns = type("lyng.Bool"),
|
||||
isStatic = true
|
||||
)
|
||||
}
|
||||
classDoc(
|
||||
name = "ConsoleEventStream",
|
||||
doc = "Endless iterable stream of console events."
|
||||
) {
|
||||
method(
|
||||
name = "iterator",
|
||||
doc = "Create an iterator over incoming console events.",
|
||||
returns = type("lyng.Iterator")
|
||||
)
|
||||
}
|
||||
classDoc(
|
||||
name = "ConsoleGeometry",
|
||||
doc = "Terminal geometry."
|
||||
) {
|
||||
field(
|
||||
name = "columns",
|
||||
doc = "Terminal width in character cells.",
|
||||
type = type("lyng.Int")
|
||||
)
|
||||
field(
|
||||
name = "rows",
|
||||
doc = "Terminal height in character cells.",
|
||||
type = type("lyng.Int")
|
||||
)
|
||||
}
|
||||
classDoc(
|
||||
name = "ConsoleDetails",
|
||||
doc = "Consolidated console capability details."
|
||||
) {
|
||||
field(
|
||||
name = "supported",
|
||||
doc = "Whether console API is supported.",
|
||||
type = type("lyng.Bool")
|
||||
)
|
||||
field(
|
||||
name = "isTty",
|
||||
doc = "Whether output is attached to a TTY.",
|
||||
type = type("lyng.Bool")
|
||||
)
|
||||
field(
|
||||
name = "ansiLevel",
|
||||
doc = "Detected ANSI color capability.",
|
||||
type = type("lyng.String")
|
||||
)
|
||||
field(
|
||||
name = "geometry",
|
||||
doc = "Current geometry or null.",
|
||||
type = type("ConsoleGeometry", nullable = true)
|
||||
)
|
||||
}
|
||||
classDoc(
|
||||
name = "ConsoleEvent",
|
||||
doc = "Base class for console events."
|
||||
) {
|
||||
field(
|
||||
name = "type",
|
||||
doc = "Event kind string.",
|
||||
type = type("lyng.String")
|
||||
)
|
||||
}
|
||||
classDoc(
|
||||
name = "ConsoleResizeEvent",
|
||||
doc = "Resize event."
|
||||
) {
|
||||
field(
|
||||
name = "type",
|
||||
doc = "Event kind string: resize.",
|
||||
type = type("lyng.String")
|
||||
)
|
||||
field(
|
||||
name = "columns",
|
||||
doc = "Terminal width in character cells.",
|
||||
type = type("lyng.Int")
|
||||
)
|
||||
field(
|
||||
name = "rows",
|
||||
doc = "Terminal height in character cells.",
|
||||
type = type("lyng.Int")
|
||||
)
|
||||
}
|
||||
classDoc(
|
||||
name = "ConsoleKeyEvent",
|
||||
doc = "Keyboard event."
|
||||
) {
|
||||
field(
|
||||
name = "type",
|
||||
doc = "Event kind string: keydown or keyup.",
|
||||
type = type("lyng.String")
|
||||
)
|
||||
field(
|
||||
name = "key",
|
||||
doc = "Logical key name.",
|
||||
type = type("lyng.String")
|
||||
)
|
||||
field(
|
||||
name = "code",
|
||||
doc = "Optional hardware code.",
|
||||
type = type("lyng.String", nullable = true)
|
||||
)
|
||||
field(
|
||||
name = "ctrl",
|
||||
doc = "Ctrl modifier state.",
|
||||
type = type("lyng.Bool")
|
||||
)
|
||||
field(
|
||||
name = "alt",
|
||||
doc = "Alt modifier state.",
|
||||
type = type("lyng.Bool")
|
||||
)
|
||||
field(
|
||||
name = "shift",
|
||||
doc = "Shift modifier state.",
|
||||
type = type("lyng.Bool")
|
||||
)
|
||||
field(
|
||||
name = "meta",
|
||||
doc = "Meta modifier state.",
|
||||
type = type("lyng.Bool")
|
||||
)
|
||||
}
|
||||
|
||||
valDoc(
|
||||
name = "Console",
|
||||
doc = "Console runtime API.",
|
||||
type = type("Console")
|
||||
)
|
||||
valDoc(name = "ConsoleGeometry", doc = "Terminal geometry class.", type = type("lyng.Class"))
|
||||
valDoc(name = "ConsoleDetails", doc = "Console details class.", type = type("lyng.Class"))
|
||||
valDoc(name = "ConsoleEvent", doc = "Base console event class.", type = type("lyng.Class"))
|
||||
valDoc(name = "ConsoleResizeEvent", doc = "Resize event class.", type = type("lyng.Class"))
|
||||
valDoc(name = "ConsoleKeyEvent", doc = "Keyboard event class.", type = type("lyng.Class"))
|
||||
valDoc(name = "ConsoleEventStream", doc = "Iterable console event stream class.", type = type("lyng.Class"))
|
||||
}
|
||||
registered = true
|
||||
// No Kotlin-side doc registration: console.lyng is the source of truth.
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user