diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ProgressState.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ProgressState.scala index d8b77ef53..ca2c3a99f 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ProgressState.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ProgressState.scala @@ -8,16 +8,18 @@ package sbt.internal.util import java.io.PrintStream +import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.atomic.{ AtomicInteger, AtomicReference } import sbt.internal.util.ConsoleAppender.{ ClearScreenAfterCursor, CursorLeft1000, DeleteLine, - cursorUp, + cursorUp } import scala.collection.mutable.ArrayBuffer +import scala.collection.JavaConverters._ private[sbt] final class ProgressState( val progressLines: AtomicReference[Seq[String]], @@ -41,6 +43,9 @@ private[sbt] final class ProgressState( padding.set(0) currentLineBytes.set(new ArrayBuffer[Byte]) } + private[this] val lineBuffer = new ArrayBlockingQueue[String](300) + private[util] def getLines: Seq[String] = lineBuffer.asScala.toVector + private[this] def appendLine(line: String) = while (!lineBuffer.offer(line)) { lineBuffer.poll } private[util] def clearBytes(): Unit = { val pad = padding.get if (currentLineBytes.get.isEmpty && pad > 0) padding.decrementAndGet() @@ -62,10 +67,12 @@ private[sbt] final class ProgressState( if (lines.contains(System.lineSeparator)) { currentLineBytes.set(new ArrayBuffer[Byte]) if (!lines.endsWith(System.lineSeparator)) { - lines - .split(System.lineSeparator) - .lastOption + val allLines = lines.split(System.lineSeparator) + allLines.dropRight(1).foreach(appendLine) + allLines.lastOption .foreach(currentLineBytes.get ++= _.getBytes("UTF-8")) + } else if (lines.contains(System.lineSeparator)) { + lines.split(System.lineSeparator).foreach(appendLine) } } } 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 d59679ee5..dacba581c 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 @@ -109,6 +109,16 @@ trait Terminal extends AutoCloseable { */ private[sbt] def getLastLine: Option[String] + /** + * Returns the buffered lines that have been written to the terminal. The + * main use case is to display the system startup log lines when a client + * connects to a booting server. This could also be used to implement a more + * tmux like experience where multiple clients connect to the same console. + * + * @return the lines + */ + private[sbt] def getLines: Seq[String] + private[sbt] def getBooleanCapability(capability: String, jline3: Boolean): Boolean private[sbt] def getNumericCapability(capability: String, jline3: Boolean): Integer private[sbt] def getStringCapability(capability: String, jline3: Boolean): String @@ -328,6 +338,7 @@ object Terminal { override def close(): Unit = {} override private[sbt] def write(bytes: Int*): Unit = t.write(bytes: _*) override def getLastLine: Option[String] = t.getLastLine + override def getLines: Seq[String] = t.getLines override private[sbt] def name: String = t.name } private[sbt] def get: Terminal = ProxyTerminal @@ -731,7 +742,7 @@ object Terminal { } } - private[sbt] def console: Terminal = consoleTerminalHolder.get match { + def console: Terminal = consoleTerminalHolder.get match { case null => throw new IllegalStateException("Uninitialized terminal.") case term => term } @@ -808,6 +819,7 @@ object Terminal { } def throwIfClosed[R](f: => R): R = if (isStopped.get) throw new ClosedChannelException else f override def getLastLine: Option[String] = progressState.currentLine + override def getLines: Seq[String] = progressState.getLines private val combinedOutputStream = new OutputStream { override def write(b: Int): Unit = { @@ -868,6 +880,7 @@ object Terminal { override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = false override def getHeight: Int = 0 override def getLastLine: Option[String] = None + override def getLines: Seq[String] = Nil override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0) override def getNumericCapability(capability: String, jline3: Boolean): Integer = null override def getStringCapability(capability: String, jline3: Boolean): String = null diff --git a/main-command/src/main/java/sbt/internal/BootServerSocket.java b/main-command/src/main/java/sbt/internal/BootServerSocket.java index 06ba74de2..c519573fd 100644 --- a/main-command/src/main/java/sbt/internal/BootServerSocket.java +++ b/main-command/src/main/java/sbt/internal/BootServerSocket.java @@ -35,6 +35,7 @@ import org.scalasbt.ipcsocket.UnixDomainSocket; import org.scalasbt.ipcsocket.Win32NamedPipeServerSocket; import org.scalasbt.ipcsocket.Win32NamedPipeSocket; import org.scalasbt.ipcsocket.Win32SecurityLevel; +import sbt.internal.util.Terminal; import xsbti.AppConfiguration; /** @@ -102,6 +103,16 @@ public class BootServerSocket implements AutoCloseable { service.submit( () -> { try { + Terminal.console() + .getLines() + .foreach( + l -> { + try { + write((l + System.lineSeparator()).getBytes("UTF-8")); + } catch (final IOException e) { + } + return 0; + }); final InputStream inputStream = socket.getInputStream(); while (alive.get()) { try { @@ -134,6 +145,15 @@ public class BootServerSocket implements AutoCloseable { } } + private void write(final byte[] b) { + try { + if (alive.get()) socket.getOutputStream().write(b); + } catch (final IOException e) { + alive.set(false); + close(); + } + } + private void write(final byte[] b, final int offset, final int len) { try { if (alive.get()) socket.getOutputStream().write(b, offset, len);