Merge pull request #5695 from eatkins/client-system-err

Support System.err in thin client
This commit is contained in:
Ethan Atkins 2020-07-22 14:54:17 -07:00 committed by GitHub
commit 88e0bd678a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 5 deletions

View File

@ -60,6 +60,12 @@ trait Terminal extends AutoCloseable {
*/
def outputStream: OutputStream
/**
* Gets the error stream for this Terminal.
* @return the error stream.
*/
def errorStream: OutputStream
/**
* Returns true if the terminal supports ansi characters.
*
@ -221,7 +227,7 @@ object Terminal {
if (System.console == null) {
originalOut.close()
originalIn.close()
System.err.close()
originalErr.close()
}
}
@ -316,6 +322,7 @@ object Terminal {
override def lineCount(line: String): Int = t.lineCount(line)
override def inputStream: InputStream = t.inputStream
override def outputStream: OutputStream = t.outputStream
override def errorStream: OutputStream = t.errorStream
override def isAnsiSupported: Boolean = t.isAnsiSupported
override def isColorEnabled: Boolean = t.isColorEnabled
override def isEchoEnabled: Boolean = t.isEchoEnabled
@ -359,14 +366,17 @@ object Terminal {
private[sbt] def withOut[T](out: PrintStream)(f: => T): T = {
val originalOut = System.out
val originalErr = System.err
val originalProxyOut = ConsoleOut.getGlobalProxy
try {
ConsoleOut.setGlobalProxy(ConsoleOut.printStreamOut(out))
System.setOut(out)
scala.Console.withOut(out)(f)
System.setErr(out)
scala.Console.withErr(out)(scala.Console.withOut(out)(f))
} finally {
ConsoleOut.setGlobalProxy(originalProxyOut)
System.setOut(originalOut)
System.setErr(originalErr)
}
}
@ -379,6 +389,7 @@ object Terminal {
}
}
private[this] val originalOut = new LinePrintStream(System.out)
private[this] val originalErr = System.err
private[this] val originalIn = System.in
private[sbt] class WriteableInputStream(in: InputStream, name: String)
extends InputStream
@ -476,9 +487,11 @@ object Terminal {
private[this] def withOut[T](f: => T): T = {
try {
System.setOut(proxyPrintStream)
scala.Console.withOut(proxyOutputStream)(f)
System.setErr(proxyErrorStream)
scala.Console.withErr(proxyErrorStream)(scala.Console.withOut(proxyOutputStream)(f))
} finally {
System.setOut(originalOut)
System.setErr(originalErr)
}
}
private[this] def withIn[T](f: => T): T =
@ -602,6 +615,15 @@ object Terminal {
private[this] val proxyPrintStream = new LinePrintStream(proxyOutputStream) {
override def toString: String = s"proxyPrintStream($proxyOutputStream)"
}
private[this] object proxyErrorOutputStream extends OutputStream {
private[this] def os: OutputStream = activeTerminal.get().errorStream
def write(byte: Int): Unit = os.write(byte)
override def write(bytes: Array[Byte]): Unit = write(bytes, 0, bytes.length)
override def write(bytes: Array[Byte], offset: Int, len: Int): Unit =
os.write(bytes, offset, len)
override def flush(): Unit = os.flush()
}
private[this] val proxyErrorStream = new PrintStream(proxyErrorOutputStream, true)
private[this] lazy val isWindows =
System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
private[this] object WrappedSystemIn extends InputStream {
@ -758,7 +780,7 @@ object Terminal {
val term: jline.Terminal with jline.Terminal2,
in: InputStream,
out: OutputStream
) extends TerminalImpl(in, out, "console0") {
) extends TerminalImpl(in, out, originalErr, "console0") {
private[util] lazy val system = JLine3.system
private[this] def isCI = sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI")
override def getWidth: Int = system.getSize.getColumns
@ -815,6 +837,7 @@ object Terminal {
private[sbt] abstract class TerminalImpl private[sbt] (
val in: InputStream,
val out: OutputStream,
override val errorStream: OutputStream,
override private[sbt] val name: String
) extends Terminal {
private[this] val rawMode = new AtomicBoolean(false)
@ -907,6 +930,7 @@ object Terminal {
override def isSuccessEnabled: Boolean = false
override def isSupershellEnabled: Boolean = false
override def outputStream: java.io.OutputStream = _ => {}
override def errorStream: java.io.OutputStream = _ => {}
override private[sbt] def getAttributes: Map[String, String] = Map.empty
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit = {}
override private[sbt] def setSize(width: Int, height: Int): Unit = {}

View File

@ -41,8 +41,10 @@ import Serialization.{
cancelRequest,
promptChannel,
systemIn,
systemErr,
systemOut,
systemOutFlush,
systemErrFlush,
terminalCapabilities,
terminalCapabilitiesResponse,
terminalPropertiesQuery,
@ -527,9 +529,19 @@ class NetworkClient(
case _ =>
}
Vector.empty
case (`systemErr`, Some(json)) =>
Converter.fromJson[Array[Byte]](json) match {
case Success(bytes) if bytes.nonEmpty && attached.get =>
synchronized(errorStream.write(bytes))
case _ =>
}
Vector.empty
case (`systemOutFlush`, _) =>
synchronized(printStream.flush())
Vector.empty
case (`systemErrFlush`, _) =>
synchronized(errorStream.flush())
Vector.empty
case (`promptChannel`, _) =>
batchMode.set(false)
Vector.empty

View File

@ -709,7 +709,24 @@ final class NetworkChannel(
write(java.util.Arrays.copyOfRange(b, off, off + len))
}
}
private class NetworkTerminal extends TerminalImpl(inputStream, outputStream, name) {
private[this] lazy val errorStream: OutputStream = new OutputStream {
private[this] val buffer = new LinkedBlockingQueue[Byte]
override def write(b: Int): Unit = buffer.synchronized {
buffer.put(b.toByte)
}
override def flush(): Unit = {
val list = new java.util.ArrayList[Byte]
buffer.synchronized(buffer.drainTo(list))
if (!list.isEmpty) jsonRpcNotify(Serialization.systemErr, list.asScala.toSeq)
}
override def write(b: Array[Byte]): Unit = buffer.synchronized {
b.foreach(buffer.put)
}
override def write(b: Array[Byte], off: Int, len: Int): Unit = {
write(java.util.Arrays.copyOfRange(b, off, off + len))
}
}
private class NetworkTerminal extends TerminalImpl(inputStream, outputStream, errorStream, name) {
private[this] val pending = new AtomicBoolean(false)
private[this] val closed = new AtomicBoolean(false)
private[this] val properties = new AtomicReference[TerminalPropertiesResponse]

View File

@ -26,7 +26,9 @@ object Serialization {
private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8"
val systemIn = "sbt/systemIn"
val systemOut = "sbt/systemOut"
val systemErr = "sbt/systemErr"
val systemOutFlush = "sbt/systemOutFlush"
val systemErrFlush = "sbt/systemErrFlush"
val terminalPropertiesQuery = "sbt/terminalPropertiesQuery"
val terminalPropertiesResponse = "sbt/terminalPropertiesResponse"
val terminalCapabilities = "sbt/terminalCapabilities"