Consolidate terminal prompt management

It was a bit tricky to reason about the state of the prompt for a
terminal. To help make things more clear, I reworked things so that the
LineReader always sets the prompt to Pending after it reads a command.
In MainLoop, we cache the prompt value and temporarily set it to Running
while the command is running, which is really how it should have always
been.
This commit is contained in:
Ethan Atkins 2020-08-03 10:37:32 -07:00
parent 90dacc339c
commit d569abe70a
5 changed files with 44 additions and 43 deletions

View File

@ -73,7 +73,6 @@ object LineReader {
historyPath: Option[File], historyPath: Option[File],
parser: Parser[_], parser: Parser[_],
terminal: Terminal, terminal: Terminal,
prompt: Prompt = Prompt.Running,
): LineReader = { ): LineReader = {
val term = JLine3(terminal) val term = JLine3(terminal)
// We may want to consider insourcing LineReader.java from jline. We don't otherwise // We may want to consider insourcing LineReader.java from jline. We don't otherwise

View File

@ -82,7 +82,9 @@ private[sbt] object UITask {
} }
} }
} }
impl() val res = impl()
terminal.setPrompt(Prompt.Pending)
res
} catch { case e: InterruptedException => Right("") } } catch { case e: InterruptedException => Right("") }
override def close(): Unit = closed.set(true) override def close(): Unit = closed.set(true)
} }

View File

@ -31,21 +31,21 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable
uiThread.synchronized { uiThread.synchronized {
val task = channel.makeUIThread(state) val task = channel.makeUIThread(state)
def submit(): Thread = { def submit(): Thread = {
def close(): Unit = { val thread: Thread = new Thread(s"sbt-$name-ui-thread") {
uiThread.get match { setDaemon(true)
case (_, t) if t == thread => uiThread.set(null) override def run(): Unit =
case _ => try task.run()
} finally uiThread.get match {
case (_, t) if t == this => uiThread.set(null)
case _ =>
}
} }
lazy val thread = new Thread(() => {
try task.run()
finally close()
}, s"sbt-$name-ui-thread")
thread.setDaemon(true)
thread.start()
uiThread.getAndSet((task, thread)) match { uiThread.getAndSet((task, thread)) match {
case null => case null => thread.start()
case (_, t) => t.interrupt() case (task, t) if t.getClass != task.getClass =>
stopThreadImpl()
thread.start()
case t => uiThread.set(t)
} }
thread thread
} }
@ -53,14 +53,14 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable
case null => uiThread.set((task, submit())) case null => uiThread.set((task, submit()))
case (t, _) if t.getClass == task.getClass => case (t, _) if t.getClass == task.getClass =>
case (t, thread) => case (t, thread) =>
thread.interrupt() stopThreadImpl()
uiThread.set((task, submit())) uiThread.set((task, submit()))
} }
} }
Option(lastProgressEvent.get).foreach(onProgressEvent) Option(lastProgressEvent.get).foreach(onProgressEvent)
} }
private[sbt] def stopThread(): Unit = uiThread.synchronized { private[sbt] def stopThreadImpl(): Unit = uiThread.synchronized {
uiThread.getAndSet(null) match { uiThread.getAndSet(null) match {
case null => case null =>
case (t, thread) => case (t, thread) =>
@ -75,21 +75,25 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable
() ()
} }
} }
private[sbt] def stopThread(): Unit = uiThread.synchronized(stopThreadImpl())
private[sbt] def onConsolePromptEvent(consolePromptEvent: ConsolePromptEvent): Unit = { private[sbt] def onConsolePromptEvent(consolePromptEvent: ConsolePromptEvent): Unit =
channel.terminal.withPrintStream { ps => // synchronize to ensure that the state isn't modified during the call to reset
ps.print(ConsoleAppender.ClearScreenAfterCursor) // at the bottom
ps.flush() synchronized {
channel.terminal.withPrintStream { ps =>
ps.print(ConsoleAppender.ClearScreenAfterCursor)
ps.flush()
}
val state = consolePromptEvent.state
terminal.prompt match {
case Prompt.Running | Prompt.Pending =>
terminal.setPrompt(Prompt.AskUser(() => UITask.shellPrompt(terminal, state)))
case _ =>
}
onProgressEvent(ProgressEvent("Info", Vector(), None, None, None))
reset(state)
} }
val state = consolePromptEvent.state
terminal.prompt match {
case Prompt.Running | Prompt.Pending =>
terminal.setPrompt(Prompt.AskUser(() => UITask.shellPrompt(terminal, state)))
case _ =>
}
onProgressEvent(ProgressEvent("Info", Vector(), None, None, None))
reset(state)
}
private[sbt] def onConsoleUnpromptEvent( private[sbt] def onConsoleUnpromptEvent(
consoleUnpromptEvent: ConsoleUnpromptEvent consoleUnpromptEvent: ConsoleUnpromptEvent

View File

@ -15,8 +15,8 @@ import sbt.internal.ShutdownHooks
import sbt.internal.langserver.ErrorCodes import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.JsonRpcResponseError import sbt.internal.protocol.JsonRpcResponseError
import sbt.internal.nio.CheckBuildSources.CheckBuildSourcesKey import sbt.internal.nio.CheckBuildSources.CheckBuildSourcesKey
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking, Terminal } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking, Prompt, Terminal }
import sbt.internal.{ ConsoleUnpromptEvent, ShutdownHooks } import sbt.internal.ShutdownHooks
import sbt.io.{ IO, Using } import sbt.io.{ IO, Using }
import sbt.protocol._ import sbt.protocol._
import sbt.util.{ Logger, LoggerContext } import sbt.util.{ Logger, LoggerContext }
@ -206,13 +206,16 @@ object MainLoop {
state.put(sbt.Keys.currentTaskProgress, new Keys.TaskProgress(progress)) state.put(sbt.Keys.currentTaskProgress, new Keys.TaskProgress(progress))
} else state } else state
} }
StandardMain.exchange.setState(progressState) exchange.setState(progressState)
StandardMain.exchange.setExec(Some(exec)) exchange.setExec(Some(exec))
StandardMain.exchange.unprompt(ConsoleUnpromptEvent(exec.source))
val restoreTerminal = channelName.flatMap(exchange.channelForName) match { val restoreTerminal = channelName.flatMap(exchange.channelForName) match {
case Some(c) => case Some(c) =>
val prevTerminal = Terminal.set(c.terminal) val prevTerminal = Terminal.set(c.terminal)
val prevPrompt = c.terminal.prompt
// temporarily set the prompt to running during task evaluation
c.terminal.setPrompt(Prompt.Running)
() => { () => {
c.terminal.setPrompt(prevPrompt)
Terminal.set(prevTerminal) Terminal.set(prevTerminal)
c.terminal.flush() c.terminal.flush()
} }

View File

@ -133,17 +133,10 @@ private[sbt] final class CommandExchange {
} }
} }
// Do not manually run GC until the user has been idling for at least the min gc interval. // Do not manually run GC until the user has been idling for at least the min gc interval.
val exec = impl(interval match { impl(interval match {
case d: FiniteDuration => Some(d.fromNow) case d: FiniteDuration => Some(d.fromNow)
case _ => None case _ => None
}, idleDeadline) }, idleDeadline)
exec.source.foreach { s =>
channelForName(s.channelName).foreach {
case c if c.terminal.prompt != Prompt.Batch => c.terminal.setPrompt(Prompt.Running)
case _ =>
}
}
exec
} }
private def addConsoleChannel(): Unit = private def addConsoleChannel(): Unit =