diff --git a/build.sbt b/build.sbt index e319e95..ba36e47 100644 --- a/build.sbt +++ b/build.sbt @@ -192,6 +192,7 @@ lazy val e2e_fixture = 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/modules/core/src/main/scala/CharCollector.scala b/modules/core/src/main/scala/CharCollector.scala index 0a51024..1be4123 100644 --- a/modules/core/src/main/scala/CharCollector.scala +++ b/modules/core/src/main/scala/CharCollector.scala @@ -68,7 +68,7 @@ private[cue4s] object CharCollector: emit(TerminalEvent.Key(KeyEvent.TAB)) case 8 | 127 => emit(TerminalEvent.Key(KeyEvent.DELETE)) - case 224 if Platform.os == Platform.OS.Windows => // 0xE0 + case 0xe0 if Platform.os == Platform.OS.Windows => // 0xE0 (State.ScanCode_Started, DecodeResult.Continue) case 3 | 4 if Platform.os == Platform.OS.Windows => // Ctrl+C or Ctrl+D @@ -119,6 +119,8 @@ private[cue4s] object CharCollector: case 75 => toInit(TerminalEvent.Key(KeyEvent.LEFT)) case 28 => toInit(TerminalEvent.Key(KeyEvent.ENTER)) case 83 => toInit(TerminalEvent.Key(KeyEvent.DELETE)) + // This doesn't actually work + // TODO: https://github.com/neandertech/cue4s/issues/61 case 15 => toInit(TerminalEvent.Key(KeyEvent.SHIFT_TAB)) case _ => (State.Init, DecodeResult.Continue) diff --git a/modules/core/src/main/scala/InteractiveMultipleChoice.scala b/modules/core/src/main/scala/InteractiveMultipleChoice.scala index 1c95789..37f4159 100644 --- a/modules/core/src/main/scala/InteractiveMultipleChoice.scala +++ b/modules/core/src/main/scala/InteractiveMultipleChoice.scala @@ -61,7 +61,12 @@ private[cue4s] class InteractiveMultipleChoice( status match 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." + // TODO: https://github.com/neandertech/cue4s/issues/61 + val shiftTab = + if Platform.os == Platform.OS.Windows then fansi.Str("") + else "Shift+Tab".emphasis ++ " to toggle all, " + + lines += "Tab".emphasis ++ " to toggle, " ++ shiftTab ++ "Enter".emphasis ++ " to submit." status match case Status.Running(Left(err)) => diff --git a/modules/core/src/main/scalanative/ChangeModeWindows.scala b/modules/core/src/main/scalanative/ChangeModeWindows.scala index 0b23d9a..631937d 100644 --- a/modules/core/src/main/scalanative/ChangeModeWindows.scala +++ b/modules/core/src/main/scalanative/ChangeModeWindows.scala @@ -17,8 +17,19 @@ package cue4s import scala.scalanative.unsafe.* +import scala.scalanative.unsigned.* object ChangeModeWindows extends ChangeModeNative: + + @extern + private def SetConsoleOutputCP(wCodePageID: UInt): Boolean = extern + @extern + private def GetConsoleOutputCP(): UInt = extern + + // https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers + private val UtfCodePage = 65001.toUInt + private var consoleOutputCP: Option[UInt] = None + def getchar(): Int = val ch = Msvcrt._getch() /* For raw scan codes, _getch() returns either 0 or 0xE0 as prefix. @@ -27,13 +38,22 @@ object ChangeModeWindows extends ChangeModeNative: */ if ch == 0 then 0xe0 else ch - def changeMode(rawMode: Boolean): Boolean = rawMode - override def read(): Int = - val ch = getchar() + def changeMode(rawMode: Boolean): Boolean = + // The code page logic here is required only on native: https://github.com/scala-native/scala-native/issues/4144 + if rawMode then + val currentCP = GetConsoleOutputCP() + if currentCP != UtfCodePage then + consoleOutputCP = Some(currentCP) + SetConsoleOutputCP(UtfCodePage) + else + // Restore code page to what it was before entering prompts + consoleOutputCP.foreach: cp => + SetConsoleOutputCP(cp) - // 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 + rawMode + end changeMode + + override def read(): Int = getchar() object Msvcrt: @extern() @link("msvcrt") diff --git a/modules/example/src/main/scala-jvm-native/UserComponent.scala b/modules/example/src/main/scala-jvm-native/UserComponent.scala index 22ccc89..e2d6190 100644 --- a/modules/example/src/main/scala-jvm-native/UserComponent.scala +++ b/modules/example/src/main/scala-jvm-native/UserComponent.scala @@ -24,9 +24,9 @@ import cue4s.* import cue4s.KeyEvent @main def tictac = - val terminal = Terminal.ansi(Output.Std) + val terminal = Terminal.ansi(Output.StdNoLogging) val input = InputProvider(terminal) - val tic = TicTacToe(terminal, Output.Std) + val tic = TicTacToe(terminal, Output.StdNoLogging) val finish = AtomicBoolean()