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 965d6d253..019e0bb46 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 @@ -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 = {} 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 5a642c22b..b95b6f55c 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -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 diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 5a06c0f8f..4aa3c1f81 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -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] diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index e9cbcf1e1..a8f2e54b6 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -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"