diff --git a/core/jvm/src/main/scala/terminus/JLineTerminal.scala b/core/jvm/src/main/scala/terminus/JLineTerminal.scala index ced35a3..6e65128 100644 --- a/core/jvm/src/main/scala/terminus/JLineTerminal.scala +++ b/core/jvm/src/main/scala/terminus/JLineTerminal.scala @@ -55,14 +55,9 @@ class JLineTerminal(terminal: JTerminal) extends Terminal, TerminalKeyReader { def write(string: String): Unit = writer.write(string) - def raw[A](f: Terminal ?=> A): A = { + private[terminus] def setRawMode(): () => Unit = { val attrs = terminal.enterRawMode() - try { - val result = f(using this) - result - } finally { - terminal.setAttributes(attrs) - } + () => terminal.setAttributes(attrs) } def getDimensions: effect.TerminalDimensions = { @@ -73,23 +68,19 @@ class JLineTerminal(terminal: JTerminal) extends Terminal, TerminalKeyReader { def setDimensions(dimensions: TerminalDimensions): Unit = terminal.setSize(Size(dimensions.columns, dimensions.rows)) - def application[A](f: Terminal ?=> A): A = { - try { - terminal.puts(Capability.keypad_xmit) - val result = f(using this) - result - } finally { - val _ = terminal.puts(Capability.keypad_local) + private[terminus] def setApplicationMode(): () => Unit = { + terminal.puts(Capability.keypad_xmit) + () => { + terminal.puts(Capability.keypad_local) + () } } - def alternateScreen[A](f: Terminal ?=> A): A = { - try { - terminal.puts(Capability.enter_ca_mode) - val result = f(using this) - result - } finally { - val _ = terminal.puts(Capability.exit_ca_mode) + private[terminus] def setAlternateScreenMode(): () => Unit = { + terminal.puts(Capability.enter_ca_mode) + () => { + terminal.puts(Capability.exit_ca_mode) + () } } diff --git a/core/native/src/main/scala/terminus/NativeTerminal.scala b/core/native/src/main/scala/terminus/NativeTerminal.scala index d2914bf..e9976f4 100644 --- a/core/native/src/main/scala/terminus/NativeTerminal.scala +++ b/core/native/src/main/scala/terminus/NativeTerminal.scala @@ -111,26 +111,26 @@ object NativeTerminal } } - def application[A](f: (terminus.Terminal) ?=> A): A = { - withEffect(AnsiCodes.mode.application.on, AnsiCodes.mode.application.off)(f) + private[terminus] def setRawMode(): () => Unit = { + implicit val z: Zone = Zone.open() + val origAttrs = termios.getAttributes() + termios.setRawMode() + + () => { + termios.setAttributes(origAttrs) + z.close() + } } - def alternateScreen[A](f: (terminus.Terminal) ?=> A): A = { - withEffect( + private[terminus] def setApplicationMode(): () => Unit = + effectDeferRollback( + AnsiCodes.mode.application.on, + AnsiCodes.mode.application.off + ) + + private[terminus] def setAlternateScreenMode(): () => Unit = + effectDeferRollback( AnsiCodes.mode.alternateScreen.on, AnsiCodes.mode.alternateScreen.off - )(f) - } - - def raw[A](f: Terminal ?=> A): A = { - Zone { - val origAttrs = termios.getAttributes() - try { - termios.setRawMode() - f(using this) - } finally { - termios.setAttributes(origAttrs) - } - } - } + ) } diff --git a/core/shared/src/main/scala/terminus/AlternateScreenMode.scala b/core/shared/src/main/scala/terminus/AlternateScreenMode.scala index 1ed5e1f..84cdb78 100644 --- a/core/shared/src/main/scala/terminus/AlternateScreenMode.scala +++ b/core/shared/src/main/scala/terminus/AlternateScreenMode.scala @@ -25,5 +25,13 @@ trait AlternateScreenMode { def alternateScreen[F <: effect.Effect, A]( f: F ?=> A ): (F & effect.AlternateScreenMode[F]) ?=> A = - effect ?=> effect.alternateScreen(f) + effect ?=> { + val finalizer = effect.setAlternateScreenMode() + + try { + f(using effect) + } finally { + finalizer() + } + } } diff --git a/core/shared/src/main/scala/terminus/ApplicationMode.scala b/core/shared/src/main/scala/terminus/ApplicationMode.scala index 05cd353..52d8ca8 100644 --- a/core/shared/src/main/scala/terminus/ApplicationMode.scala +++ b/core/shared/src/main/scala/terminus/ApplicationMode.scala @@ -25,5 +25,12 @@ trait ApplicationMode { def application[F <: effect.Effect, A]( f: F ?=> A ): (F & effect.ApplicationMode[F]) ?=> A = - effect ?=> effect.application(f) + effect ?=> + val finalizer = effect.setApplicationMode() + + try { + f(using effect) + } finally { + finalizer() + } } diff --git a/core/shared/src/main/scala/terminus/RawMode.scala b/core/shared/src/main/scala/terminus/RawMode.scala index ae58e52..ec4aad1 100644 --- a/core/shared/src/main/scala/terminus/RawMode.scala +++ b/core/shared/src/main/scala/terminus/RawMode.scala @@ -23,5 +23,13 @@ trait RawMode { * which is the default, user input is only available a line at a time. */ def raw[F <: effect.Effect, A](f: F ?=> A): (F & effect.RawMode[F]) ?=> A = - effect ?=> effect.raw(f) + effect ?=> { + val finalizer = effect.setRawMode() + + try { + f(using effect) + } finally { + finalizer() + } + } } diff --git a/core/shared/src/main/scala/terminus/effect/AlternateScreenMode.scala b/core/shared/src/main/scala/terminus/effect/AlternateScreenMode.scala index cdcf798..8145cfe 100644 --- a/core/shared/src/main/scala/terminus/effect/AlternateScreenMode.scala +++ b/core/shared/src/main/scala/terminus/effect/AlternateScreenMode.scala @@ -21,6 +21,10 @@ trait AlternateScreenMode[+F <: Effect] { self: F => /** Run the given terminal program `f` in alternate screen mode, which means * that whatever is displayed by `f` will not been shown when the program * exits, and similarly key presses will not be saved in the history buffer. + * + * This is a low level method that is slightly dangerous. Care must be taken + * to ensure raw mode is closed and as such, this method is package private + * to avoid exposure. */ - def alternateScreen[A](f: F ?=> A): A + private[terminus] def setAlternateScreenMode(): () => Unit } diff --git a/core/shared/src/main/scala/terminus/effect/ApplicationMode.scala b/core/shared/src/main/scala/terminus/effect/ApplicationMode.scala index a717d89..719193f 100644 --- a/core/shared/src/main/scala/terminus/effect/ApplicationMode.scala +++ b/core/shared/src/main/scala/terminus/effect/ApplicationMode.scala @@ -21,7 +21,10 @@ trait ApplicationMode[+F <: Effect] { self: F => /** Run the given terminal program `f` in application mode, which changes the * input sent to the program when arrow keys are pressed. See * https://invisible-island.net/xterm/xterm.faq.html#xterm_arrows + * + * This is a low level method that is slightly dangerous. Care must be taken + * to ensure raw mode is closed and as such, this method is package private + * to avoid exposure. */ - def application[A](f: F ?=> A): A - + private[terminus] def setApplicationMode(): () => Unit } diff --git a/core/shared/src/main/scala/terminus/effect/RawMode.scala b/core/shared/src/main/scala/terminus/effect/RawMode.scala index b51cbb6..3f6afdc 100644 --- a/core/shared/src/main/scala/terminus/effect/RawMode.scala +++ b/core/shared/src/main/scala/terminus/effect/RawMode.scala @@ -18,9 +18,13 @@ package terminus.effect trait RawMode[+F <: Effect] { self: F => - /** Run the given terminal program `f` in raw mode, which means that the - * program can read user input a character at a time. In canonical mode, - * which is the default, user input is only available a line at a time. + /** Sets the terminal to raw mode and returns thunk that rolls back the + * terminal mode to its original state. This returned thunk should not be + * called more than once. + * + * This is a low level method that is slightly dangerous. Care must be taken + * to ensure raw mode is closed and as such, this method is package private + * to avoid exposure. */ - def raw[A](f: F ?=> A): A + private[terminus] def setRawMode(): () => Unit } diff --git a/core/shared/src/main/scala/terminus/effect/WithEffect.scala b/core/shared/src/main/scala/terminus/effect/WithEffect.scala index 39dd666..161b5de 100644 --- a/core/shared/src/main/scala/terminus/effect/WithEffect.scala +++ b/core/shared/src/main/scala/terminus/effect/WithEffect.scala @@ -30,4 +30,9 @@ trait WithEffect[+F <: Writer] { self: F => write(on) f(using this) } + + protected def effectDeferRollback(on: String, off: String): () => Unit = { + write(on) + () => write(off) + } }