From f5131e0b554ae0e8962c875f5bdea27c75f5961a Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 11:58:48 +0100 Subject: [PATCH 01/12] file logging --- .../main/scala-jvm-native/PlatformStd.scala | 2 +- .../main/scala-jvm-native/SyncPrompts.scala | 9 ++-- .../core/src/main/scala/ASyncPrompts.scala | 2 +- modules/core/src/main/scala/FileLogging.scala | 41 +++++++++++++++++ modules/core/src/main/scala/Output.scala | 46 +++++++++++++++---- .../core/src/main/scalajs/PlatformStd.scala | 2 +- 6 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 modules/core/src/main/scala/FileLogging.scala diff --git a/modules/core/src/main/scala-jvm-native/PlatformStd.scala b/modules/core/src/main/scala-jvm-native/PlatformStd.scala index f95ed53..2a0d7c3 100644 --- a/modules/core/src/main/scala-jvm-native/PlatformStd.scala +++ b/modules/core/src/main/scala-jvm-native/PlatformStd.scala @@ -17,7 +17,7 @@ package cue4s private[cue4s] trait PlatformStd extends Output: - override def logLn[A: AsString](a: A): Unit = + override def logLn[A: AsString](a: => A): Unit = System.err.println(a.render) System.err.flush() override def out[A: AsString](a: A): Unit = diff --git a/modules/core/src/main/scala-jvm-native/SyncPrompts.scala b/modules/core/src/main/scala-jvm-native/SyncPrompts.scala index 1333e29..8edf45b 100644 --- a/modules/core/src/main/scala-jvm-native/SyncPrompts.scala +++ b/modules/core/src/main/scala-jvm-native/SyncPrompts.scala @@ -19,7 +19,7 @@ package cue4s import cue4s.Prompt.PasswordInput.Password class SyncPromptsBuilder private (impl: SyncPromptsOptions): - def this() = this(SyncPromptsOptions(Output.Std, Theme.Default)) + def this() = this(SyncPromptsOptions(Output.StdNoLogging, Theme.Default)) def noColors: SyncPromptsBuilder = withTheme(Theme.NoColors) def withOutput(out: Output): SyncPromptsBuilder = copy(_.copy(out = out)) @@ -28,9 +28,10 @@ class SyncPromptsBuilder private (impl: SyncPromptsOptions): def use[A](f: SyncPrompts => A): A = val p = Prompts(impl.out, Terminal.ansi, impl.theme) val inst = SyncPrompts(p) - try - f(inst) - finally p.close() + try f(inst) + finally + p.close() + impl.out.close() private def copy(f: SyncPromptsOptions => SyncPromptsOptions) = new SyncPromptsBuilder(f(impl)) diff --git a/modules/core/src/main/scala/ASyncPrompts.scala b/modules/core/src/main/scala/ASyncPrompts.scala index 8edeae6..4920c4d 100644 --- a/modules/core/src/main/scala/ASyncPrompts.scala +++ b/modules/core/src/main/scala/ASyncPrompts.scala @@ -22,7 +22,7 @@ import scala.concurrent.Future import cue4s.Prompt.PasswordInput.Password class AsyncPromptsBuilder private (impl: AsyncPromptsOptions): - def this() = this(AsyncPromptsOptions(Output.Std, Theme.Default)) + def this() = this(AsyncPromptsOptions(Output.StdNoLogging, Theme.Default)) def noColors: AsyncPromptsBuilder = withTheme(Theme.NoColors) def withOutput(out: Output): AsyncPromptsBuilder = copy(_.copy(out = out)) diff --git a/modules/core/src/main/scala/FileLogging.scala b/modules/core/src/main/scala/FileLogging.scala new file mode 100644 index 0000000..f3d89eb --- /dev/null +++ b/modules/core/src/main/scala/FileLogging.scala @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Neandertech + * + * 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 cue4s + +import java.io.File +import java.io.FileWriter + +object FileLogging: + def toFile(originalOut: Output, file: String): Output = + createLoggingOut(originalOut, file) + + def fromEnv(originalOut: Output, env: Map[String, String] = sys.env): Output = + env.get("CUE4S_LOG_FILE") match + case Some(value) => + createLoggingOut(originalOut, value) + case None => + originalOut + + private def createLoggingOut(originalOut: Output, file: String): Output = + new Output: + private val fw = new FileWriter(new File(file)) + override def logLn[A: AsString](a: => A): Unit = + fw.write(a.render + "\n") + fw.flush() + override def out[A: AsString](a: A): Unit = originalOut.out(a) + override def close(): Unit = fw.close() +end FileLogging diff --git a/modules/core/src/main/scala/Output.scala b/modules/core/src/main/scala/Output.scala index 6d160b2..ea23f67 100644 --- a/modules/core/src/main/scala/Output.scala +++ b/modules/core/src/main/scala/Output.scala @@ -18,7 +18,8 @@ package cue4s trait Output: def out[A: AsString](a: A): Unit - def logLn[A: AsString](a: A): Unit + def logLn[A: AsString](a: => A): Unit + def close(): Unit = () trait AsString[A]: extension (a: A) def render: String @@ -31,22 +32,49 @@ extension (o: Output) o.out(a.render + "\n") def loggerOnly: Output = new Output: - override def logLn[A: AsString](a: A): Unit = o.logLn(a) - override def out[A: AsString](a: A): Unit = () + override def logLn[A: AsString](a: => A): Unit = o.logLn(a) + override def out[A: AsString](a: A): Unit = () +end extension object Output: object Std extends PlatformStd + /** Outputs to STDOUT, logging messages are ignored. This is the default + * output chosen by the library under normal usage. + * + * If logging is necessary, the recommended approach is to log to a file: + * + * {{{ + * val out = FileLogging.toFile(Output.StdNoLogging, "myfile.log") + * Promps.sync.withOutput(out).use { prompts => ... } + * }}} + * + * For convenience, you can use a logger that is enabled only if a special + * environment variable is passed: + * + * {{{ + * val out = FileLogging.fromEnv(Output.StdNoLogging) + * Promps.sync.withOutput(out).use { prompts => ... } + * }}} + * + * Then logging will be written to a file specified by `CUE4S_LOG_FILE` env + * variable. If the env variable is absent, logger will not output anything + */ + object StdNoLogging extends PlatformStd: + override def logLn[A: AsString](a: => A): Unit = () + end StdNoLogging + object StdOut extends Output: - override def logLn[A: AsString](a: A): Unit = System.out.println(a.render) - override def out[A: AsString](a: A): Unit = System.out.print(a.render) + override def logLn[A: AsString](a: => A): Unit = + System.out.println(a.render) + override def out[A: AsString](a: A): Unit = System.out.print(a.render) object DarkVoid extends Output: - override def logLn[A: AsString](a: A): Unit = () - override def out[A: AsString](a: A): Unit = () + override def logLn[A: AsString](a: => A): Unit = () + override def out[A: AsString](a: A): Unit = () class Delegate(writeOut: String => Unit, writeLog: String => Unit) extends Output: - override def logLn[A: AsString](a: A): Unit = writeLog(a.render + "\n") - override def out[A: AsString](a: A): Unit = writeOut(a.render) + override def logLn[A: AsString](a: => A): Unit = writeLog(a.render + "\n") + override def out[A: AsString](a: A): Unit = writeOut(a.render) end Output diff --git a/modules/core/src/main/scalajs/PlatformStd.scala b/modules/core/src/main/scalajs/PlatformStd.scala index 5adfdd4..8f9ed6d 100644 --- a/modules/core/src/main/scalajs/PlatformStd.scala +++ b/modules/core/src/main/scalajs/PlatformStd.scala @@ -17,6 +17,6 @@ package cue4s private[cue4s] trait PlatformStd extends Output: - override def logLn[A: AsString](a: A): Unit = + override def logLn[A: AsString](a: => A): Unit = Process.stderr.write(a.render + "\n") override def out[A: AsString](a: A): Unit = Process.stdout.write(a.render) From f2a747e2349f28f466d93a2b1f0675ab5619de3b Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 11:59:04 +0100 Subject: [PATCH 02/12] Handle unknown characters explicitly, and fix Windows on native --- .../core/src/main/scala/AnsiTerminal.scala | 26 +++++++++---------- .../core/src/main/scala/CharCollector.scala | 4 +++ .../main/scalanative/ChangeModeWindows.scala | 9 ++++--- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/modules/core/src/main/scala/AnsiTerminal.scala b/modules/core/src/main/scala/AnsiTerminal.scala index c42a546..d2ef2ee 100644 --- a/modules/core/src/main/scala/AnsiTerminal.scala +++ b/modules/core/src/main/scala/AnsiTerminal.scala @@ -21,7 +21,7 @@ class AnsiTerminal(out: Output) extends Terminal: private val writer = (s: String) => out.out(s) - private inline def call(name: Char, inline args: Int*): this.type = + private inline def csi(name: Char, inline args: Int*): this.type = writer(s"$CSI${args.mkString(";")}$name") this @@ -33,10 +33,10 @@ class AnsiTerminal(out: Output) extends Terminal: override inline def cursorShow(): this.type = call(s"$CSI?25h") private inline def lineEraseMode(n: Int): this.type = - call('K', n) + csi('K', n) private inline def screenEraseMode(n: Int): this.type = - call('K', n) + csi('K', n) override inline def eraseEntireLine(): this.type = lineEraseMode(2) override inline def eraseEntireScreen(): this.type = screenEraseMode(2) @@ -47,20 +47,20 @@ class AnsiTerminal(out: Output) extends Terminal: override inline def eraseToEndOfLine(): this.type = lineEraseMode(0) override inline def eraseToEndOfScreen(): this.type = screenEraseMode(0) - override inline def moveBack(n: Int): this.type = call('D', n) - override inline def moveDown(n: Int): this.type = call('B', n) - override inline def moveForward(n: Int): this.type = call('C', n) + override inline def moveBack(n: Int): this.type = csi('D', n) + override inline def moveDown(n: Int): this.type = csi('B', n) + override inline def moveForward(n: Int): this.type = csi('C', n) override inline def moveHorizontalTo(column: Int): this.type = - call('G', column) - override inline def moveNextLine(n: Int): this.type = call('E', n) - override inline def movePreviousLine(n: Int): this.type = call('F', n) + csi('G', column) + override inline def moveNextLine(n: Int): this.type = csi('E', n) + override inline def movePreviousLine(n: Int): this.type = csi('F', n) override inline def moveToPosition(row: Int, column: Int): this.type = - call('H', row, column) + csi('H', row, column) override inline def moveUp(n: Int): this.type = - call('A', n) + csi('A', n) - override inline def restore(): this.type = call('u') - override inline def save(): this.type = call('s') + override inline def restore(): this.type = call(s"${ESC}8") + override inline def save(): this.type = call(s"${ESC}7") override inline def screenClear(): this.type = call(s"${ESC}c") end AnsiTerminal diff --git a/modules/core/src/main/scala/CharCollector.scala b/modules/core/src/main/scala/CharCollector.scala index 19fcd94..0a51024 100644 --- a/modules/core/src/main/scala/CharCollector.scala +++ b/modules/core/src/main/scala/CharCollector.scala @@ -98,6 +98,10 @@ private[cue4s] object CharCollector: b, ) => (State.CSI_Collecting(b.toByte :: Nil), DecodeResult.Continue) + case _ => + error( + s"Unexpected byte ${char}, expected CSI parameter or intermediate byte", + ) case State.CSI_Collecting(bytes) => char match diff --git a/modules/core/src/main/scalanative/ChangeModeWindows.scala b/modules/core/src/main/scalanative/ChangeModeWindows.scala index 621e667..0b23d9a 100644 --- a/modules/core/src/main/scalanative/ChangeModeWindows.scala +++ b/modules/core/src/main/scalanative/ChangeModeWindows.scala @@ -29,10 +29,11 @@ object ChangeModeWindows extends ChangeModeNative: def changeMode(rawMode: Boolean): Boolean = rawMode override def read(): Int = - ??? - // val b = stackalloc[Byte](1) - // val cnt = read(STDIN_FILENO, b, 1) - // b(0) + val ch = getchar() + + // For raw scan codes, this function returns either 0 or 0xE0. + // To avoid ambiguity, force it to always be 0xE0. + if ch == 0 then 0xe0 else ch object Msvcrt: @extern() @link("msvcrt") From 9912788f68dcbaf6a52b134f7a53fbc29c497a02 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 11:59:40 +0100 Subject: [PATCH 03/12] Change transition to be stateful instead of exposed vars --- modules/core/src/main/scala/Transition.scala | 113 ++++++++++++++++++- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/scala/Transition.scala b/modules/core/src/main/scala/Transition.scala index a91fe5c..f5699f5 100644 --- a/modules/core/src/main/scala/Transition.scala +++ b/modules/core/src/main/scala/Transition.scala @@ -16,6 +16,113 @@ package cue4s -private[cue4s] case class Transition[R](current: R, last: Option[R] = None): - def next(r: R) = copy(current = r, last = Some(current)) - def nextFn(r: R => R) = copy(current = r(current), last = Some(current)) +import cue4s.Transition.Changed + +private[cue4s] trait Transition[R]: + def current: R + def last: Option[R] + def next(r: R): Changed + def nextFn(r: R => R): Changed + + def changed: Boolean = + !last.contains(current) + +private[cue4s] object Transition: + enum Changed: + case Yes, No + + class RefTransition[R](init: R) extends Transition[R]: + private var _current: R = init + private var _last: Option[R] = None + + override def current: R = + if _current == null then + _current = init + _current.nn + else _current.nn + + override def last: Option[R] = _last + + override def next(r: R): Changed = + if r == current then Changed.No + else + this.synchronized: + _last = Some(current) + _current = r + + Changed.Yes + + override def nextFn(r: R => R): Changed = + val newRes = r(current) + if newRes == current then Changed.No + else + this.synchronized: + _last = Some(current) + _current = newRes + Changed.Yes + + end RefTransition + + class FollowTransition[R](t: Transition[R]) extends Transition[R]: + override def current: R = t.current + override def last: Option[R] = t.last + override def next(r: R): Changed = t.next(r) + override def nextFn(r: R => R): Changed = t.nextFn(r) + + def base[R](current: R): Transition[R] = + new RefTransition(current) + + def follow[R](t: Transition[R]): Transition[R] = + new FollowTransition(t) + + class ReadOnlyTransition[R](t: Transition[R]) extends Transition[R]: + override def current: R = t.current + override def last: Option[R] = t.last + override def next(r: R): Changed = Changed.No + override def nextFn(r: R => R): Changed = Changed.Yes + override def changed: Boolean = t.changed + + def readOnly[R](t: Transition[R]): Transition[R] = + new ReadOnlyTransition(t) + +end Transition + +// class BaseTransition[R](private var _current: => R, val last: Option[R] = None) +// extends Transition[R]: +// override lazy val current: R = _current +// override def next(r: R): Transition[R] = +// new BaseTransition(_current = r, last = Some(current)) +// override def nextFn(r: R => R): Transition[R] = +// new BaseTransition(_current = r(current), last = Some(current)) + +// end Transition + +private[cue4s] class LoggedTransition[R] private ( + label: String, + t: Transition[R], + out: Output, +) extends Transition[R]: + export t.{current, last} + override def next(r: R): Changed = + // out.logLn(s"Transition [$label] (set): $current changed to $r") + out.logLn(s"Transition [$label] (set):") + out.logLn(s" current: $current") + out.logLn(s" new: $r") + t.next(r) + + override def nextFn(r: R => R): Changed = + val cur = current + val transition = t.nextFn(r) + if cur != t.current then + out.logLn(s"Transition [$label] (mapping):") + out.logLn(s" current: $cur") + out.logLn(s" new: $current") + + transition + end nextFn + +end LoggedTransition + +private[cue4s] object LoggedTransition: + def apply[R](label: String, t: Transition[R], out: Output): Transition[R] = + new LoggedTransition(label, t, out) From 7da0c7f28b34487deb7e1124beb8522107e29f02 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:02:01 +0100 Subject: [PATCH 04/12] Rework derived prompts and validations, tighten public API Prompts can no longer manage status manually --- .../main/scala/InteractiveConfirmation.scala | 19 +- .../scala/InteractiveMultipleChoice.scala | 11 +- .../main/scala/InteractiveSingleChoice.scala | 38 +- .../src/main/scala/InteractiveTextInput.scala | 34 +- modules/core/src/main/scala/Prompt.scala | 68 +-- .../core/src/main/scala/PromptFramework.scala | 388 ++++++++++++------ .../snapshots/core/alternatives_cancel_input | 2 - .../core/alternatives_cancel_multiple_derived | 3 +- .../snapshots/core/alternatives_confirm_agree | 2 - .../core/alternatives_confirm_cancel | 2 - .../core/alternatives_confirm_decline | 2 - .../core/alternatives_confirm_default | 2 - .../snapshots/core/derived_validated_input | 16 - .../core/src/snapshots/core/fallback_confirm | 2 - .../core/src/snapshots/core/fallback_input | 7 - modules/core/src/snapshots/core/input | 7 - modules/core/src/snapshots/core/input_default | 2 - .../src/snapshots/core/input_default_ignore | 9 - .../core/src/snapshots/core/number_default | 2 - modules/core/src/snapshots/core/number_input | 27 -- .../core/src/snapshots/core/password_input | 9 - .../src/snapshots/core/password_input_default | 2 - modules/core/src/snapshots/core/promptchain | 27 -- .../coreJS/alternatives_cancel_input | 2 - .../alternatives_cancel_multiple_derived | 3 +- .../coreJS/alternatives_confirm_agree | 2 - .../coreJS/alternatives_confirm_cancel | 2 - .../coreJS/alternatives_confirm_decline | 2 - .../coreJS/alternatives_confirm_default | 2 - .../snapshots/coreJS/derived_validated_input | 16 - .../src/snapshots/coreJS/fallback_confirm | 2 - .../core/src/snapshots/coreJS/fallback_input | 7 - modules/core/src/snapshots/coreJS/input | 7 - .../core/src/snapshots/coreJS/input_default | 2 - .../src/snapshots/coreJS/input_default_ignore | 9 - .../core/src/snapshots/coreJS/number_default | 2 - .../core/src/snapshots/coreJS/number_input | 27 -- .../core/src/snapshots/coreJS/password_input | 9 - .../snapshots/coreJS/password_input_default | 2 - modules/core/src/snapshots/coreJS/promptchain | 27 -- .../coreNative/alternatives_cancel_input | 2 - .../alternatives_cancel_multiple_derived | 3 +- .../coreNative/alternatives_confirm_agree | 2 - .../coreNative/alternatives_confirm_cancel | 2 - .../coreNative/alternatives_confirm_decline | 2 - .../coreNative/alternatives_confirm_default | 2 - .../coreNative/derived_validated_input | 16 - .../src/snapshots/coreNative/fallback_confirm | 2 - .../src/snapshots/coreNative/fallback_input | 7 - modules/core/src/snapshots/coreNative/input | 7 - .../src/snapshots/coreNative/input_default | 2 - .../snapshots/coreNative/input_default_ignore | 9 - .../src/snapshots/coreNative/number_default | 2 - .../src/snapshots/coreNative/number_input | 27 -- .../src/snapshots/coreNative/password_input | 9 - .../coreNative/password_input_default | 2 - .../core/src/snapshots/coreNative/promptchain | 27 -- .../core/src/test/scala/ExampleTests.scala | 45 +- .../core/src/test/scala/TerminalTests.scala | 2 +- .../main/scala-jvm-native/UserComponent.scala | 13 +- .../src/main/scala-jvm-native/sync.scala | 30 +- 61 files changed, 434 insertions(+), 583 deletions(-) diff --git a/modules/core/src/main/scala/InteractiveConfirmation.scala b/modules/core/src/main/scala/InteractiveConfirmation.scala index 0c2ff0e..3bbcd59 100644 --- a/modules/core/src/main/scala/InteractiveConfirmation.scala +++ b/modules/core/src/main/scala/InteractiveConfirmation.scala @@ -25,25 +25,28 @@ private[cue4s] class InteractiveConfirmation( symbols: Symbols, ) extends PromptFramework[Boolean](terminal, out): - override type PromptState = Unit + override type PromptState = Boolean override type Event = TerminalEvent - override def initialState: PromptState = () + override def initialState: PromptState = default + + override def extractResult(state: PromptState): Either[PromptError, Boolean] = + Right(state) override def handleEvent(event: TerminalEvent): PromptAction = event match - case TerminalEvent.Init => PromptAction.Update() + case TerminalEvent.Init => PromptAction.Continue case TerminalEvent.Key(KeyEvent.ENTER) => - PromptAction.setStatus(Status.Finished(default)) + PromptAction.TrySubmit case TerminalEvent.Char(which) => which match case 'y' | 'Y' => - PromptAction.setStatus(Status.Finished(true)) + PromptAction.submit(true) case 'n' | 'N' => - PromptAction.setStatus(Status.Finished(false)) + PromptAction.submit(false) case _ => PromptAction.Continue case TerminalEvent.Interrupt => - PromptAction.setStatus(Status.Canceled) + PromptAction.Stop case _ => PromptAction.Continue end match @@ -52,7 +55,7 @@ private[cue4s] class InteractiveConfirmation( import theme.* override def renderState( - state: Unit, + state: Boolean, status: Status, ): List[String] = val lines = List.newBuilder[String] diff --git a/modules/core/src/main/scala/InteractiveMultipleChoice.scala b/modules/core/src/main/scala/InteractiveMultipleChoice.scala index 5695e39..11b7011 100644 --- a/modules/core/src/main/scala/InteractiveMultipleChoice.scala +++ b/modules/core/src/main/scala/InteractiveMultipleChoice.scala @@ -113,6 +113,9 @@ private[cue4s] class InteractiveMultipleChoice( lines.result() end renderState + override def extractResult(state: State): Either[PromptError, List[String]] = + Right(state.selected.toList.sorted.map(altMapping)) + override def handleEvent(event: TerminalEvent) = event match case TerminalEvent.Key(KeyEvent.UP) => @@ -122,11 +125,7 @@ private[cue4s] class InteractiveMultipleChoice( PromptAction.updateState(_.updateDisplay(_.down)) case TerminalEvent.Key(KeyEvent.ENTER) => - PromptAction.setStatus( - Status.Finished( - currentState().selected.toList.sorted.map(altMapping), - ), - ) + PromptAction.TrySubmit case TerminalEvent.Key(KeyEvent.TAB) => PromptAction.updateState(_.toggle) @@ -141,7 +140,7 @@ private[cue4s] class InteractiveMultipleChoice( PromptAction.updateState(_.addText(which.toChar)) case TerminalEvent.Interrupt => - PromptAction.setStatus(Status.Canceled) + PromptAction.Stop case _ => PromptAction.Continue diff --git a/modules/core/src/main/scala/InteractiveSingleChoice.scala b/modules/core/src/main/scala/InteractiveSingleChoice.scala index bd1b8bf..0da3079 100644 --- a/modules/core/src/main/scala/InteractiveSingleChoice.scala +++ b/modules/core/src/main/scala/InteractiveSingleChoice.scala @@ -43,6 +43,14 @@ private[cue4s] class InteractiveSingleChoice( ), ) + override def extractResult(state: PromptState) = + state.display.showing match + case None => + Left(PromptError("nothing selected")) + + case Some((_, idx)) => + Right(altMapping(idx)) + override def handleEvent(event: TerminalEvent) = event match case TerminalEvent.Key(KeyEvent.UP) => @@ -52,14 +60,7 @@ private[cue4s] class InteractiveSingleChoice( PromptAction.updateState(_.updateDisplay(_.down)) case TerminalEvent.Key(KeyEvent.ENTER) => // enter - currentState().display.showing match - case None => - PromptAction.setStatus( - Status.Running(Left(PromptError("nothing selected"))), - ) - case Some((_, idx)) => - PromptAction.setStatus(Status.Finished(altMapping(idx))) - + PromptAction.TrySubmit case TerminalEvent.Key(KeyEvent.DELETE) => // enter PromptAction.updateState(_.trimText) @@ -67,7 +68,7 @@ private[cue4s] class InteractiveSingleChoice( PromptAction.updateState(_.addText(which.toChar)) case TerminalEvent.Interrupt => - PromptAction.setStatus(Status.Canceled) + PromptAction.Stop case _ => PromptAction.Continue @@ -80,18 +81,18 @@ private[cue4s] class InteractiveSingleChoice( st: State, status: Status, ): List[String] = - val lines = List.newBuilder[String] + val lines = List.newBuilder[fansi.Str] import symbols.* status match - case Status.Running(_) | Status.Init => + case Status.Running(_) => // prompt question - lines += "? ".focused + (lab + s" $promptCue ").prompt + st.text.input + lines += "? ".focused ++ (lab + s" $promptCue ").prompt ++ st.text.input status match case Status.Running(err) => - err.foreach: err => + err.left.foreach: err => lines += err.error case _ => @@ -118,16 +119,15 @@ private[cue4s] class InteractiveSingleChoice( ) end match case Status.Finished(value) => - lines += s"$promptDone ".focused + - (lab + " ").prompt + - s" $ellipsis ".hint + + lines += s"$promptDone ".focused ++ + (lab + " ").prompt ++ + s" $ellipsis ".hint ++ value.emphasis case Status.Canceled => - lines += s"$promptCancelled ".canceled + - (lab + " ").prompt + lines += s"$promptCancelled ".canceled ++ (lab + " ").prompt end match - lines.result() + lines.result().map(_.render) end renderState end InteractiveSingleChoice diff --git a/modules/core/src/main/scala/InteractiveTextInput.scala b/modules/core/src/main/scala/InteractiveTextInput.scala index 80ca0d2..9102f37 100644 --- a/modules/core/src/main/scala/InteractiveTextInput.scala +++ b/modules/core/src/main/scala/InteractiveTextInput.scala @@ -21,7 +21,6 @@ private[cue4s] class InteractiveTextInput( terminal: Terminal, out: Output, theme: Theme, - validate: String => Option[PromptError], hideText: Boolean, symbols: Symbols, default: Option[String], @@ -32,37 +31,27 @@ private[cue4s] class InteractiveTextInput( override type PromptState = State override type Event = TerminalEvent - override def initialState: State = State(default.getOrElse("")) + override def initialState: State = State(default.getOrElse(""), None) - override def handleEvent(event: TerminalEvent) = - - // TODO: rework the framework to support validation? - def update(f: State => State) = - val newState = f(currentState()) - val newStatus = - Status.Running(validate(newState.text).toLeft(newState.text)) + override def extractResult(state: State): Either[PromptError, String] = + Right(state.text) - PromptAction.set(newState, newStatus) + override def handleEvent(event: TerminalEvent) = event match case TerminalEvent.Key(KeyEvent.ENTER) => - currentStatus() match - case Status.Running(Right(candidate)) => - PromptAction.setStatus(Status.Finished(candidate)) - case Status.Init => - default match - case None => PromptAction.Continue - case Some(value) => PromptAction.setStatus(Status.Finished(value)) - - case _ => PromptAction.Continue + PromptAction.TrySubmit case TerminalEvent.Key(KeyEvent.DELETE) => - update(_.trimText) + PromptAction.updateState(_.trimText) case TerminalEvent.Char(which) => - update(_.addText(which.toChar)) + PromptAction.updateState(_.addText(which.toChar)) + + case TerminalEvent.Interrupt => PromptAction.Stop - case TerminalEvent.Interrupt => PromptAction.setStatus(Status.Canceled) + case TerminalEvent.Resized(rows, cols) => + PromptAction.updateState(_.copy(terminalSize = Some(rows -> cols))) case _ => PromptAction.Continue end match @@ -103,6 +92,7 @@ end InteractiveTextInput private[cue4s] object InteractiveTextInput: case class State( text: String, + terminalSize: Option[(TerminalRows, TerminalCols)], ): def addText(t: Char) = copy(text = text + t) diff --git a/modules/core/src/main/scala/Prompt.scala b/modules/core/src/main/scala/Prompt.scala index 9c9acd2..394ea49 100644 --- a/modules/core/src/main/scala/Prompt.scala +++ b/modules/core/src/main/scala/Prompt.scala @@ -44,14 +44,16 @@ end Prompt object Prompt: case class Input private ( private val lab: String, - private val validate: String => Option[PromptError] = _ => None, + private val validate: Option[String => Option[PromptError]] = None, private val default: Option[String] = None, ) extends Prompt[String]: - def this(lab: String) = this(lab, _ => None) + def this(lab: String) = this(lab, None) def validate(f: String => Option[PromptError]): Input = - copy(validate = (n: String) => validate(n).orElse(f(n))) + validate match + case Some(value) => copy(validate = Some(in => value(in).orElse(f(in)))) + case None => copy(validate = Some(f)) def default(value: String): Input = copy(default = Some(value)) @@ -61,16 +63,26 @@ object Prompt: output: Output, theme: Theme, symbols: Symbols, - ) = InteractiveTextInput( - prompt = lab, - terminal = terminal, - out = output, - theme = theme, - validate = validate, - hideText = false, - symbols = symbols, - default = default, - ) + ) = + + val base = InteractiveTextInput( + prompt = lab, + terminal = terminal, + out = output, + theme = theme, + // validate = validate, + hideText = false, + symbols = symbols, + default = default, + ) + + validate match + case Some(value) => + base.mapValidated[String](i => value(i).toLeft(i)) + case None => + base + end framework + end Input object Input: @@ -80,17 +92,20 @@ object Prompt: case class PasswordInput private ( private val lab: String, - private val validate: Password => Option[PromptError] = _ => None, + private val validate: Option[Password => Option[PromptError]] = None, private val default: Option[Password] = None, ) extends Prompt[Password]: - def this(lab: String) = this(lab, _ => None) + def this(lab: String) = this(lab, None, None) def default(value: Password): PasswordInput = copy(default = Some(value)) def validate(f: Password => Option[PromptError]): PasswordInput = - copy(validate = (n: Password) => validate(n).orElse(f(n))) + validate match + case Some(value) => + copy(validate = Some(in => value(in).orElse(f(in)))) + case None => copy(validate = Some(f)) override def framework( terminal: Terminal, @@ -104,7 +119,6 @@ object Prompt: terminal = terminal, out = output, theme = theme, - validate = _ => None, hideText = true, symbols = symbols, default = default.map(_.raw), @@ -112,7 +126,7 @@ object Prompt: textBase.mapValidated[Password](str => val pwd = Password(str) - validate(pwd).toLeft(pwd), + validate.getOrElse(_ => None).apply(pwd).toLeft(pwd), ) end framework @@ -126,17 +140,23 @@ object Prompt: case class NumberInput[N: Numeric] private ( private val lab: String, - private val validateNumber: N => Option[PromptError] = (_: N) => None, + private val validateNumber: Option[N => Option[PromptError]] = None, private val default: Option[N] = None, ) extends Prompt[N]: private val num = Numeric[N] - def this(lab: String) = this(lab, _ => None) + def this(lab: String) = this(lab, None) def default(value: N): NumberInput[N] = copy(default = Some(value)) + // def validate(f: N => Option[PromptError]): NumberInput[N] = + // copy(validateNumber = (n: N) => validateNumber(n).orElse(f(n))) + def validate(f: N => Option[PromptError]): NumberInput[N] = - copy(validateNumber = (n: N) => validateNumber(n).orElse(f(n))) + validateNumber match + case Some(value) => + copy(validateNumber = Some(in => value(in).orElse(f(in)))) + case None => copy(validateNumber = Some(f)) def positive = validate(n => Option.when(num.lteq(n, num.zero))(PromptError("must be positive")), @@ -164,7 +184,8 @@ object Prompt: theme: Theme, symbols: Symbols, ) = - val lifted = (n: N) => validateNumber(n).toLeft(n) + val lifted = (n: N) => + validateNumber.getOrElse(_ => None).apply(n).toLeft(n) val transform = (s: String) => Numeric[N] @@ -172,14 +193,11 @@ object Prompt: .toRight(PromptError("not a valid number")) .flatMap(lifted) - val stringValidate = transform(_: String).left.toOption - InteractiveTextInput( prompt = lab, terminal = terminal, out = output, theme = theme, - validate = stringValidate, hideText = false, symbols = symbols, default = default.map(_.toString()), diff --git a/modules/core/src/main/scala/PromptFramework.scala b/modules/core/src/main/scala/PromptFramework.scala index b2594ff..6a5d599 100644 --- a/modules/core/src/main/scala/PromptFramework.scala +++ b/modules/core/src/main/scala/PromptFramework.scala @@ -19,6 +19,35 @@ package cue4s /** A base trait for interactive terminal components. `Result` is the type of * value the component produces when terminated normally. * + * When extending this trait, you need to override PromptState type (which + * should be an **immutable** state, such as case class, which will contain + * everything required to render your prompt). + * + * ```scala + * class MyPrompt(terminal: Terminal, out: Output) + * extends PromptFramework[String](terminal, out): + * override type PromptState = String + * override type Event = TerminalEvent + * + * override def initialState: PromptState = "" + * + * override def extractResult(s: String) = Right(s) + * + * override def handleEvent(event: Event): PromptAction = event match + * case TerminalEvent.Key(KeyEvent.ENTER) => + * PromptAction.TrySubmit + * case TerminalEvent.Char(char) => + * PromptAction.updateState(_ :+ char.toChar) + * case _ => PromptAction.Continue + * + * override def renderState( + * state: PromptState, + * status: Status, + * ): List[String] = + * List(s"Current state: $state") + * end MyPrompt + * ``` + * * @param terminal * @param out */ @@ -26,7 +55,9 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) extends PromptFrameworkPlatform[Result]: self => - /** This type represents the internal state of the component. + /** This type represents the internal state of the component – it should be an + * **immutable** state, such as case class, which will contain everything + * required to render your prompt. */ type PromptState @@ -35,12 +66,12 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) * etc.). Best used with a union type that includes `TerminalEvent` * * Example: - * {{{ + * ```scala * enum MyEvent: * case Ping, Pong * * type Event = TerminalEvent | MyEvent - * }}} + * ``` */ type Event >: TerminalEvent @@ -61,28 +92,26 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) def handleEvent(event: Event): PromptAction /** Terminal rendering of the prompt based purely on its state and status. - * Each list element must be a string NOT containing any newlines. The prompt - * framework mechanism will use the result of this method to incrementally - * update the visible state of the prompt. + * Each list element mu= ?st be a string NOT containing any newlines. The + * prompt framework mechanism will use the result of this method to + * incrementally update the visible state of the prompt. * * This function MUST be pure, as the results might be cached. * * @param state * @param status * @return + * list of lines to be printed to terminal */ def renderState(state: PromptState, status: Status): List[String] + def extractResult(state: PromptState): Either[PromptError, Result] + /** Prompt-specific status */ protected enum Status: - /** All prompts start as [Init], and once it moves to any other status it - * cannot transition back. - */ - case Init - /** [Running] status is where the prompt spends most of its time – reacting * to terminal events and re-rendering. The value might not be available if - * validation returns `Left[PromptError]`. If the + * validation returns `Left[PromptError]` */ case Running(err: Either[PromptError, Result]) @@ -109,13 +138,16 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) protected enum PromptAction: /** Modify status or state, or both */ case Update( - status: Status => Status = identity, state: PromptState => PromptState = identity, ) /** do nothing */ case Continue + case Submit(result: Result) + + case TrySubmit + /** Immediately terminate this prompt, returning an * [CompletionError.Interrupted] error to the caller */ @@ -123,12 +155,6 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) end PromptAction protected object PromptAction: - def set(state: PromptState, status: Status) = - PromptAction.Update(_ => status, _ => state) - - /** Set [Status] to the given value. State remains unchanged */ - def setStatus(f: Status): PromptAction = PromptAction.Update(_ => f) - /** Set [State] to the given value. Status remains unchanged */ def setState(f: PromptState): PromptAction = PromptAction.Update(state = _ => f) @@ -136,6 +162,10 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) /** Update [State] using the given function. [Status] remains unchanged */ def updateState(f: PromptState => PromptState) = PromptAction.Update(state = f) + + def trySubmit: PromptAction = PromptAction.TrySubmit + + def submit(result: Result): PromptAction = PromptAction.Submit(result) end PromptAction /** Construct a new [PromptFramework] for a different type, with the mapping @@ -144,49 +174,115 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) final def mapValidated[Derived]( f: Result => Either[PromptError, Derived], ): PromptFramework[Derived] = + val inner = self new PromptFramework[Derived](terminal, out): - override type PromptState = self.PromptState - override type Event = self.Event + override type PromptState = inner.PromptState + override type Event = inner.Event + + private val outer = this + + override def initialState: PromptState = inner.initialState - override def initialState: PromptState = self.initialState + override val state = + Transition.readOnly(inner.state) + + override val rendering = Transition.readOnly(inner.rendering) + + override val status = LoggedTransition( + "derived status", + Transition.base( + Status.Running(outer.extractResult(outer.state.current)), + ), + out, + ) + + override def extractResult( + state: PromptState, + ): Either[PromptError, Derived] = + inner.extractResult(state).flatMap(f) override def renderState( state: PromptState, status: Status, ): List[String] = - self.renderState(state, self.currentStatus()) + inner.renderState(inner.currentState(), inner.currentStatus()) + + private def statuses( + state: PromptState, + ): (inner.Status.Running, outer.Status.Running) = + inner.extractResult(state) match + case v @ Left(err) => + (inner.Status.Running(v), outer.Status.Running(Left(err))) + case Right(innerValue) => + f(innerValue) match + case v @ Left(err) => + ( + inner.Status.Running(Left(err)), + outer.Status.Running(Left(err)), + ) + case v @ Right(outerValue) => + ( + inner.Status + .Running(Right(innerValue)), + outer.Status.Running(Right(outerValue)), + ) override def handleEvent( event: Event, ): PromptAction = - self.handleEvent(event) match - case self.PromptAction.Update(statusChange, stateChange) => - self.stateTransition(stateChange, statusChange) - - val refinedStatus = - self.currentStatus() match - case self.Status.Finished(result) => - f(result) match - case Left(value) => Status.Running(Left(value)) - case Right(value) => Status.Finished(value) - - case self.Status.Canceled => Status.Canceled - case self.Status.Running(r) => Status.Running(r.flatMap(f)) - case self.Status.Init => Status.Init - - // propagate information backwards... - refinedStatus match - case Status.Running(Left(err)) => - self.stateTransition( - identity, - _ => self.Status.Running(Left(err)), + import inner.PromptAction as InnerPromptAction + val innerResult = inner.handleEvent(event) + out.logLn(s"[derived] Inner result: $innerResult") + innerResult match + case InnerPromptAction.Continue => PromptAction.Continue + case InnerPromptAction.Stop => + inner.stateTransition(status = _ => inner.Status.Canceled) + PromptAction.Stop + case InnerPromptAction.TrySubmit => + statuses(state.current) match + case ( + inner.Status.Running(Right(innerValue)), + outer.Status.Running(Right(outerValue)), + ) => + inner.stateTransition(status = + _ => inner.Status.Finished(innerValue), + ) + PromptAction.Submit(outerValue) + case (innerStatus, outerStatus) => + inner.stateTransition(status = _ => innerStatus) + PromptAction.Continue + + case InnerPromptAction.Submit(result) => + statuses(state.current) match + case ( + inner.Status.Running(Right(innerValue)), + _, + ) => + inner.stateTransition(status = + _ => inner.Status.Finished(innerValue), ) - case _ => + f(result) match + case Left(value) => PromptAction.Continue + case Right(value) => PromptAction.Submit(value) + case (innerStatus, _) => + inner.stateTransition(status = _ => innerStatus) + PromptAction.Continue + + case InnerPromptAction.Update(stateChange) => + val tempState = stateChange(inner.state.current) + val innerStatus = statuses(tempState)._1 - PromptAction.Update(_ => refinedStatus, stateChange) + inner.stateTransition( + state = _ => tempState, + status = _ => innerStatus, + ) - case self.PromptAction.Continue => PromptAction.Continue - case self.PromptAction.Stop => PromptAction.Stop + PromptAction.Continue + + end match + + end handleEvent + end new end mapValidated @@ -194,7 +290,14 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) override def setupBackchannel(notif: Next[Result] => Unit): Unit = backchannel = Some(notif) override def apply(ev: TerminalEvent): Next[Result] = - if ev == TerminalEvent.Init then printPrompt() + out.logLn(s"Handling event $ev") + ev match + case TerminalEvent.Resized(rows, cols) => + this.synchronized: + terminalSize.next(Some(cols)) + printPrompt() + case _ => + manage(handleEvent(ev)) end apply @@ -207,108 +310,139 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) * @param ev */ final def send(ev: Event) = + out.logLn(s"Event sent directly to prompt: $ev") backchannel.foreach(b => b(manage(handleEvent(ev)))) private var backchannel: Option[Next[Result] => Unit] = None private def manage(a: PromptAction) = - a match + out.logLn(s"Interpreting prompt action: $a") + val next = a match case PromptAction.Continue => Next.Continue - case PromptAction.Stop => Next.Stop + case PromptAction.Stop => + stateTransition(status = _ => Status.Canceled) + Next.Stop - case PromptAction.Update(statusF, stateF) => - stateTransition(stateF, statusF) - val next: Next[Result] = currentStatus() match + case PromptAction.Update(stateF) => + stateTransition( + stateF, + _ => Status.Running(extractResult(state.current)), + ) + + status.current match case Status.Finished(result) => Next.Done(result) case Status.Running(_) => Next.Continue case Status.Canceled => Next.Stop - case Status.Init => Next.Continue - printPrompt() - next + case PromptAction.TrySubmit => + extractResult(state.current) match + case Right(result) => + stateTransition(status = _ => Status.Finished(result)) + Next.Done(result) + case _ => Next.Continue + + case PromptAction.Submit(value) => + stateTransition(status = _ => Status.Finished(value)) + out.logLn(s"Submitted value: $value, ${status.current}") + Next.Done(value) + if rendering.changed then printPrompt() + + next end manage private def stateTransition( - stateChange: PromptState => PromptState, - statusChange: Status => Status, + state: PromptState => PromptState = identity, + status: Status => Status = identity, ) = this.synchronized: - state = state.nextFn(stateChange) - status = status.nextFn(statusChange) - rendering = rendering.next( - renderState(state.current, status.current), - ) + val stateChanged = this.state.nextFn(state) + val statusChanged = this.status.nextFn(status) + import Transition.Changed + if stateChanged == Changed.Yes || statusChanged == Changed.Yes then + this.rendering.next( + renderState(this.state.current, this.status.current), + ) end stateTransition private def printPrompt() = - import terminal.* - this.synchronized: - if currentStatus() != Status.Canceled then cursorHide() - rendering.last match - case None => - // initial print - rendering.current.foreach: line => - out.outLn(line) - moveHorizontalTo(0) - moveUp(rendering.current.length).moveHorizontalTo(0) - case Some(previousRendering) => - val paddingLength = - (previousRendering.length - rendering.current.length).max(0) - - inline def pad(n: Int) = List.fill(n)("") - - val (current, previous) = - if rendering.current.length > previousRendering.length then - ( - rendering.current, - previousRendering ++ pad( - rendering.current.length - previousRendering.length, - ), - ) - else - ( - rendering.current ++ pad( - previousRendering.length - rendering.current.length, - ), - previousRendering, - ) - - def render = - current - .zip(previous) - .foreach: (line, oldLine) => - if line != oldLine then - moveHorizontalTo(0).eraseEntireLine() - out.out(line) - moveDown(1) - - if isRunning(currentStatus()) then - render - moveUp(current.length).moveHorizontalTo(0) - else // we are finished - render - // do not leave empty lines behind - move cursor up - moveUp(paddingLength).moveHorizontalTo(0) - end if - end match - + terminal.cursorHide() + rendering.last match + case Some(value) if value.length > rendering.current.length => + out.logLn("Clearing because previous rendering was longer") + terminal.withRestore: + clear(value, terminalSize.current) + case None => + // We don't use cursor save/restore here because after outputing empty lines, + // the terminal might scroll and the saved cursor position would be incorrect + out.logLn("Freeing up empty space for initial rendering") + rendering.current.foreach(_ => out.outLn("")) + terminal.moveUp(rendering.current.length).moveHorizontalTo(0) + rendering.nextFn(identity) + case _ => + out.logLn("Clearing just for current rendering") + terminal.withRestore: + clear(rendering.current, terminalSize.current) + end match + + out.logLn( + s"Rendering ${rendering.current} with ${terminalSize.current}", + ) + currentStatus() match + case _: Status.Finished | Status.Canceled => + rendering.current.foreach(l => out.outLn(l)) + terminal.cursorShow() + case _ => + terminal.withRestore: + rendering.current.foreach(l => out.outLn(l)) end printPrompt - private def isRunning(status: Status) = - status match - case Status.Finished(_) => false - case Status.Canceled => false - case _ => true - - private var state = Transition( - initialState, + def clear(lines: List[String], cols: Option[TerminalCols]) = + cols match + case Some(cols) => + val rows = + lines + .map(l => + 1 + math.floor((l.length.toDouble - 1).max(0) / cols).toInt, + ) + .sum + out.logLn(s"Clearing ${rows} rows (with terminal size)") + for _ <- 0 until rows do + terminal.eraseEntireLine() + terminal.moveDown(1) + case None => + out.logLn(s"Clearing ${lines.length} rows (no terminal size)") + for _ <- lines.indices do + terminal.eraseEntireLine() + terminal.moveDown(1) + + protected val state: Transition[self.PromptState] = LoggedTransition( + "state", + Transition.base( + initialState, + ), + out, ) - private var status = Transition[Status]( - Status.Init, + + protected val status = LoggedTransition( + "status", + Transition.base( + Status.Running(extractResult(initialState)), + ), + out, ) - private var rendering = Transition( - renderState(currentState(), currentStatus()), + + protected val rendering: Transition[List[String]] = + LoggedTransition( + "rendering", + Transition.base( + renderState(currentState(), currentStatus()), + ), + out, + ) + + private var terminalSize = Transition.base[Option[TerminalCols]]( + None, ) end PromptFramework diff --git a/modules/core/src/snapshots/core/alternatives_cancel_input b/modules/core/src/snapshots/core/alternatives_cancel_input index 65da855..f2623a7 100644 --- a/modules/core/src/snapshots/core/alternatives_cancel_input +++ b/modules/core/src/snapshots/core/alternatives_cancel_input @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Interrupt ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃× how do you do fellow kids? ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/alternatives_cancel_multiple_derived b/modules/core/src/snapshots/core/alternatives_cancel_multiple_derived index 60caa6e..cb449c0 100644 --- a/modules/core/src/snapshots/core/alternatives_cancel_multiple_derived +++ b/modules/core/src/snapshots/core/alternatives_cancel_multiple_derived @@ -12,6 +12,7 @@ Event.Key(TAB) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What would you like for lunch › ┃ ┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃show pizza some love! ┃ ┃ ◯ pizza ┃ ┃ ◉ steak ┃ ┃ ◉ sweet potato ┃ @@ -33,12 +34,12 @@ Event.Key(TAB) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What would you like for lunch › ┃ ┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ -┃show pizza some love! ┃ ┃ ◉ pizza ┃ ┃ ◉ steak ┃ ┃ ◉ sweet potato ┃ ┃ ◉ fried chicken ┃ ┃ ┃ +┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ diff --git a/modules/core/src/snapshots/core/alternatives_confirm_agree b/modules/core/src/snapshots/core/alternatives_confirm_agree index e1136fa..f37e3b6 100644 --- a/modules/core/src/snapshots/core/alternatives_confirm_agree +++ b/modules/core/src/snapshots/core/alternatives_confirm_agree @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (y/N)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('y') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … yes ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/alternatives_confirm_cancel b/modules/core/src/snapshots/core/alternatives_confirm_cancel index 631b8a9..2777d4a 100644 --- a/modules/core/src/snapshots/core/alternatives_confirm_cancel +++ b/modules/core/src/snapshots/core/alternatives_confirm_cancel @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Interrupt ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃× Are you sure? ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/alternatives_confirm_decline b/modules/core/src/snapshots/core/alternatives_confirm_decline index 3801c78..fcfd72a 100644 --- a/modules/core/src/snapshots/core/alternatives_confirm_decline +++ b/modules/core/src/snapshots/core/alternatives_confirm_decline @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/alternatives_confirm_default b/modules/core/src/snapshots/core/alternatives_confirm_default index 2352497..7ec3969 100644 --- a/modules/core/src/snapshots/core/alternatives_confirm_default +++ b/modules/core/src/snapshots/core/alternatives_confirm_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … yes ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/derived_validated_input b/modules/core/src/snapshots/core/derived_validated_input index fe08f97..86d16eb 100644 --- a/modules/core/src/snapshots/core/derived_validated_input +++ b/modules/core/src/snapshots/core/derived_validated_input @@ -2,110 +2,94 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › g┃ ┃look up, it's not g ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › goo┃ ┃look up, it's not goo ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › good┃ ┃look up, it's not good ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › good┃ ┃look up, it's not good ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › goo ┃ ┃look up, it's not goo ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go ┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › g ┃ ┃look up, it's not g ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃look up, it's not ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('b') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › b ┃ ┃look up, it's not b ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › bl ┃ ┃look up, it's not bl ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('u') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blu ┃ ┃look up, it's not blu ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … blue┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/fallback_confirm b/modules/core/src/snapshots/core/fallback_confirm index faefd83..acbcbab 100644 --- a/modules/core/src/snapshots/core/fallback_confirm +++ b/modules/core/src/snapshots/core/fallback_confirm @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? » (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃√ Are you sure? ... no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/fallback_input b/modules/core/src/snapshots/core/fallback_input index f081354..f4f7f8e 100644 --- a/modules/core/src/snapshots/core/fallback_input +++ b/modules/core/src/snapshots/core/fallback_input @@ -2,47 +2,40 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » g┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » goo┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃√ how do you do fellow kids? ... good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/input b/modules/core/src/snapshots/core/input index 6053bfd..05d6362 100644 --- a/modules/core/src/snapshots/core/input +++ b/modules/core/src/snapshots/core/input @@ -2,47 +2,40 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › g┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › goo┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ how do you do fellow kids? … good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/input_default b/modules/core/src/snapshots/core/input_default index 2c0a40a..11ad6f9 100644 --- a/modules/core/src/snapshots/core/input_default +++ b/modules/core/src/snapshots/core/input_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/input_default_ignore b/modules/core/src/snapshots/core/input_default_ignore index 5d236a4..ebce565 100644 --- a/modules/core/src/snapshots/core/input_default_ignore +++ b/modules/core/src/snapshots/core/input_default_ignore @@ -2,53 +2,44 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blu ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › bl ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › b ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('r') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › r ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › re ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › red ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … red ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/number_default b/modules/core/src/snapshots/core/number_default index 1a687a4..2a7754f 100644 --- a/modules/core/src/snapshots/core/number_default +++ b/modules/core/src/snapshots/core/number_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25.7┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ think of a number … 25.7┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/number_input b/modules/core/src/snapshots/core/number_input index 5fd6148..f768857 100644 --- a/modules/core/src/snapshots/core/number_input +++ b/modules/core/src/snapshots/core/number_input @@ -2,187 +2,160 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.┃ ┃must be no less than 5.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.0┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.0┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3. ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('h') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › h ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › h ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('1') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31 ┃ ┃must be no more than 30.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.┃ ┃must be no more than 30.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.0┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.0┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31. ┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31 ┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('2') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 2 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('5') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25 ┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25. ┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25.0┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ think of a number … 25.0┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/password_input b/modules/core/src/snapshots/core/password_input index c954f5d..7713cd7 100644 --- a/modules/core/src/snapshots/core/password_input +++ b/modules/core/src/snapshots/core/password_input @@ -2,61 +2,52 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › **┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › **┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ***┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ****┃ ┃must contain a digit ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ****┃ ┃must contain a digit ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('5') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *****┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose your password … *****┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/password_input_default b/modules/core/src/snapshots/core/password_input_default index 1fd17c7..91d1cd7 100644 --- a/modules/core/src/snapshots/core/password_input_default +++ b/modules/core/src/snapshots/core/password_input_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose your password … *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/promptchain b/modules/core/src/snapshots/core/promptchain index 01b4def..0d5ed21 100644 --- a/modules/core/src/snapshots/core/promptchain +++ b/modules/core/src/snapshots/core/promptchain @@ -2,43 +2,36 @@ Event.Init ┏━━━━━━━━━━━━┓ ┃? Sir...? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━┛ Event.Char('h') ┏━━━━━━━━━━━━━┓ ┃? Sir...? › h┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━┓ ┃? Sir...? › he┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━┓ ┃? Sir...? › hel┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━━┓ ┃? Sir...? › hell┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━┓ ┃? Sir...? › hello┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━┓ ┃✔ Sir...? … hello┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━┛ Event.Init @@ -60,123 +53,103 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('2') ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › 2┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('4') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › 24┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ how old are you? … 24┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('1') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.1┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('4') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.14┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What is PI to first 2 digits? … 3.14┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Do you want to build a snowman? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Do you want to build a snowman? … no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('q') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › *┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('w') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › **┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ***┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('r') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('t') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('y') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ******┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose a password … ******┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/alternatives_cancel_input b/modules/core/src/snapshots/coreJS/alternatives_cancel_input index 65da855..f2623a7 100644 --- a/modules/core/src/snapshots/coreJS/alternatives_cancel_input +++ b/modules/core/src/snapshots/coreJS/alternatives_cancel_input @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Interrupt ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃× how do you do fellow kids? ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/alternatives_cancel_multiple_derived b/modules/core/src/snapshots/coreJS/alternatives_cancel_multiple_derived index 60caa6e..cb449c0 100644 --- a/modules/core/src/snapshots/coreJS/alternatives_cancel_multiple_derived +++ b/modules/core/src/snapshots/coreJS/alternatives_cancel_multiple_derived @@ -12,6 +12,7 @@ Event.Key(TAB) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What would you like for lunch › ┃ ┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃show pizza some love! ┃ ┃ ◯ pizza ┃ ┃ ◉ steak ┃ ┃ ◉ sweet potato ┃ @@ -33,12 +34,12 @@ Event.Key(TAB) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What would you like for lunch › ┃ ┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ -┃show pizza some love! ┃ ┃ ◉ pizza ┃ ┃ ◉ steak ┃ ┃ ◉ sweet potato ┃ ┃ ◉ fried chicken ┃ ┃ ┃ +┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ diff --git a/modules/core/src/snapshots/coreJS/alternatives_confirm_agree b/modules/core/src/snapshots/coreJS/alternatives_confirm_agree index e1136fa..f37e3b6 100644 --- a/modules/core/src/snapshots/coreJS/alternatives_confirm_agree +++ b/modules/core/src/snapshots/coreJS/alternatives_confirm_agree @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (y/N)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('y') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … yes ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/alternatives_confirm_cancel b/modules/core/src/snapshots/coreJS/alternatives_confirm_cancel index 631b8a9..2777d4a 100644 --- a/modules/core/src/snapshots/coreJS/alternatives_confirm_cancel +++ b/modules/core/src/snapshots/coreJS/alternatives_confirm_cancel @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Interrupt ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃× Are you sure? ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/alternatives_confirm_decline b/modules/core/src/snapshots/coreJS/alternatives_confirm_decline index 3801c78..fcfd72a 100644 --- a/modules/core/src/snapshots/coreJS/alternatives_confirm_decline +++ b/modules/core/src/snapshots/coreJS/alternatives_confirm_decline @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/alternatives_confirm_default b/modules/core/src/snapshots/coreJS/alternatives_confirm_default index 2352497..7ec3969 100644 --- a/modules/core/src/snapshots/coreJS/alternatives_confirm_default +++ b/modules/core/src/snapshots/coreJS/alternatives_confirm_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … yes ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/derived_validated_input b/modules/core/src/snapshots/coreJS/derived_validated_input index fe08f97..86d16eb 100644 --- a/modules/core/src/snapshots/coreJS/derived_validated_input +++ b/modules/core/src/snapshots/coreJS/derived_validated_input @@ -2,110 +2,94 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › g┃ ┃look up, it's not g ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › goo┃ ┃look up, it's not goo ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › good┃ ┃look up, it's not good ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › good┃ ┃look up, it's not good ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › goo ┃ ┃look up, it's not goo ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go ┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › g ┃ ┃look up, it's not g ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃look up, it's not ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('b') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › b ┃ ┃look up, it's not b ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › bl ┃ ┃look up, it's not bl ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('u') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blu ┃ ┃look up, it's not blu ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … blue┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/fallback_confirm b/modules/core/src/snapshots/coreJS/fallback_confirm index faefd83..acbcbab 100644 --- a/modules/core/src/snapshots/coreJS/fallback_confirm +++ b/modules/core/src/snapshots/coreJS/fallback_confirm @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? » (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃√ Are you sure? ... no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/fallback_input b/modules/core/src/snapshots/coreJS/fallback_input index f081354..f4f7f8e 100644 --- a/modules/core/src/snapshots/coreJS/fallback_input +++ b/modules/core/src/snapshots/coreJS/fallback_input @@ -2,47 +2,40 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » g┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » goo┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃√ how do you do fellow kids? ... good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/input b/modules/core/src/snapshots/coreJS/input index 6053bfd..05d6362 100644 --- a/modules/core/src/snapshots/coreJS/input +++ b/modules/core/src/snapshots/coreJS/input @@ -2,47 +2,40 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › g┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › goo┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ how do you do fellow kids? … good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/input_default b/modules/core/src/snapshots/coreJS/input_default index 2c0a40a..11ad6f9 100644 --- a/modules/core/src/snapshots/coreJS/input_default +++ b/modules/core/src/snapshots/coreJS/input_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/input_default_ignore b/modules/core/src/snapshots/coreJS/input_default_ignore index 5d236a4..ebce565 100644 --- a/modules/core/src/snapshots/coreJS/input_default_ignore +++ b/modules/core/src/snapshots/coreJS/input_default_ignore @@ -2,53 +2,44 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blu ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › bl ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › b ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('r') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › r ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › re ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › red ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … red ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/number_default b/modules/core/src/snapshots/coreJS/number_default index 26cd55d..5a250b0 100644 --- a/modules/core/src/snapshots/coreJS/number_default +++ b/modules/core/src/snapshots/coreJS/number_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25.700000762939453┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ think of a number … 25.700000762939453┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/number_input b/modules/core/src/snapshots/coreJS/number_input index 0fed44a..435410d 100644 --- a/modules/core/src/snapshots/coreJS/number_input +++ b/modules/core/src/snapshots/coreJS/number_input @@ -2,187 +2,160 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.0┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.0┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3. ┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('h') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › h ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › h ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('1') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31 ┃ ┃must be no more than 30 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.┃ ┃must be no more than 30 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.0┃ ┃must be no more than 30 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.0┃ ┃must be no more than 30 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31. ┃ ┃must be no more than 30 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31 ┃ ┃must be no more than 30 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('2') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 2 ┃ ┃must be no less than 5 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('5') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25 ┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25. ┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25.0┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ think of a number … 25.0┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/password_input b/modules/core/src/snapshots/coreJS/password_input index c954f5d..7713cd7 100644 --- a/modules/core/src/snapshots/coreJS/password_input +++ b/modules/core/src/snapshots/coreJS/password_input @@ -2,61 +2,52 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › **┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › **┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ***┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ****┃ ┃must contain a digit ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ****┃ ┃must contain a digit ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('5') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *****┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose your password … *****┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/password_input_default b/modules/core/src/snapshots/coreJS/password_input_default index 1fd17c7..91d1cd7 100644 --- a/modules/core/src/snapshots/coreJS/password_input_default +++ b/modules/core/src/snapshots/coreJS/password_input_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose your password … *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/promptchain b/modules/core/src/snapshots/coreJS/promptchain index 01b4def..0d5ed21 100644 --- a/modules/core/src/snapshots/coreJS/promptchain +++ b/modules/core/src/snapshots/coreJS/promptchain @@ -2,43 +2,36 @@ Event.Init ┏━━━━━━━━━━━━┓ ┃? Sir...? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━┛ Event.Char('h') ┏━━━━━━━━━━━━━┓ ┃? Sir...? › h┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━┓ ┃? Sir...? › he┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━┓ ┃? Sir...? › hel┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━━┓ ┃? Sir...? › hell┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━┓ ┃? Sir...? › hello┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━┓ ┃✔ Sir...? … hello┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━┛ Event.Init @@ -60,123 +53,103 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('2') ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › 2┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('4') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › 24┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ how old are you? … 24┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('1') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.1┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('4') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.14┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What is PI to first 2 digits? … 3.14┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Do you want to build a snowman? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Do you want to build a snowman? … no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('q') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › *┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('w') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › **┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ***┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('r') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('t') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('y') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ******┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose a password … ******┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/alternatives_cancel_input b/modules/core/src/snapshots/coreNative/alternatives_cancel_input index 65da855..f2623a7 100644 --- a/modules/core/src/snapshots/coreNative/alternatives_cancel_input +++ b/modules/core/src/snapshots/coreNative/alternatives_cancel_input @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Interrupt ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃× how do you do fellow kids? ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/alternatives_cancel_multiple_derived b/modules/core/src/snapshots/coreNative/alternatives_cancel_multiple_derived index 60caa6e..cb449c0 100644 --- a/modules/core/src/snapshots/coreNative/alternatives_cancel_multiple_derived +++ b/modules/core/src/snapshots/coreNative/alternatives_cancel_multiple_derived @@ -12,6 +12,7 @@ Event.Key(TAB) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What would you like for lunch › ┃ ┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃show pizza some love! ┃ ┃ ◯ pizza ┃ ┃ ◉ steak ┃ ┃ ◉ sweet potato ┃ @@ -33,12 +34,12 @@ Event.Key(TAB) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What would you like for lunch › ┃ ┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ -┃show pizza some love! ┃ ┃ ◉ pizza ┃ ┃ ◉ steak ┃ ┃ ◉ sweet potato ┃ ┃ ◉ fried chicken ┃ ┃ ┃ +┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ diff --git a/modules/core/src/snapshots/coreNative/alternatives_confirm_agree b/modules/core/src/snapshots/coreNative/alternatives_confirm_agree index e1136fa..f37e3b6 100644 --- a/modules/core/src/snapshots/coreNative/alternatives_confirm_agree +++ b/modules/core/src/snapshots/coreNative/alternatives_confirm_agree @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (y/N)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('y') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … yes ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/alternatives_confirm_cancel b/modules/core/src/snapshots/coreNative/alternatives_confirm_cancel index 631b8a9..2777d4a 100644 --- a/modules/core/src/snapshots/coreNative/alternatives_confirm_cancel +++ b/modules/core/src/snapshots/coreNative/alternatives_confirm_cancel @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Interrupt ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃× Are you sure? ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/alternatives_confirm_decline b/modules/core/src/snapshots/coreNative/alternatives_confirm_decline index 3801c78..fcfd72a 100644 --- a/modules/core/src/snapshots/coreNative/alternatives_confirm_decline +++ b/modules/core/src/snapshots/coreNative/alternatives_confirm_decline @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/alternatives_confirm_default b/modules/core/src/snapshots/coreNative/alternatives_confirm_default index 2352497..7ec3969 100644 --- a/modules/core/src/snapshots/coreNative/alternatives_confirm_default +++ b/modules/core/src/snapshots/coreNative/alternatives_confirm_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Are you sure? … yes ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/derived_validated_input b/modules/core/src/snapshots/coreNative/derived_validated_input index fe08f97..86d16eb 100644 --- a/modules/core/src/snapshots/coreNative/derived_validated_input +++ b/modules/core/src/snapshots/coreNative/derived_validated_input @@ -2,110 +2,94 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › g┃ ┃look up, it's not g ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › goo┃ ┃look up, it's not goo ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › good┃ ┃look up, it's not good ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › good┃ ┃look up, it's not good ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › goo ┃ ┃look up, it's not goo ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › go ┃ ┃look up, it's not go ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › g ┃ ┃look up, it's not g ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃look up, it's not ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('b') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › b ┃ ┃look up, it's not b ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › bl ┃ ┃look up, it's not bl ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('u') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blu ┃ ┃look up, it's not blu ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … blue┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/fallback_confirm b/modules/core/src/snapshots/coreNative/fallback_confirm index faefd83..acbcbab 100644 --- a/modules/core/src/snapshots/coreNative/fallback_confirm +++ b/modules/core/src/snapshots/coreNative/fallback_confirm @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Are you sure? » (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃√ Are you sure? ... no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/fallback_input b/modules/core/src/snapshots/coreNative/fallback_input index f081354..f4f7f8e 100644 --- a/modules/core/src/snapshots/coreNative/fallback_input +++ b/modules/core/src/snapshots/coreNative/fallback_input @@ -2,47 +2,40 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » g┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » goo┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? » good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃√ how do you do fellow kids? ... good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/input b/modules/core/src/snapshots/coreNative/input index 6053bfd..05d6362 100644 --- a/modules/core/src/snapshots/coreNative/input +++ b/modules/core/src/snapshots/coreNative/input @@ -2,47 +2,40 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › g┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › go┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › goo┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how do you do fellow kids? › good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ how do you do fellow kids? … good┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/input_default b/modules/core/src/snapshots/coreNative/input_default index 2c0a40a..11ad6f9 100644 --- a/modules/core/src/snapshots/coreNative/input_default +++ b/modules/core/src/snapshots/coreNative/input_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/input_default_ignore b/modules/core/src/snapshots/coreNative/input_default_ignore index 5d236a4..ebce565 100644 --- a/modules/core/src/snapshots/coreNative/input_default_ignore +++ b/modules/core/src/snapshots/coreNative/input_default_ignore @@ -2,53 +2,44 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blue┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › blu ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › bl ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › b ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('r') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › r ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › re ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What color is the sky? › red ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What color is the sky? … red ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/number_default b/modules/core/src/snapshots/coreNative/number_default index 1a687a4..2a7754f 100644 --- a/modules/core/src/snapshots/coreNative/number_default +++ b/modules/core/src/snapshots/coreNative/number_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25.7┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ think of a number … 25.7┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/number_input b/modules/core/src/snapshots/coreNative/number_input index 5fd6148..f768857 100644 --- a/modules/core/src/snapshots/coreNative/number_input +++ b/modules/core/src/snapshots/coreNative/number_input @@ -2,187 +2,160 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.┃ ┃must be no less than 5.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.0┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3.0┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3. ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('h') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › h ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › h ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('1') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31 ┃ ┃must be no more than 30.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.┃ ┃must be no more than 30.0┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.0┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31.0┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31. ┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 31 ┃ ┃must be no more than 30.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 3 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(DELETE) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › ┃ ┃not a valid number ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('2') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 2 ┃ ┃must be no less than 5.0 ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('5') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25 ┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25. ┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('0') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? think of a number › 25.0┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ think of a number … 25.0┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/password_input b/modules/core/src/snapshots/coreNative/password_input index c954f5d..7713cd7 100644 --- a/modules/core/src/snapshots/coreNative/password_input +++ b/modules/core/src/snapshots/coreNative/password_input @@ -2,61 +2,52 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('g') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › **┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › **┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ***┃ ┃too short! ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('d') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ****┃ ┃must contain a digit ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › ****┃ ┃must contain a digit ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('5') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *****┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose your password … *****┃ ┃ ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/password_input_default b/modules/core/src/snapshots/coreNative/password_input_default index 1fd17c7..91d1cd7 100644 --- a/modules/core/src/snapshots/coreNative/password_input_default +++ b/modules/core/src/snapshots/coreNative/password_input_default @@ -2,11 +2,9 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose your password › *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose your password … *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/promptchain b/modules/core/src/snapshots/coreNative/promptchain index 01b4def..0d5ed21 100644 --- a/modules/core/src/snapshots/coreNative/promptchain +++ b/modules/core/src/snapshots/coreNative/promptchain @@ -2,43 +2,36 @@ Event.Init ┏━━━━━━━━━━━━┓ ┃? Sir...? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━┛ Event.Char('h') ┏━━━━━━━━━━━━━┓ ┃? Sir...? › h┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━┓ ┃? Sir...? › he┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━┓ ┃? Sir...? › hel┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━┛ Event.Char('l') ┏━━━━━━━━━━━━━━━━┓ ┃? Sir...? › hell┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━┛ Event.Char('o') ┏━━━━━━━━━━━━━━━━━┓ ┃? Sir...? › hello┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━┓ ┃✔ Sir...? … hello┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━┛ Event.Init @@ -60,123 +53,103 @@ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('2') ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › 2┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('4') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? how old are you? › 24┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ how old are you? … 24┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('3') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('.') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('1') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.1┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('4') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? What is PI to first 2 digits? › 3.14┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ What is PI to first 2 digits? … 3.14┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Do you want to build a snowman? › (Y/n)┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('n') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Do you want to build a snowman? … no ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Init ┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('q') ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › *┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('w') ┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › **┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('e') ┏━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ***┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('r') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('t') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › *****┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Char('y') ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃? Choose a password › ******┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Event.Key(ENTER) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃✔ Choose a password … ******┃ ┃ ┃ -┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/test/scala/ExampleTests.scala b/modules/core/src/test/scala/ExampleTests.scala index 956fc42..1fd83e2 100644 --- a/modules/core/src/test/scala/ExampleTests.scala +++ b/modules/core/src/test/scala/ExampleTests.scala @@ -146,6 +146,32 @@ class ExampleTests extends munit.FunSuite, TerminalTests: true, ) + terminalTestComplete("alternatives.multiple.derived.revalidation")( + Prompt.MultipleChoice + .withNoneSelected( + "Pick at least one", + List("alpha", "beta", "gamma"), + ) + .mapValidated(ls => + Either.cond( + ls.nonEmpty, + ls, + PromptError("Please select at least one."), + ), + ), + list( + TerminalEvent.Init, + ENTER, // reject: nothing selected + TAB, // select alpha + TAB, // deselect alpha + ENTER, // reject again: nothing selected + TAB, // select alpha + ENTER, // accept: alpha selected + ), + List("alpha"), + // log = true, + ) + terminalTest("alternatives.confirm.cancel")( Prompt.Confirmation("Are you sure?"), list( @@ -185,6 +211,24 @@ class ExampleTests extends munit.FunSuite, TerminalTests: "good", ) + terminalTestComplete("input.terminal.size")( + Prompt + .Input("how do you do fellow kids?") + .validate(value => + if value.length < 4 then Some(PromptError("too short!")) + else None, + ), + list( + TerminalEvent.Init, + TerminalEvent.Resized(TerminalRows(20), TerminalCols(100)), + chars("go"), + ENTER, // prevents submission + chars("od"), + ENTER, + ), + "good", + ) + terminalTestComplete("input.default")( Prompt .Input("What color is the sky?") @@ -264,7 +308,6 @@ class ExampleTests extends munit.FunSuite, TerminalTests: ENTER, ), true, - log = false, ) terminalTestComplete("number.default")( diff --git a/modules/core/src/test/scala/TerminalTests.scala b/modules/core/src/test/scala/TerminalTests.scala index dfbbdb8..b1e3536 100644 --- a/modules/core/src/test/scala/TerminalTests.scala +++ b/modules/core/src/test/scala/TerminalTests.scala @@ -114,7 +114,7 @@ trait TerminalTests extends MunitSnapshotsIntegration: ): Result[Next[T]] = val sb = new java.lang.StringBuilder val logger: String => Unit = - if log then s => sb.append(s + "\n") else _ => () + if log then s => sb.append(s) else _ => () val term = TracingTerminal(Output.Delegate(_ => (), _ => ())) val capturing = Output.Delegate(term.writer, logger) diff --git a/modules/example/src/main/scala-jvm-native/UserComponent.scala b/modules/example/src/main/scala-jvm-native/UserComponent.scala index 7d3ddbe..22ccc89 100644 --- a/modules/example/src/main/scala-jvm-native/UserComponent.scala +++ b/modules/example/src/main/scala-jvm-native/UserComponent.scala @@ -56,6 +56,9 @@ class TicTacToe(terminal: Terminal, out: Output) override def initialState: PromptState = State.init + override def extractResult(state: State) = + state.finished.toRight(PromptError("Game is not over yet")) + override def handleEvent(event: TerminalEvent | TimerEvent): PromptAction = event match case t: TimerEvent => handleTimerEvent(t) @@ -90,20 +93,18 @@ class TicTacToe(terminal: Terminal, out: Output) case TerminalEvent.Char(' ') => checkWin case TerminalEvent.Interrupt => - PromptAction.setStatus(Status.Canceled) + PromptAction.Stop case _ => PromptAction.Continue private def checkWin = val cur = currentState() val newState = cur.toggle - val newStatus = newState.finished match + newState.finished match case Some(value) => - Status.Finished(value) + PromptAction.Submit(value) case None => - currentStatus() - - PromptAction.set(newState, newStatus) + PromptAction.updateState(_ => newState) end checkWin override def renderState(state: State, status: Status): List[String] = diff --git a/modules/example/src/main/scala-jvm-native/sync.scala b/modules/example/src/main/scala-jvm-native/sync.scala index 9c6b48f..6b2477d 100644 --- a/modules/example/src/main/scala-jvm-native/sync.scala +++ b/modules/example/src/main/scala-jvm-native/sync.scala @@ -22,11 +22,12 @@ import cue4s.Prompt.PasswordInput.Password @main def sync(which: String*) = Prompts.sync.use: prompts => val examples = Map[String, (String, SyncPrompts => Unit)]( - "confirm" -> ("Yes/No confirmation", likeCats), - "single-choice" -> ("Single choice", day), - "multi-choice" -> ("Multiple choice", letters), - "validated-int" -> ("Validated integer", seasons), - "password" -> ("Password", password), + "confirm" -> ("Yes/No confirmation", likeCats), + "single-choice" -> ("Single choice", day), + "multi-choice" -> ("Multiple choice", letters), + "validated-int" -> ("Validated integer", seasons), + "password" -> ("Password", password), + "validated-multi" -> ("Validated multiple choice", validatedMultiChoice), ) if which.isEmpty then @@ -120,3 +121,22 @@ def seasons(prompts: SyncPrompts): Completion[Int] = def password(prompts: SyncPrompts): Completion[Password] = prompts.password("Choose a new password") + +def validatedMultiChoice(prompts: SyncPrompts): Unit = + val result = prompts.run( + Prompt.MultipleChoice + .withNoneSelected( + "Pick at least one letter", + ('A' to 'Z').zip(('B' to 'Z') :+ 'A').map(_.toString).toList, + ) + .withWindowSize(7) + .mapValidated(ls => + Either.cond( + ls.nonEmpty, + ls, + PromptError("Please select at least one letter."), + ), + ), + ) + println(s"Result: $result") +end validatedMultiChoice From 2b9acf0c59d4027a76ea4ecd2813a6d6c14797f6 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:02:40 +0100 Subject: [PATCH 05/12] Move rendering to fansi --- .../main/scala/InteractiveConfirmation.scala | 18 ++--- .../scala/InteractiveMultipleChoice.scala | 15 ++-- .../src/main/scala/InteractiveTextInput.scala | 14 ++-- .../core/src/main/scala/TextFormatting.scala | 78 +++++++++---------- modules/core/src/main/scala/Theme.scala | 78 +++++++++---------- 5 files changed, 99 insertions(+), 104 deletions(-) diff --git a/modules/core/src/main/scala/InteractiveConfirmation.scala b/modules/core/src/main/scala/InteractiveConfirmation.scala index 3bbcd59..9a9ee5e 100644 --- a/modules/core/src/main/scala/InteractiveConfirmation.scala +++ b/modules/core/src/main/scala/InteractiveConfirmation.scala @@ -58,28 +58,26 @@ private[cue4s] class InteractiveConfirmation( state: Boolean, status: Status, ): List[String] = - val lines = List.newBuilder[String] + val lines = List.newBuilder[fansi.Str] import symbols.* status match - case Status.Init => - lines += "? ".focused + prompt.prompt + - (s" $promptCue (" + (if default then "Y/n" else "y/N") + ")").hint case Status.Running(error) => - lines += "? ".focused + prompt.prompt + + lines += "? ".focused ++ prompt.prompt ++ (s" $promptCue (" + (if default then "Y/n" else "y/N") + ")").hint error.left.toOption.foreach: err => lines += err.error case Status.Finished(res) => - lines += s"$promptDone ".focused + prompt.prompt + s" $ellipsis ".hint + + lines += s"$promptDone ".focused ++ + prompt.prompt ++ + s" $ellipsis ".hint ++ (if res then "yes" else "no").emphasis case Status.Canceled => - lines += s"$promptCancelled ".canceled + prompt.emphasis + lines += s"$promptCancelled ".canceled ++ prompt.emphasis end match + // lines += "" - lines += "" - - lines.result() + lines.result().map(_.render) end renderState end InteractiveConfirmation diff --git a/modules/core/src/main/scala/InteractiveMultipleChoice.scala b/modules/core/src/main/scala/InteractiveMultipleChoice.scala index 11b7011..1c95789 100644 --- a/modules/core/src/main/scala/InteractiveMultipleChoice.scala +++ b/modules/core/src/main/scala/InteractiveMultipleChoice.scala @@ -54,14 +54,14 @@ private[cue4s] class InteractiveMultipleChoice( st: State, status: Status, ): List[String] = - val lines = List.newBuilder[String] + val lines = List.newBuilder[fansi.Str] import symbols.* status match - case Status.Running(_) | Status.Init => - lines += "? ".focused + lab.prompt + s" $promptCue ".prompt + st.text.input - lines += "Tab".emphasis + " to toggle, " + "Shift+Tab".emphasis + " to toggle all, " + "Enter".emphasis + " to submit." + case Status.Running(_) => + lines += "? ".focused ++ lab.prompt ++ s" $promptCue ".prompt ++ st.text.input + lines += "Tab".emphasis ++ " to toggle, " ++ "Shift+Tab".emphasis ++ " to toggle all, " ++ "Enter".emphasis ++ " to submit." status match case Status.Running(Left(err)) => @@ -96,7 +96,7 @@ private[cue4s] class InteractiveMultipleChoice( end match case Status.Finished(ids) => - lines += s"$promptDone ".focused + lab.prompt + lines += s"$promptDone ".focused ++ lab.prompt if ids.isEmpty then lines += "nothing selected".nothingSelected else @@ -105,12 +105,11 @@ private[cue4s] class InteractiveMultipleChoice( lines += s" $altSelected " + value.emphasis case Status.Canceled => - lines += s"$promptCancelled ".canceled + - (lab + " ").prompt + lines += s"$promptCancelled ".canceled ++ (lab + " ").prompt lines += "" end match - lines.result() + lines.result().map(_.render) end renderState override def extractResult(state: State): Either[PromptError, List[String]] = diff --git a/modules/core/src/main/scala/InteractiveTextInput.scala b/modules/core/src/main/scala/InteractiveTextInput.scala index 9102f37..4ed21f1 100644 --- a/modules/core/src/main/scala/InteractiveTextInput.scala +++ b/modules/core/src/main/scala/InteractiveTextInput.scala @@ -63,28 +63,26 @@ private[cue4s] class InteractiveTextInput( st: State, status: Status, ): List[String] = - val lines = List.newBuilder[String] + val lines = List.newBuilder[fansi.Str] import symbols.* val txt = if hideText then "*" * st.text.length else st.text status match - case Status.Init => - lines += "? ".focused + prompt.prompt + s" $promptCue " + txt.input case Status.Running(err) => - lines += "? ".focused + prompt.prompt + s" $promptCue " + txt.input + lines += "? ".focused ++ prompt.prompt ++ s" $promptCue ".hint ++ txt.input err.left.toOption.foreach: err => lines += err.error case Status.Finished(res) => val txt = if hideText then "*" * res.length else res - lines += s"$promptDone ".focused + prompt.prompt + s" $ellipsis ".hint + txt.emphasis + lines += s"$promptDone ".focused ++ prompt.prompt ++ s" $ellipsis ".hint ++ txt.emphasis case Status.Canceled => - lines += s"$promptCancelled ".canceled + prompt.prompt + lines += s"$promptCancelled ".canceled ++ prompt.prompt end match + // lines += "" - lines += "" + lines.result().map(_.render) - lines.result() end renderState end InteractiveTextInput diff --git a/modules/core/src/main/scala/TextFormatting.scala b/modules/core/src/main/scala/TextFormatting.scala index 62f0fc6..041854f 100644 --- a/modules/core/src/main/scala/TextFormatting.scala +++ b/modules/core/src/main/scala/TextFormatting.scala @@ -18,50 +18,50 @@ package cue4s object TextFormatting: - def rgb(r: Int, g: Int, b: Int): String => String = - s => fansi.Color.True(r, g, b)(s).toString + def rgb(r: Int, g: Int, b: Int): String => fansi.Str = + s => fansi.Color.True(r, g, b)(s) extension (t: String) - def bold = fansi.Bold.On(t).toString - def underline = fansi.Underlined.On(t).toString + def bold = fansi.Bold.On(t) + def underline = fansi.Underlined.On(t) - def rgb(r: Int, g: Int, b: Int) = fansi.Color.True(r, g, b)(t).toString + def rgb(r: Int, g: Int, b: Int) = fansi.Color.True(r, g, b)(t) - def reset = fansi.Color.Reset(t).toString - def black = fansi.Color.Black(t).toString - def red = fansi.Color.Red(t).toString - def green = fansi.Color.Green(t).toString - def yellow = fansi.Color.Yellow(t).toString - def blue = fansi.Color.Blue(t).toString - def magenta = fansi.Color.Magenta(t).toString - def cyan = fansi.Color.Cyan(t).toString - def lightGray = fansi.Color.LightGray(t).toString - def darkGray = fansi.Color.DarkGray(t).toString - def lightRed = fansi.Color.LightRed(t).toString - def lightGreen = fansi.Color.LightGreen(t).toString - def lightYellow = fansi.Color.LightYellow(t).toString - def lightBlue = fansi.Color.LightBlue(t).toString - def lightMagenta = fansi.Color.LightMagenta(t).toString - def lightCyan = fansi.Color.LightCyan(t).toString - def white = fansi.Color.White(t).toString + def reset = fansi.Color.Reset(t) + def black = fansi.Color.Black(t) + def red = fansi.Color.Red(t) + def green = fansi.Color.Green(t) + def yellow = fansi.Color.Yellow(t) + def blue = fansi.Color.Blue(t) + def magenta = fansi.Color.Magenta(t) + def cyan = fansi.Color.Cyan(t) + def lightGray = fansi.Color.LightGray(t) + def darkGray = fansi.Color.DarkGray(t) + def lightRed = fansi.Color.LightRed(t) + def lightGreen = fansi.Color.LightGreen(t) + def lightYellow = fansi.Color.LightYellow(t) + def lightBlue = fansi.Color.LightBlue(t) + def lightMagenta = fansi.Color.LightMagenta(t) + def lightCyan = fansi.Color.LightCyan(t) + def white = fansi.Color.White(t) - def bgReset = fansi.Back.Reset(t).toString - def bgBlack = fansi.Back.Black(t).toString - def bgRed = fansi.Back.Red(t).toString - def bgGreen = fansi.Back.Green(t).toString - def bgYellow = fansi.Back.Yellow(t).toString - def bgBlue = fansi.Back.Blue(t).toString - def bgMagenta = fansi.Back.Magenta(t).toString - def bgCyan = fansi.Back.Cyan(t).toString - def bgLightGray = fansi.Back.LightGray(t).toString - def bgDarkGray = fansi.Back.DarkGray(t).toString - def bgLightRed = fansi.Back.LightRed(t).toString - def bgLightGreen = fansi.Back.LightGreen(t).toString - def bgLightYellow = fansi.Back.LightYellow(t).toString - def bgLightBlue = fansi.Back.LightBlue(t).toString - def bgLightMagenta = fansi.Back.LightMagenta(t).toString - def bgLightCyan = fansi.Back.LightCyan(t).toString - def bgWhite = fansi.Back.White(t).toString + def bgReset = fansi.Back.Reset(t) + def bgBlack = fansi.Back.Black(t) + def bgRed = fansi.Back.Red(t) + def bgGreen = fansi.Back.Green(t) + def bgYellow = fansi.Back.Yellow(t) + def bgBlue = fansi.Back.Blue(t) + def bgMagenta = fansi.Back.Magenta(t) + def bgCyan = fansi.Back.Cyan(t) + def bgLightGray = fansi.Back.LightGray(t) + def bgDarkGray = fansi.Back.DarkGray(t) + def bgLightRed = fansi.Back.LightRed(t) + def bgLightGreen = fansi.Back.LightGreen(t) + def bgLightYellow = fansi.Back.LightYellow(t) + def bgLightBlue = fansi.Back.LightBlue(t) + def bgLightMagenta = fansi.Back.LightMagenta(t) + def bgLightCyan = fansi.Back.LightCyan(t) + def bgWhite = fansi.Back.White(t) end extension end TextFormatting diff --git a/modules/core/src/main/scala/Theme.scala b/modules/core/src/main/scala/Theme.scala index c61b7d0..ae4786e 100644 --- a/modules/core/src/main/scala/Theme.scala +++ b/modules/core/src/main/scala/Theme.scala @@ -18,19 +18,19 @@ package cue4s trait Theme: extension (s: String) - def prompt: String - def emphasis: String - def input: String - def option: String - def optionMany: String - def noMatches: String - def nothingSelected: String - def focused: String - def selectedMany: String - def selectedManyInactive: String - def canceled: String - def error: String - def hint: String + def prompt: fansi.Str + def emphasis: fansi.Str + def input: fansi.Str + def option: fansi.Str + def optionMany: fansi.Str + def noMatches: fansi.Str + def nothingSelected: fansi.Str + def focused: fansi.Str + def selectedMany: fansi.Str + def selectedManyInactive: fansi.Str + def canceled: fansi.Str + def error: fansi.Str + def hint: fansi.Str end extension end Theme @@ -39,37 +39,37 @@ object Theme: object Default extends Theme: extension (s: String) - def prompt: String = s.cyan - def emphasis: String = s.bold - def input: String = s - def option: String = s.bold - def optionMany: String = s - def noMatches: String = s.bold - def nothingSelected: String = s.underline - def focused: String = s.green - def selectedMany: String = s.underline.green - def selectedManyInactive: String = s.underline - def canceled: String = s.red - def error: String = s.red - def hint: String = s.darkGray + def prompt: fansi.Str = s.cyan + def emphasis: fansi.Str = s.bold + def input: fansi.Str = s + def option: fansi.Str = s.bold + def optionMany: fansi.Str = s + def noMatches: fansi.Str = s.bold + def nothingSelected: fansi.Str = s.underline + def focused: fansi.Str = s.green + def selectedMany: fansi.Str = fansi.Color.Green(s.underline) + def selectedManyInactive: fansi.Str = s.underline + def canceled: fansi.Str = s.red + def error: fansi.Str = s.red + def hint: fansi.Str = s.darkGray end extension end Default object NoColors extends Theme: extension (s: String) - def prompt: String = s - def emphasis: String = s - def input: String = s - def option: String = s - def optionMany: String = s - def noMatches: String = s - def nothingSelected: String = s - def focused: String = s - def selectedMany: String = s - def selectedManyInactive: String = s - def canceled: String = s - def error: String = s - def hint: String = s + def prompt: fansi.Str = s + def emphasis: fansi.Str = s + def input: fansi.Str = s + def option: fansi.Str = s + def optionMany: fansi.Str = s + def noMatches: fansi.Str = s + def nothingSelected: fansi.Str = s + def focused: fansi.Str = s + def selectedMany: fansi.Str = s + def selectedManyInactive: fansi.Str = s + def canceled: fansi.Str = s + def error: fansi.Str = s + def hint: fansi.Str = s end extension end NoColors end Theme From ce1bd145df64b44578c3cf4ac0ac221bfd2b1e28 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:04:55 +0100 Subject: [PATCH 06/12] Update snapshots plugin and stop native image from saying stuff --- build.sbt | 3 +++ project/plugins.sbt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3a1fab7..3a9969a 100644 --- a/build.sbt +++ b/build.sbt @@ -70,7 +70,9 @@ lazy val core = projectMatrix .settings( snapshotsPackageName := "cue4s", snapshotsIntegrations += SnapshotIntegration.MUnit, + snapshotsForceOverwrite := !sys.env.contains("CI"), scalacOptions += "-Wunused:all", + scalacOptions += "-snippet-compiler:compile", scalaJSUseMainModuleInitializer := true, scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)), libraryDependencies += "com.lihaoyi" %%% "fansi" % Versions.fansi, @@ -117,6 +119,7 @@ lazy val example = projectMatrix nativeImageJvm := "graalvm-java23", nativeImageVersion := "23.0.0", nativeImageOptions ++= Seq("--install-exit-handlers", "--no-fallback"), + nativeImageReady := {() => sLog.value.info("Native image ready")} ), ) .jsPlatform( diff --git a/project/plugins.sbt b/project/plugins.sbt index e24572b..030f271 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -21,7 +21,7 @@ addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.11") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") -addSbtPlugin("com.indoorvivants.snapshots" % "sbt-snapshots" % "0.0.11") +addSbtPlugin("com.indoorvivants.snapshots" % "sbt-snapshots" % "0.0.12") addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.4") From 054fed424e6241e6d2230fb664c0fdc443379290 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:05:52 +0100 Subject: [PATCH 07/12] Helpers for terminal resizing types --- modules/core/src/main/scala/TerminalEvent.scala | 5 +++++ .../scalanative/TerminalResizingHandler.scala | 15 +++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/core/src/main/scala/TerminalEvent.scala b/modules/core/src/main/scala/TerminalEvent.scala index 9b2779a..3f3012b 100644 --- a/modules/core/src/main/scala/TerminalEvent.scala +++ b/modules/core/src/main/scala/TerminalEvent.scala @@ -17,7 +17,12 @@ package cue4s opaque type TerminalRows <: Int = Int +object TerminalRows: + inline def apply(value: Int): TerminalRows = value.asInstanceOf[TerminalRows] + opaque type TerminalCols <: Int = Int +object TerminalCols: + inline def apply(value: Int): TerminalCols = value.asInstanceOf[TerminalCols] enum TerminalEvent: case Init diff --git a/modules/core/src/main/scalanative/TerminalResizingHandler.scala b/modules/core/src/main/scalanative/TerminalResizingHandler.scala index eaf48d7..cc66d92 100644 --- a/modules/core/src/main/scalanative/TerminalResizingHandler.scala +++ b/modules/core/src/main/scalanative/TerminalResizingHandler.scala @@ -23,12 +23,6 @@ import scala.scalanative.unsigned.UShort import types.* -@extern -private[cue4s] def scalanative_sigwinch(): Int = extern - -@extern -private[cue4s] def scalanative_get_window_size(ws: Ptr[WinSize]): Int = extern - private[cue4s] object types: opaque type WinSize = CStruct2[UShort, UShort] object WinSize: @@ -41,6 +35,15 @@ private[cue4s] object types: end WinSize end types +import types.* + +@extern +private[cue4s] def scalanative_sigwinch(): Int = extern + +@extern +private[cue4s] def scalanative_get_window_size(ws: Ptr[WinSize]): Int = + extern + object TerminalResizingHandler: // We are using this var here because we can't capture @send variable from dynamic scope inside the C function pointer. // All the terms used in the signal handler must come from static scopes (in this case it has a stable path, TerminalResizingHandler.sender) From bd4ede434d6a77ff6d9ca38a83cb3d29adb35321 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:06:56 +0100 Subject: [PATCH 08/12] Missing snapshots for revalidation and terminal size --- ...alternatives_multiple_derived_revalidation | 69 +++++++++++++++++++ .../src/snapshots/core/input_terminal_size | 47 +++++++++++++ ...alternatives_multiple_derived_revalidation | 69 +++++++++++++++++++ .../src/snapshots/coreJS/input_terminal_size | 47 +++++++++++++ ...alternatives_multiple_derived_revalidation | 69 +++++++++++++++++++ .../snapshots/coreNative/input_terminal_size | 47 +++++++++++++ 6 files changed, 348 insertions(+) create mode 100644 modules/core/src/snapshots/core/alternatives_multiple_derived_revalidation create mode 100644 modules/core/src/snapshots/core/input_terminal_size create mode 100644 modules/core/src/snapshots/coreJS/alternatives_multiple_derived_revalidation create mode 100644 modules/core/src/snapshots/coreJS/input_terminal_size create mode 100644 modules/core/src/snapshots/coreNative/alternatives_multiple_derived_revalidation create mode 100644 modules/core/src/snapshots/coreNative/input_terminal_size diff --git a/modules/core/src/snapshots/core/alternatives_multiple_derived_revalidation b/modules/core/src/snapshots/core/alternatives_multiple_derived_revalidation new file mode 100644 index 0000000..6cc2d27 --- /dev/null +++ b/modules/core/src/snapshots/core/alternatives_multiple_derived_revalidation @@ -0,0 +1,69 @@ +Event.Init +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◉ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◉ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃✔ Pick at least one ┃ +┃ ◉ alpha ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/core/input_terminal_size b/modules/core/src/snapshots/core/input_terminal_size new file mode 100644 index 0000000..c5ba972 --- /dev/null +++ b/modules/core/src/snapshots/core/input_terminal_size @@ -0,0 +1,47 @@ +Event.Init +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Resized(20, 100) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › ┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('g') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › g┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('o') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › go┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › go┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('o') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › goo┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('d') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › good┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃✔ how do you do fellow kids? … good┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/alternatives_multiple_derived_revalidation b/modules/core/src/snapshots/coreJS/alternatives_multiple_derived_revalidation new file mode 100644 index 0000000..6cc2d27 --- /dev/null +++ b/modules/core/src/snapshots/coreJS/alternatives_multiple_derived_revalidation @@ -0,0 +1,69 @@ +Event.Init +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◉ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◉ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃✔ Pick at least one ┃ +┃ ◉ alpha ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreJS/input_terminal_size b/modules/core/src/snapshots/coreJS/input_terminal_size new file mode 100644 index 0000000..c5ba972 --- /dev/null +++ b/modules/core/src/snapshots/coreJS/input_terminal_size @@ -0,0 +1,47 @@ +Event.Init +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Resized(20, 100) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › ┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('g') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › g┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('o') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › go┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › go┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('o') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › goo┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('d') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › good┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃✔ how do you do fellow kids? … good┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/alternatives_multiple_derived_revalidation b/modules/core/src/snapshots/coreNative/alternatives_multiple_derived_revalidation new file mode 100644 index 0000000..6cc2d27 --- /dev/null +++ b/modules/core/src/snapshots/coreNative/alternatives_multiple_derived_revalidation @@ -0,0 +1,69 @@ +Event.Init +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◉ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃Please select at least one. ┃ +┃ ◯ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(TAB) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? Pick at least one › ┃ +┃Tab to toggle, Shift+Tab to toggle all, Enter to submit.┃ +┃ ◉ alpha ┃ +┃ ◯ beta ┃ +┃ ◯ gamma ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃✔ Pick at least one ┃ +┃ ◉ alpha ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/modules/core/src/snapshots/coreNative/input_terminal_size b/modules/core/src/snapshots/coreNative/input_terminal_size new file mode 100644 index 0000000..c5ba972 --- /dev/null +++ b/modules/core/src/snapshots/coreNative/input_terminal_size @@ -0,0 +1,47 @@ +Event.Init +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Resized(20, 100) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › ┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('g') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › g┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('o') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › go┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › go┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('o') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › goo┃ +┃too short! ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Char('d') +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃? how do you do fellow kids? › good┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +Event.Key(ENTER) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃✔ how do you do fellow kids? … good┃ +┃ ┃ +┃ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ From af5d6e78f52ba8602b63f9631ff18fb39cad5f25 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:10:59 +0100 Subject: [PATCH 09/12] Scala.js 1.21 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 030f271..f390af8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -15,7 +15,7 @@ addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.8.1") // Scala.js and Scala Native -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.21.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.11") From 169783cbf1c4fc304b660fff71e6855df144ecb8 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Tue, 21 Apr 2026 12:15:35 +0100 Subject: [PATCH 10/12] formatting --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3a9969a..e319e95 100644 --- a/build.sbt +++ b/build.sbt @@ -119,7 +119,7 @@ lazy val example = projectMatrix nativeImageJvm := "graalvm-java23", nativeImageVersion := "23.0.0", nativeImageOptions ++= Seq("--install-exit-handlers", "--no-fallback"), - nativeImageReady := {() => sLog.value.info("Native image ready")} + nativeImageReady := { () => sLog.value.info("Native image ready") }, ), ) .jsPlatform( From 81d8e9a20de98b877937ea0fe2ae55b953890866 Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Thu, 23 Apr 2026 09:59:39 +0100 Subject: [PATCH 11/12] continue suffering --- .../core/src/main/scala/PromptFramework.scala | 57 +++++++++++----- modules/core/src/main/scala/Transition.scala | 56 ++++++++++++---- .../core/src/test/scala/TerminalTests.scala | 2 +- .../src/main/scala-jvm-native/sync.scala | 66 ++++++++++--------- 4 files changed, 117 insertions(+), 64 deletions(-) diff --git a/modules/core/src/main/scala/PromptFramework.scala b/modules/core/src/main/scala/PromptFramework.scala index 6a5d599..6991dae 100644 --- a/modules/core/src/main/scala/PromptFramework.scala +++ b/modules/core/src/main/scala/PromptFramework.scala @@ -293,9 +293,10 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) out.logLn(s"Handling event $ev") ev match case TerminalEvent.Resized(rows, cols) => - this.synchronized: - terminalSize.next(Some(cols)) - printPrompt() + stateTransition(terminalSize = _ => Some(cols)) + if rendering.changed || rendering.last.isEmpty then + printPrompt() + rendering.forgetChanges() case _ => manage(handleEvent(ev)) @@ -346,7 +347,9 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) out.logLn(s"Submitted value: $value, ${status.current}") Next.Done(value) - if rendering.changed then printPrompt() + if rendering.changed || rendering.last.isEmpty then + printPrompt() + rendering.forgetChanges() next end manage @@ -354,12 +357,15 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) private def stateTransition( state: PromptState => PromptState = identity, status: Status => Status = identity, + terminalSize: Option[TerminalCols] => Option[TerminalCols] = identity, ) = this.synchronized: val stateChanged = this.state.nextFn(state) val statusChanged = this.status.nextFn(status) + val sizeChanged = this.terminalSize.nextFn(terminalSize) import Transition.Changed - if stateChanged == Changed.Yes || statusChanged == Changed.Yes then + if stateChanged == Changed.Yes || statusChanged == Changed.Yes || sizeChanged == Changed.Yes + then this.rendering.next( renderState(this.state.current, this.status.current), ) @@ -368,26 +374,43 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) private def printPrompt() = terminal.cursorHide() rendering.last match - case Some(value) if value.length > rendering.current.length => - out.logLn("Clearing because previous rendering was longer") - terminal.withRestore: - clear(value, terminalSize.current) case None => // We don't use cursor save/restore here because after outputing empty lines, // the terminal might scroll and the saved cursor position would be incorrect out.logLn("Freeing up empty space for initial rendering") - rendering.current.foreach(_ => out.outLn("")) + rendering.current.foreach: _ => + terminal.eraseToEndOfLine() + out.outLn("") terminal.moveUp(rendering.current.length).moveHorizontalTo(0) - rendering.nextFn(identity) - case _ => - out.logLn("Clearing just for current rendering") - terminal.withRestore: - clear(rendering.current, terminalSize.current) + case Some(last) => + if last.length == rendering.current.length then + out.logLn( + "Clearing just for current rendering (same length as before)", + ) + terminal.withRestore: + clear(last, terminalSize.current) + else if last.length < rendering.current.length then + out.logLn( + s"Current rendering is longer than previous one, clearing ${rendering.current.length} rows", + ) + rendering.current.foreach: _ => + terminal.eraseToEndOfLine() + out.outLn("") + terminal.moveUp(rendering.current.length).moveHorizontalTo(0) + else + out.logLn( + s"Clearing because previous rendering was longer ${last.length} vs ${rendering.current.length}", + ) + terminal.withRestore: + clear(last, terminalSize.current) end match out.logLn( - s"Rendering ${rendering.current} with ${terminalSize.current}", + s"Rendering with terminal size: ${terminalSize.current}:", ) + rendering.current.zipWithIndex.foreach { (line, index) => + out.logLn(s" ${(index + 1).toString.padTo(4, ' ')}|$line") + } currentStatus() match case _: Status.Finished | Status.Canceled => rendering.current.foreach(l => out.outLn(l)) @@ -441,7 +464,7 @@ trait PromptFramework[Result](terminal: Terminal, out: Output) out, ) - private var terminalSize = Transition.base[Option[TerminalCols]]( + protected val terminalSize = Transition.base[Option[TerminalCols]]( None, ) diff --git a/modules/core/src/main/scala/Transition.scala b/modules/core/src/main/scala/Transition.scala index f5699f5..e4820fb 100644 --- a/modules/core/src/main/scala/Transition.scala +++ b/modules/core/src/main/scala/Transition.scala @@ -23,9 +23,9 @@ private[cue4s] trait Transition[R]: def last: Option[R] def next(r: R): Changed def nextFn(r: R => R): Changed - - def changed: Boolean = - !last.contains(current) + def changed: Boolean = !last.contains(current) + def forgetChanges(): Unit = + next(current) private[cue4s] object Transition: enum Changed: @@ -34,32 +34,49 @@ private[cue4s] object Transition: class RefTransition[R](init: R) extends Transition[R]: private var _current: R = init private var _last: Option[R] = None + // private var _changed = false override def current: R = if _current == null then _current = init - _current.nn - else _current.nn + _current + else _current + + // override def changed: Boolean = _changed override def last: Option[R] = _last override def next(r: R): Changed = - if r == current then Changed.No + if r == current then + this.synchronized: + if _last.isEmpty then _last = Some(current) + _current = r + // _changed = true + + // _changed = false + Changed.No else this.synchronized: _last = Some(current) _current = r - + // _changed = true Changed.Yes override def nextFn(r: R => R): Changed = val newRes = r(current) - if newRes == current then Changed.No + if newRes == current then + this.synchronized: + if _last.isEmpty then _last = Some(current) + // _changed = false + Changed.No else this.synchronized: _last = Some(current) _current = newRes + // _changed = true Changed.Yes + end if + end nextFn end RefTransition @@ -68,6 +85,7 @@ private[cue4s] object Transition: override def last: Option[R] = t.last override def next(r: R): Changed = t.next(r) override def nextFn(r: R => R): Changed = t.nextFn(r) + override def changed: Boolean = t.changed def base[R](current: R): Transition[R] = new RefTransition(current) @@ -103,24 +121,34 @@ private[cue4s] class LoggedTransition[R] private ( out: Output, ) extends Transition[R]: export t.{current, last} + override def changed: Boolean = t.changed override def next(r: R): Changed = - // out.logLn(s"Transition [$label] (set): $current changed to $r") - out.logLn(s"Transition [$label] (set):") - out.logLn(s" current: $current") - out.logLn(s" new: $r") - t.next(r) + val transition = t.next(r) + if transition == Changed.Yes then + out.logLn(s"Transition [$label] (set):") + out.logLn(s" current: $current") + out.logLn(s" new: $r") + else out.logLn(s"Transition [$label] (set): ") + + transition + end next override def nextFn(r: R => R): Changed = val cur = current val transition = t.nextFn(r) - if cur != t.current then + if transition == Changed.Yes then out.logLn(s"Transition [$label] (mapping):") out.logLn(s" current: $cur") out.logLn(s" new: $current") + else out.logLn(s"Transition [$label] (mapping): ") transition end nextFn + override def forgetChanges(): Unit = + out.logLn(s"Forgetting changes [$label]") + t.forgetChanges() + end LoggedTransition private[cue4s] object LoggedTransition: diff --git a/modules/core/src/test/scala/TerminalTests.scala b/modules/core/src/test/scala/TerminalTests.scala index b1e3536..310819f 100644 --- a/modules/core/src/test/scala/TerminalTests.scala +++ b/modules/core/src/test/scala/TerminalTests.scala @@ -115,7 +115,7 @@ trait TerminalTests extends MunitSnapshotsIntegration: val sb = new java.lang.StringBuilder val logger: String => Unit = if log then s => sb.append(s) else _ => () - val term = TracingTerminal(Output.Delegate(_ => (), _ => ())) + val term = TracingTerminal(Output.Delegate(_ => (), logger)) val capturing = Output.Delegate(term.writer, logger) val handler = diff --git a/modules/example/src/main/scala-jvm-native/sync.scala b/modules/example/src/main/scala-jvm-native/sync.scala index 6b2477d..d0b4e3f 100644 --- a/modules/example/src/main/scala-jvm-native/sync.scala +++ b/modules/example/src/main/scala-jvm-native/sync.scala @@ -20,42 +20,44 @@ import cue4s.* import cue4s.Prompt.PasswordInput.Password @main def sync(which: String*) = - Prompts.sync.use: prompts => - val examples = Map[String, (String, SyncPrompts => Unit)]( - "confirm" -> ("Yes/No confirmation", likeCats), - "single-choice" -> ("Single choice", day), - "multi-choice" -> ("Multiple choice", letters), - "validated-int" -> ("Validated integer", seasons), - "password" -> ("Password", password), - "validated-multi" -> ("Validated multiple choice", validatedMultiChoice), - ) + Prompts.sync + .withOutput(FileLogging.fromEnv(Output.Std)) + .use: prompts => + val examples = Map[String, (String, SyncPrompts => Unit)]( + "confirm" -> ("Yes/No confirmation", likeCats), + "single-choice" -> ("Single choice", day), + "multi-choice" -> ("Multiple choice", letters), + "validated-int" -> ("Validated integer", seasons), + "password" -> ("Password", password), + "validated-multi" -> ("Validated multiple choice", validatedMultiChoice), + ) - if which.isEmpty then - val maxLength = examples.keys.map(_.length).max + if which.isEmpty then + val maxLength = examples.keys.map(_.length).max - val selected = prompts - .multiChoiceNoneSelected( - "Which examples would you like to run?", - examples - .map { case (k, (desc, _)) => - s"${k.padTo(maxLength, ' ')}: $desc" - } - .toList - .sorted, - ) - .getOrThrow + val selected = prompts + .multiChoiceNoneSelected( + "Which examples would you like to run?", + examples + .map { case (k, (desc, _)) => + s"${k.padTo(maxLength, ' ')}: $desc" + } + .toList + .sorted, + ) + .getOrThrow - val toShow = selected.map(_.split(": ").head.trim) - toShow.foreach { k => examples(k)._2(prompts) } - else - val allowed = examples.keySet - val missing = which.toSet -- allowed + val toShow = selected.map(_.split(": ").head.trim) + toShow.foreach { k => examples(k)._2(prompts) } + else + val allowed = examples.keySet + val missing = which.toSet -- allowed - if missing.nonEmpty then - sys.error(s"Unknown example names: ${missing - .mkString(", ")}. Available examples: ${allowed.mkString(", ")}") - else which.foreach { k => examples(k)._2(prompts) } - end if + if missing.nonEmpty then + sys.error(s"Unknown example names: ${missing + .mkString(", ")}. Available examples: ${allowed.mkString(", ")}") + else which.foreach { k => examples(k)._2(prompts) } + end if end sync From e62249bf4c0b7b37b942146fce925dfc71ec3fdf Mon Sep 17 00:00:00 2001 From: Anton Sviridov Date: Thu, 23 Apr 2026 10:11:44 +0100 Subject: [PATCH 12/12] Clean up outputs --- modules/cats-effect/src/main/scala/PromptsIO.scala | 4 ++-- modules/core/src/main/scala/Output.scala | 9 +++------ modules/core/src/main/scala/Prompt.scala | 12 ++++-------- modules/core/src/main/scala/Prompts.scala | 4 ++-- modules/example/src/main/scala-jvm-native/sync.scala | 2 +- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/modules/cats-effect/src/main/scala/PromptsIO.scala b/modules/cats-effect/src/main/scala/PromptsIO.scala index fd2c6dd..f35f74b 100644 --- a/modules/cats-effect/src/main/scala/PromptsIO.scala +++ b/modules/cats-effect/src/main/scala/PromptsIO.scala @@ -131,7 +131,7 @@ object PromptsIO: "0.0.5", ) def apply( - out: Output = Output.Std, + out: Output = Output.StdNoLogging, createTerminal: Output => Terminal = Terminal.ansi, theme: Theme = Theme.Default, symbols: Symbols = Symbols.platformDefault, @@ -145,7 +145,7 @@ object PromptsIO: end PromptsIO class PromptsIOBuilder private (impl: IOPromptsOptions): - def this() = this(IOPromptsOptions(Output.Std, Theme.Default)) + def this() = this(IOPromptsOptions(Output.StdNoLogging, Theme.Default)) def noColors: PromptsIOBuilder = withTheme(Theme.NoColors) def withOutput(out: Output): PromptsIOBuilder = copy(_.copy(out = out)) diff --git a/modules/core/src/main/scala/Output.scala b/modules/core/src/main/scala/Output.scala index ea23f67..2a7a036 100644 --- a/modules/core/src/main/scala/Output.scala +++ b/modules/core/src/main/scala/Output.scala @@ -60,15 +60,12 @@ object Output: * Then logging will be written to a file specified by `CUE4S_LOG_FILE` env * variable. If the env variable is absent, logger will not output anything */ - object StdNoLogging extends PlatformStd: + object StdNoLogging extends Output: override def logLn[A: AsString](a: => A): Unit = () + override def out[A: AsString](a: A): Unit = Std.out(a) + override def close(): Unit = Std.close() end StdNoLogging - object StdOut extends Output: - override def logLn[A: AsString](a: => A): Unit = - System.out.println(a.render) - override def out[A: AsString](a: A): Unit = System.out.print(a.render) - object DarkVoid extends Output: override def logLn[A: AsString](a: => A): Unit = () override def out[A: AsString](a: A): Unit = () diff --git a/modules/core/src/main/scala/Prompt.scala b/modules/core/src/main/scala/Prompt.scala index 394ea49..aba1b71 100644 --- a/modules/core/src/main/scala/Prompt.scala +++ b/modules/core/src/main/scala/Prompt.scala @@ -44,7 +44,7 @@ end Prompt object Prompt: case class Input private ( private val lab: String, - private val validate: Option[String => Option[PromptError]] = None, + private val validate: Option[String => Option[PromptError]], private val default: Option[String] = None, ) extends Prompt[String]: @@ -70,7 +70,6 @@ object Prompt: terminal = terminal, out = output, theme = theme, - // validate = validate, hideText = false, symbols = symbols, default = default, @@ -92,8 +91,8 @@ object Prompt: case class PasswordInput private ( private val lab: String, - private val validate: Option[Password => Option[PromptError]] = None, - private val default: Option[Password] = None, + private val validate: Option[Password => Option[PromptError]], + private val default: Option[Password], ) extends Prompt[Password]: def this(lab: String) = this(lab, None, None) @@ -149,9 +148,6 @@ object Prompt: def default(value: N): NumberInput[N] = copy(default = Some(value)) - // def validate(f: N => Option[PromptError]): NumberInput[N] = - // copy(validateNumber = (n: N) => validateNumber(n).orElse(f(n))) - def validate(f: N => Option[PromptError]): NumberInput[N] = validateNumber match case Some(value) => @@ -219,7 +215,7 @@ object Prompt: case class Confirmation private ( private val lab: String, - private val default: Boolean = true, + private val default: Boolean, ) extends Prompt[Boolean]: def default(value: Boolean) = copy(default = value) diff --git a/modules/core/src/main/scala/Prompts.scala b/modules/core/src/main/scala/Prompts.scala index e8a70ad..a3247e0 100644 --- a/modules/core/src/main/scala/Prompts.scala +++ b/modules/core/src/main/scala/Prompts.scala @@ -38,7 +38,7 @@ object Prompts extends PromptsCompanionPlatform: "0.0.5", ) def apply( - out: Output = Output.Std, + out: Output = Output.StdNoLogging, createTerminal: Output => Terminal = Terminal.ansi, theme: Theme = Theme.Default, symbols: Symbols = Symbols.platformDefault, @@ -49,7 +49,7 @@ object Prompts extends PromptsCompanionPlatform: "0.0.5", ) def use[A]( - out: Output = Output.Std, + out: Output = Output.StdNoLogging, createTerminal: Output => Terminal = Terminal.ansi, theme: Theme = Theme.Default, )(f: Prompts => A): A = diff --git a/modules/example/src/main/scala-jvm-native/sync.scala b/modules/example/src/main/scala-jvm-native/sync.scala index d0b4e3f..7a0b9b2 100644 --- a/modules/example/src/main/scala-jvm-native/sync.scala +++ b/modules/example/src/main/scala-jvm-native/sync.scala @@ -21,7 +21,7 @@ import cue4s.Prompt.PasswordInput.Password @main def sync(which: String*) = Prompts.sync - .withOutput(FileLogging.fromEnv(Output.Std)) + .withOutput(FileLogging.fromEnv(Output.StdNoLogging)) .use: prompts => val examples = Map[String, (String, SyncPrompts => Unit)]( "confirm" -> ("Yes/No confirmation", likeCats),