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.
This commit is contained in:
Ethan Atkins 2020-07-09 13:02:22 -07:00
parent bc4fe0a31a
commit 25e83d8fec
7 changed files with 25 additions and 14 deletions

View File

@ -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(

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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) }
}

2
main/src/main/scala/sbt/Defaults.scala Executable file → Normal file
View File

@ -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 =>

View File

@ -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' }

View File

@ -759,7 +759,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
}
}
terminal.withRawSystemIn(impl())
terminal.withRawInput(impl())
}
}