From 25e83d8feccd159bdb32e796720efdb50dc49e2e Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 9 Jul 2020 13:02:22 -0700 Subject: [PATCH] Add Terminal.withRawOutput api In the scala console, it's essential that we not process the bytes that are written to the terminal by jline. --- .../scala/sbt/internal/util/LineReader.scala | 4 ++-- .../scala/sbt/internal/util/Terminal.scala | 21 +++++++++++++------ main-actions/src/main/scala/sbt/Console.scala | 4 +++- .../sbt/internal/client/NetworkClient.scala | 2 +- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/Main.scala | 4 ++-- .../main/scala/sbt/internal/Continuous.scala | 2 +- 7 files changed, 25 insertions(+), 14 deletions(-) mode change 100755 => 100644 main/src/main/scala/sbt/Defaults.scala diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index 836a0ba6d..34526a948 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -79,7 +79,7 @@ object LineReader { historyPath.foreach(f => reader.setVariable(JLineReader.HISTORY_FILE, f)) new LineReader { override def readLine(prompt: String, mask: Option[Char]): Option[String] = { - try terminal.withRawSystemIn { + try terminal.withRawInput { Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt))) } catch { case e: EndOfFileException => @@ -240,7 +240,7 @@ private[sbt] object JLine { } @deprecated("Avoid referencing JLine directly.", "1.4.0") - def withJLine[T](action: => T): T = Terminal.get.withRawSystemIn(action) + def withJLine[T](action: => T): T = Terminal.get.withRawInput(action) @deprecated("Use LineReader.simple instead", "1.4.0") def simple( diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala index dacba581c..d98b01b06 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala @@ -127,11 +127,12 @@ trait Terminal extends AutoCloseable { private[sbt] def setSize(width: Int, height: Int): Unit private[sbt] def name: String - private[sbt] def withRawSystemIn[T](f: => T): T = f + private[sbt] def withRawInput[T](f: => T): T = f private[sbt] def withCanonicalIn[T](f: => T): T = f private[sbt] def write(bytes: Int*): Unit private[sbt] def printStream: PrintStream private[sbt] def withPrintStream[T](f: PrintStream => T): T + private[sbt] def withRawOutput[R](f: => R): R private[sbt] def restore(): Unit = {} private[sbt] val progressState = new ProgressState(1) private[this] val promptHolder: AtomicReference[Prompt] = new AtomicReference(Prompt.Running) @@ -330,10 +331,11 @@ object Terminal { override private[sbt] def setAttributes(attributes: Map[String, String]): Unit = t.setAttributes(attributes) override private[sbt] def setSize(width: Int, height: Int): Unit = t.setSize(width, height) - override def withRawSystemIn[T](f: => T): T = t.withRawSystemIn(f) + override def withRawInput[T](f: => T): T = t.withRawInput(f) override def withCanonicalIn[T](f: => T): T = t.withCanonicalIn(f) override def printStream: PrintStream = t.printStream override def withPrintStream[T](f: PrintStream => T): T = t.withPrintStream(f) + override private[sbt] def withRawOutput[R](f: => R): R = t.withRawOutput(f) override def restore(): Unit = t.restore() override def close(): Unit = {} override private[sbt] def write(bytes: Int*): Unit = t.write(bytes: _*) @@ -737,8 +739,8 @@ object Terminal { private[sbt] def createReader(term: Terminal, prompt: Prompt): ConsoleReader = { new ConsoleReader(term.inputStream, term.outputStream, term.toJLine) { override def readLine(prompt: String, mask: Character): String = - term.withRawSystemIn(super.readLine(prompt, mask)) - override def readLine(prompt: String): String = term.withRawSystemIn(super.readLine(prompt)) + term.withRawInput(super.readLine(prompt, mask)) + override def readLine(prompt: String): String = term.withRawInput(super.readLine(prompt)) } } @@ -782,7 +784,7 @@ object Terminal { override private[sbt] def setSize(width: Int, height: Int): Unit = system.setSize(new org.jline.terminal.Size(width, height)) - override def withRawSystemIn[T](f: => T): T = term.synchronized { + override def withRawInput[T](f: => T): T = term.synchronized { val prev = JLine3.enterRawMode(system) try f catch { case _: InterruptedIOException => throw new InterruptedException } finally { @@ -812,6 +814,7 @@ object Terminal { val out: OutputStream, override private[sbt] val name: String ) extends Terminal { + private[this] val rawMode = new AtomicBoolean(false) private[this] val writeLock = new AnyRef private[this] val writeableInputStream = in match { case w: WriteableInputStream => w @@ -852,7 +855,7 @@ object Terminal { override def flush(): Unit = combinedOutputStream.flush() } private def doWrite(bytes: Array[Byte]): Unit = - progressState.write(TerminalImpl.this, bytes, rawPrintStream, hasProgress.get) + progressState.write(TerminalImpl.this, bytes, rawPrintStream, hasProgress.get && !rawMode.get) override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream) override def inputStream: InputStream = writeableInputStream @@ -867,6 +870,11 @@ object Terminal { case _ => (0, 0) } + private[sbt] def withRawOutput[R](f: => R): R = { + rawMode.set(true) + try f + finally rawMode.set(false) + } private[this] val rawPrintStream: PrintStream = new LinePrintStream(combinedOutputStream) override def withPrintStream[T](f: PrintStream => T): T = writeLock.synchronized(f(rawPrintStream)) @@ -904,5 +912,6 @@ object Terminal { new PrintStream(outputStream, false) override private[sbt] def withPrintStream[T](f: java.io.PrintStream => T): T = f(printStream) override private[sbt] def write(bytes: Int*): Unit = {} + override private[sbt] def withRawOutput[R](f: => R): R = f } } diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index 76332f3e0..8e9d40984 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -66,7 +66,9 @@ final class Console(compiler: AnalyzingCompiler) { val previous = sys.props.get("scala.color").getOrElse("auto") try { sys.props("scala.color") = if (terminal.isColorEnabled) "true" else "false" - terminal.withRawSystemIn(Run.executeTrapExit(console0, log)) + terminal.withRawOutput { + terminal.withRawInput(Run.executeTrapExit(console0, log)) + } } finally { sys.props("scala.color") = previous } diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index a9483247e..5a642c22b 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -885,7 +885,7 @@ class NetworkClient( if (!stopped.get()) read() } } - try Terminal.console.withRawSystemIn(read()) + try Terminal.console.withRawInput(read()) catch { case NonFatal(_) => stopped.set(true) } } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala old mode 100755 new mode 100644 index edec7feb3..19dacc16f --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1503,7 +1503,7 @@ object Defaults extends BuildCommon { Some(s => { def print(st: String) = { scala.Console.out.print(st); scala.Console.out.flush() } print(s) - Terminal.get.withRawSystemIn { + Terminal.get.withRawInput { try Terminal.get.inputStream.read match { case -1 | -2 => None case b => diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 47764e91a..c9862bedc 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -114,7 +114,7 @@ private[sbt] object xMain { case _: ServerAlreadyBootingException if System.console != null && !Terminal.startedByRemoteClient => println("sbt server is already booting. Create a new server? y/n (default y)") - val exit = Terminal.get.withRawSystemIn(System.in.read) match { + val exit = Terminal.get.withRawInput(System.in.read) match { case 110 => Some(Exit(1)) case _ => None } @@ -835,7 +835,7 @@ object BuiltinCommands { @tailrec private[this] def doLoadFailed(s: State, loadArg: String): State = { s.log.warn("Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? (default: r)") - val result = try Terminal.get.withRawSystemIn(System.in.read) match { + val result = try Terminal.get.withRawInput(System.in.read) match { case -1 => 'q'.toInt case b => b } catch { case _: ClosedChannelException => 'q' } diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index e52a041a8..3cbc9ed1b 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -759,7 +759,7 @@ private[sbt] object Continuous extends DeprecatedContinuous { } } - terminal.withRawSystemIn(impl()) + terminal.withRawInput(impl()) } }