diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala index 1777b78bd..838f00ff6 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala @@ -148,40 +148,9 @@ object ConsoleAppender { * 3. -Dsbt.colour=always/auto/never/true/false * 4. -Dsbt.log.format=always/auto/never/true/false */ - lazy val formatEnabledInEnv: Boolean = { - def useColorDefault: Boolean = { - // This approximates that both stdin and stdio are connected, - // so by default color will be turned off for pipes and redirects. - val hasConsole = Option(java.lang.System.console).isDefined - ansiSupported && hasConsole - } - sys.props.get("sbt.log.noformat") match { - case Some(_) => !java.lang.Boolean.getBoolean("sbt.log.noformat") - case _ => - sys.props - .get("sbt.color") - .orElse(sys.props.get("sbt.colour")) - .orElse(sys.props.get("sbt.log.format")) - .flatMap({ s => - parseLogOption(s) match { - case LogOption.Always => Some(true) - case LogOption.Never => Some(false) - case _ => None - } - }) - .getOrElse(useColorDefault) - } - } + lazy val formatEnabledInEnv: Boolean = Terminal.formatEnabledInEnv - private[sbt] def parseLogOption(s: String): LogOption = - s.toLowerCase match { - case "always" => LogOption.Always - case "auto" => LogOption.Auto - case "never" => LogOption.Never - case "true" => LogOption.Always - case "false" => LogOption.Never - case _ => LogOption.Auto - } + private[sbt] def parseLogOption(s: String): LogOption = Terminal.parseLogOption(s) private[this] val generateId: AtomicInteger = new AtomicInteger @@ -399,6 +368,10 @@ trait Appender extends AutoCloseable { if (ansiCodesSupported && useFormat) scala.Console.RESET else "" } + private def clearScreenAfterCursor: String = { + if (ansiCodesSupported && useFormat) ClearScreenAfterCursor + else "" + } private val SUCCESS_LABEL_COLOR = GREEN private val SUCCESS_MESSAGE_COLOR = reset @@ -488,7 +461,7 @@ trait Appender extends AutoCloseable { if (message == null) () else { val len = - labelColor.length + label.length + messageColor.length + reset.length * 3 + ClearScreenAfterCursor.length + labelColor.length + label.length + messageColor.length + reset.length * 3 + clearScreenAfterCursor.length val builder: StringBuilder = new StringBuilder(len) message.linesIterator.foreach { line => builder.ensureCapacity(len + line.length + 4) @@ -500,7 +473,7 @@ trait Appender extends AutoCloseable { fmted(labelColor, label) builder.append("] ") fmted(messageColor, line) - builder.append(ClearScreenAfterCursor) + builder.append(clearScreenAfterCursor) write(builder.toString) } } 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 4e8ef7262..d44b7b91d 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 @@ -275,17 +275,58 @@ object Terminal { private[this] val hasProgress: AtomicBoolean = new AtomicBoolean(false) + private[sbt] def parseLogOption(s: String): LogOption = + s.toLowerCase match { + case "always" => LogOption.Always + case "auto" => LogOption.Auto + case "never" => LogOption.Never + case "true" => LogOption.Always + case "false" => LogOption.Never + case _ => LogOption.Auto + } + + /** + * Indicates whether formatting has been disabled in environment variables. + * 1. -Dsbt.log.noformat=true means no formatting. + * 2. -Dsbt.color=always/auto/never/true/false + * 3. -Dsbt.colour=always/auto/never/true/false + * 4. -Dsbt.log.format=always/auto/never/true/false + */ + private[sbt] lazy val formatEnabledInEnv: Boolean = { + def useColorDefault: Boolean = { + // This approximates that both stdin and stdio are connected, + // so by default color will be turned off for pipes and redirects. + val hasConsole = Option(java.lang.System.console).isDefined + props.map(_.ansi).getOrElse(true) && hasConsole + } + sys.props.get("sbt.log.noformat") match { + case Some(_) => !java.lang.Boolean.getBoolean("sbt.log.noformat") + case _ => + sys.props + .get("sbt.color") + .orElse(sys.props.get("sbt.colour")) + .orElse(sys.props.get("sbt.log.format")) + .flatMap({ s => + parseLogOption(s) match { + case LogOption.Always => Some(true) + case LogOption.Never => Some(false) + case _ => None + } + }) + .getOrElse(useColorDefault) + } + } + /** * - * @param progress toggles whether or not the console terminal has progress + * @param isServer toggles whether or not this is a server of client process * @param f the thunk to run * @tparam T the result type of the thunk * @return the result of the thunk */ private[sbt] def withStreams[T](isServer: Boolean)(f: => T): T = // In ci environments, don't touch the io streams unless run with -Dsbt.io.virtual=true - if (isCI && System.getProperty("sbt.io.virtual", "") != "true") f - else { + if (System.getProperty("sbt.io.virtual", "") == "true" || (formatEnabledInEnv && !isCI)) { hasProgress.set(isServer) consoleTerminalHolder.set(wrap(jline.TerminalFactory.get)) activeTerminal.set(consoleTerminalHolder.get) @@ -317,7 +358,7 @@ object Terminal { console.close() } } - } + } else f private[this] object ProxyTerminal extends Terminal { private def t: Terminal = activeTerminal.get @@ -693,7 +734,7 @@ object Terminal { override def isSupported: Boolean = terminal.isSupported override def getWidth: Int = props.map(_.width).getOrElse(terminal.getWidth) override def getHeight: Int = props.map(_.height).getOrElse(terminal.getHeight) - override def isAnsiSupported: Boolean = props.map(_.ansi).getOrElse(terminal.isAnsiSupported) + override def isAnsiSupported: Boolean = formatEnabledInEnv override def wrapOutIfNeeded(out: OutputStream): OutputStream = terminal.wrapOutIfNeeded(out) override def wrapInIfNeeded(in: InputStream): InputStream = terminal.wrapInIfNeeded(in) override def hasWeirdWrap: Boolean = terminal.hasWeirdWrap @@ -952,6 +993,7 @@ object Terminal { } private[sbt] object NullTerminal extends DefaultTerminal private[sbt] object SimpleTerminal extends DefaultTerminal { + override lazy val inputStream: InputStream = nonBlockingIn override lazy val outputStream: OutputStream = originalOut override lazy val errorStream: OutputStream = originalErr } diff --git a/main-command/src/main/scala/sbt/internal/ui/UserThread.scala b/main-command/src/main/scala/sbt/internal/ui/UserThread.scala index 1e251d1cc..c547994b9 100644 --- a/main-command/src/main/scala/sbt/internal/ui/UserThread.scala +++ b/main-command/src/main/scala/sbt/internal/ui/UserThread.scala @@ -75,9 +75,11 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable // synchronize to ensure that the state isn't modified during the call to reset // at the bottom synchronized { - channel.terminal.withPrintStream { ps => - ps.print(ConsoleAppender.ClearScreenAfterCursor) - ps.flush() + if (terminal.isAnsiSupported) { + channel.terminal.withPrintStream { ps => + ps.print(ConsoleAppender.ClearScreenAfterCursor) + ps.flush() + } } val state = consolePromptEvent.state terminal.prompt match { diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 34e6333c6..e3aacd778 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -104,8 +104,10 @@ private[sbt] object xMain { } finally { // Clear any stray progress lines ShutdownHooks.close() - System.out.print(ConsoleAppender.ClearScreenAfterCursor) - System.out.flush() + if (Terminal.formatEnabledInEnv) { + System.out.print(ConsoleAppender.ClearScreenAfterCursor) + System.out.flush() + } } } diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index e7ec8c644..c97cbfe16 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -729,7 +729,6 @@ private[sbt] object Continuous extends DeprecatedContinuous { * to a state where it does not parse an action, we can wait until we receive new input * to attempt to parse again. */ - type ActionParser = String => Watch.Action // Transform the Config.watchSettings.inputParser instances to functions of type // String => Watch.Action. The String that is provided will contain any characters that // have been read from stdin. If there are any characters available, then it calls the @@ -766,9 +765,10 @@ private[sbt] object Continuous extends DeprecatedContinuous { val action = try { interrupted.set(false) - val byte = terminal.inputStream.read - val parse: ActionParser => Watch.Action = parser => parser(byte.toChar.toString) - parse(inputHandler) + terminal.inputStream.read match { + case -1 => Watch.Ignore + case byte => inputHandler(byte.toChar.toString) + } } catch { case _: InterruptedException => interrupted.set(true)