+added console spport for lyngio

+added console support to lyng/jlyng CLI
+added unicode escapes
+created tetris console sample
This commit is contained in:
sergeych committed 2026-03-19 01:09:32 +03:00
1 parent 1a080bc53e
commit d9d7cafec8
44 files changed
+3842 -28

No files matched your search

@@ -0,0 +1,115 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.io.console
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjBool
import net.sergeych.lyng.obj.ObjIllegalOperationException
import net.sergeych.lyngio.console.security.ConsoleAccessOp
import net.sergeych.lyngio.console.security.ConsoleAccessPolicy
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
import net.sergeych.lyngio.fs.security.AccessContext
import net.sergeych.lyngio.fs.security.AccessDecision
import net.sergeych.lyngio.fs.security.Decision
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertTrue
class LyngConsoleModuleTest {
private fun newScope(): Scope = Scope.new()
@Test
fun installIsIdempotent() = runBlocking {
val scope = newScope()
assertTrue(createConsoleModule(PermitAllConsoleAccessPolicy, scope))
assertFalse(createConsoleModule(PermitAllConsoleAccessPolicy, scope))
}
@Test
fun moduleSmokeScript() = runBlocking {
val scope = newScope()
createConsoleModule(PermitAllConsoleAccessPolicy, scope)
val code = """
import lyng.io.console
import lyng.stdlib
val d = Console.details()
assert(d.supported is Bool)
assert(d.isTty is Bool)
assert(d.ansiLevel is String)
val g = Console.geometry()
if (g != null) {
assert(g.columns is Int)
assert(g.rows is Int)
assert(g.columns > 0)
assert(g.rows > 0)
}
assert(Console.events() is Iterable)
Console.write("")
Console.flush()
Console.home()
Console.clear()
Console.moveTo(1, 1)
Console.clearLine()
Console.enterAltScreen()
Console.leaveAltScreen()
Console.setCursorVisible(true)
val changed = Console.setRawMode(false)
assert(changed is Bool)
true
""".trimIndent()
val result = scope.eval(code)
assertIs<ObjBool>(result)
assertTrue(result.value)
}
@Test
fun denyWritePolicyMapsToIllegalOperation() {
runBlocking {
val denyWritePolicy = object : ConsoleAccessPolicy {
override suspend fun check(op: ConsoleAccessOp, ctx: AccessContext): AccessDecision = when (op) {
is ConsoleAccessOp.WriteText -> AccessDecision(Decision.Deny, "denied by test policy")
else -> AccessDecision(Decision.Allow)
}
}
val scope = newScope()
createConsoleModule(denyWritePolicy, scope)
val error = kotlin.test.assertFailsWith<ExecutionError> {
scope.eval(
"""
import lyng.io.console
Console.write("x")
""".trimIndent()
)
}
assertIs<ObjIllegalOperationException>(error.errorObject)
}
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio.console
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class MordantLyngConsoleJvmTest {
@Test
fun basicCapabilitiesSmoke() = runBlocking {
val console = getSystemConsole()
assertNotNull(console)
// Must be callable in any environment (interactive or redirected output).
val tty = console.isTty()
val ansi = console.ansiLevel()
val geometry = console.geometry()
if (geometry != null) {
assertTrue(geometry.columns > 0, "columns must be positive when geometry is present")
assertTrue(geometry.rows > 0, "rows must be positive when geometry is present")
}
// no-op smoke checks
console.write("")
console.flush()
// Keep values live so compiler doesn't optimize away calls in future changes
assertNotNull(ansi)
assertTrue(tty || !tty)
}
@Test
fun setRawModeContract() = runBlocking {
val console = getSystemConsole()
val enabledChanged = console.setRawMode(true)
val disabledChanged = console.setRawMode(false)
// If enabling changed state, disabling should also change it back.
if (enabledChanged) {
assertTrue(disabledChanged, "raw mode disable should report changed after enable")
}
}
@Test
fun eventsSourceDoesNotCrash() = runBlocking {
val console = getSystemConsole()
val source = console.events()
val event = source.nextEvent(350)
source.close()
// Any event kind is acceptable in this smoke test; null is also valid when idle.
if (event != null) {
assertTrue(
event is ConsoleEvent.Resize || event is ConsoleEvent.KeyDown || event is ConsoleEvent.KeyUp,
"unexpected event type: ${event::class.simpleName}"
)
}
}
}