From cd26abd656dd2bf6932cee09bdea3a08c2ded001 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 19 Jul 2020 12:02:45 -0700 Subject: [PATCH 1/5] Print ClearScreenAfterCursor on shutdown There are scenarios in which sbt can leave behind stray progress lines after it exits. This attempts to delete them. --- main/src/main/scala/sbt/Main.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index c9862bedc..6fccb749f 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -102,7 +102,10 @@ private[sbt] object xMain { } } } finally { + // Clear any stray progress lines ShutdownHooks.close() + System.out.print(ConsoleAppender.ClearScreenAfterCursor) + System.out.flush() } } From 761c926944c9fdfddf29e495ae2af53c5a3ec9c7 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 19 Jul 2020 12:10:19 -0700 Subject: [PATCH 2/5] Catch ClosedChannelException in TaskTraceEvent sbt would print a stack trace on exit when run with -Dsbt.task.timings=true. This removes that annoying stack trace. --- main/src/main/scala/sbt/internal/TaskTraceEvent.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/TaskTraceEvent.scala b/main/src/main/scala/sbt/internal/TaskTraceEvent.scala index de8b4eede..b623c70cc 100644 --- a/main/src/main/scala/sbt/internal/TaskTraceEvent.scala +++ b/main/src/main/scala/sbt/internal/TaskTraceEvent.scala @@ -73,7 +73,8 @@ private[sbt] final class TaskTraceEvent () } finally { trace.close() - console.println(s"wrote $outFile") + try console.println(s"wrote $outFile") + catch { case _: java.nio.channels.ClosedChannelException => } } } } From 901c8ee5df1bdb2aca9a00c31779351f017942b3 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 19 Jul 2020 12:13:11 -0700 Subject: [PATCH 3/5] Use new history file path for jline3 The jline2 history file format is incompatible with jline3 and jline3 prints a very noisy warning if it detects such a file. History will also not work with jline3 until you remove or reformat the old file. --- main/src/main/scala/sbt/Defaults.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index f783d2d3a..6a5cdd6fb 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -454,7 +454,12 @@ object Defaults extends BuildCommon { def paths: Seq[Setting[_]] = Seq( baseDirectory := thisProject.value.base, target := baseDirectory.value / "target", - historyPath := (historyPath or target(t => Option(t / ".history"))).value, + // Use a different history path for jline3 because the jline2 format is + // incompatible. By sbt 1.4.0, we should consider revering this to t / ".history" + // and possibly rewriting the jline2 history in a jline3 compatible format if the + // history file is incompatible. For now, just use a different file to facilitate + // going back and forth between 1.3.x and 1.4.x. + historyPath := (historyPath or target(t => Option(t / ".history3"))).value, sourceDirectory := baseDirectory.value / "src", sourceManaged := crossTarget.value / "src_managed", resourceManaged := crossTarget.value / "resource_managed", From 12112741cb6aa998bb49aa6046ce80c3f8f07049 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 21 Jul 2020 11:34:11 -0700 Subject: [PATCH 4/5] Revert "Unprompt channels during project load" This reverts commit b1dcf031a585d8f6ba8cc86f3e215c9673f9bd18. I found that b1dcf031a585d8f6ba8cc86f3e215c9673f9bd18 had some unintended consequences that seemed to mess up the prompt state. The real problem that it was trying to address was that the prompt was being interleaved with log messages in some scenarios. There was a different way to fix that in ProgressState that was both simpler and more reliable. --- .../main/scala/sbt/internal/util/ProgressState.scala | 2 +- .../src/main/scala/sbt/internal/util/Prompt.scala | 1 - .../src/main/scala/sbt/internal/ui/UserThread.scala | 7 +++---- main/src/main/scala/sbt/Main.scala | 12 +++--------- main/src/main/scala/sbt/MainLoop.scala | 6 +++--- .../main/scala/sbt/internal/CommandExchange.scala | 6 +----- main/src/main/scala/sbt/internal/Continuous.scala | 6 +++--- 7 files changed, 14 insertions(+), 26 deletions(-) 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 ca2c3a99f..80b81c543 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 @@ -90,7 +90,7 @@ private[sbt] final class ProgressState( hasProgress: Boolean ): Unit = { addBytes(terminal, bytes) - if (hasProgress && terminal.prompt != Prompt.Loading) { + if (hasProgress) { terminal.prompt match { case a: Prompt.AskUser if a.render.nonEmpty => printStream.print(System.lineSeparator + ClearScreenAfterCursor + CursorLeft1000) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/Prompt.scala b/internal/util-logging/src/main/scala/sbt/internal/util/Prompt.scala index eb5e4a660..90f02f66c 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/Prompt.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/Prompt.scala @@ -34,6 +34,5 @@ private[sbt] object Prompt { private[sbt] case object Running extends NoPrompt private[sbt] case object Batch extends NoPrompt private[sbt] case object Watch extends NoPrompt - private[sbt] case object Loading extends NoPrompt private[sbt] case object NoPrompt extends NoPrompt } 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 97accf35b..e868c3682 100644 --- a/main-command/src/main/scala/sbt/internal/ui/UserThread.scala +++ b/main-command/src/main/scala/sbt/internal/ui/UserThread.scala @@ -14,7 +14,7 @@ import java.util.concurrent.Executors import sbt.State import sbt.internal.util.{ ConsoleAppender, ProgressEvent, ProgressState, Util } -import sbt.internal.util.Prompt.{ AskUser, Loading, Running } +import sbt.internal.util.Prompt.{ AskUser, Running } private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable { private[this] val uiThread = new AtomicReference[(UITask, Thread)] @@ -70,9 +70,8 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable } val state = consolePromptEvent.state terminal.prompt match { - case Loading | Running => - terminal.setPrompt(AskUser(() => UITask.shellPrompt(terminal, state))) - case _ => + case Running => terminal.setPrompt(AskUser(() => UITask.shellPrompt(terminal, state))) + case _ => } onProgressEvent(ProgressEvent("Info", Vector(), None, None, None)) reset(state) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 6fccb749f..9b3b71e35 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -916,8 +916,6 @@ object BuiltinCommands { } def doLoadProject(s0: State, action: LoadAction.Value): State = { - StandardMain.exchange.unprompt(ConsoleUnpromptEvent(None), force = true) - StandardMain.exchange.channels.foreach(_.terminal.setPrompt(Prompt.Loading)) welcomeBanner(s0) checkSBTVersionChanged(s0) val (s1, base) = Project.loadAction(SessionVar.clear(s0), action) @@ -938,9 +936,7 @@ object BuiltinCommands { SessionSettings.checkSession(session, s2) val s3 = addCacheStoreFactoryFactory(Project.setProject(session, structure, s2)) val s4 = setupGlobalFileTreeRepository(s3) - val s5 = CheckBuildSources.init(LintUnused.lintUnusedFunc(s4)) - StandardMain.exchange.prompt(ConsolePromptEvent(s5)) - s5 + CheckBuildSources.init(LintUnused.lintUnusedFunc(s4)) } private val setupGlobalFileTreeRepository: State => State = { state => @@ -984,11 +980,9 @@ object BuiltinCommands { val exchange = StandardMain.exchange if (exchange.channels.exists(ContinuousCommands.isInWatch)) { val s1 = exchange.run(s0) - def needPrompt(c: CommandChannel) = - ContinuousCommands.isInWatch(c) && !ContinuousCommands.isPending(c) exchange.channels.foreach { - case c if needPrompt(c) => c.prompt(ConsolePromptEvent(s1)) - case _ => + case c if ContinuousCommands.isPending(c) => + case c => c.prompt(ConsolePromptEvent(s1)) } val exec: Exec = getExec(s1, Duration.Inf) val remaining: List[Exec] = diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 53a662371..09f26e96e 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -198,9 +198,9 @@ object MainLoop { state.put(sbt.Keys.currentTaskProgress, new Keys.TaskProgress(progress)) } else state } - exchange.setState(progressState) - exchange.setExec(Some(exec)) - exchange.unprompt(ConsoleUnpromptEvent(exec.source), force = false) + StandardMain.exchange.setState(progressState) + StandardMain.exchange.setExec(Some(exec)) + StandardMain.exchange.unprompt(ConsoleUnpromptEvent(exec.source)) val newState = Command.process(exec.commandLine, progressState) if (exec.execId.fold(true)(!_.startsWith(networkExecPrefix)) && !exec.commandLine.startsWith(networkExecPrefix)) { diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index ab169877d..d4c6bbf27 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -357,11 +357,7 @@ private[sbt] final class CommandExchange { case c => c.prompt(event) } } - def unprompt(event: ConsoleUnpromptEvent, force: Boolean): Unit = { - if (force) - channels.foreach(c => c.unprompt(event.copy(lastSource = Some(CommandSource(c.name))))) - else channels.foreach(_.unprompt(event)) - } + def unprompt(event: ConsoleUnpromptEvent): Unit = channels.foreach(_.unprompt(event)) def logMessage(event: LogEvent): Unit = { channels.foreach { diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 0ea51c4a2..86618ceb2 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -1254,11 +1254,11 @@ private[sbt] object ContinuousCommands { } private[this] val preWatchCommand = watchCommand(preWatch) { (channel, state) => - StandardMain.exchange.channelForName(channel).foreach(_.terminal.setPrompt(Prompt.Running)) + StandardMain.exchange.channelForName(channel).foreach(_.terminal.setPrompt(Prompt.Watch)) watchState(channel).beforeCommand(state) } private[this] val postWatchCommand = watchCommand(postWatch) { (channel, state) => - StandardMain.exchange.unprompt(ConsoleUnpromptEvent(Some(CommandSource(channel))), false) + StandardMain.exchange.unprompt(ConsoleUnpromptEvent(Some(CommandSource(channel)))) val ws = watchState(channel) watchStates.put(channel, ws.withPending(false)) ws.afterCommand(state) @@ -1268,7 +1268,7 @@ private[sbt] object ContinuousCommands { state } private[sbt] def stopWatchImpl(channelName: String): Unit = { - StandardMain.exchange.unprompt(ConsoleUnpromptEvent(Some(CommandSource(channelName))), false) + StandardMain.exchange.unprompt(ConsoleUnpromptEvent(Some(CommandSource(channelName)))) Option(watchStates.get(channelName)).foreach { ws => ws.afterWatch() ws.callbacks.onExit() From 9b332379ddb7a3b54a01bc08c97fe300587e488f Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 21 Jul 2020 12:41:53 -0700 Subject: [PATCH 5/5] Clear prompt on log messages Printing a new line was not great ux. You might see something like: [info] set current project to project (in build file:project) sbt:project> [info] new client connected: network-1 sbt:project> instead of initially [info] set current project to project (in build file:project) sbt:project> and then after the client connects: [info] set current project to project (in build file:project) [info] new client connected: network-1 sbt:project> --- .../src/main/scala/sbt/internal/util/ProgressState.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 80b81c543..d27fe9dcd 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 @@ -80,8 +80,8 @@ private[sbt] final class ProgressState( private[util] def printPrompt(terminal: Terminal, printStream: PrintStream): Unit = if (terminal.prompt != Prompt.Running && terminal.prompt != Prompt.Batch) { val prefix = if (terminal.isAnsiSupported) s"$DeleteLine$CursorLeft1000" else "" - val pmpt = prefix.getBytes ++ terminal.prompt.render().getBytes - pmpt.foreach(b => printStream.write(b & 0xFF)) + printStream.write(prefix.getBytes ++ terminal.prompt.render().getBytes) + printStream.flush() } private[util] def write( terminal: Terminal, @@ -93,7 +93,7 @@ private[sbt] final class ProgressState( if (hasProgress) { terminal.prompt match { case a: Prompt.AskUser if a.render.nonEmpty => - printStream.print(System.lineSeparator + ClearScreenAfterCursor + CursorLeft1000) + printStream.print(DeleteLine + ClearScreenAfterCursor + CursorLeft1000) printStream.flush() case _ => }