mirror of https://github.com/sbt/sbt.git
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:
parent
90dacc339c
commit
d569abe70a
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue