sbt.internal.ui.UserThread.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of command_2.12 Show documentation
Show all versions of command_2.12 Show documentation
sbt is an interactive build tool
The newest version!
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal
package ui
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import java.util.concurrent.Executors
import sbt.State
import scala.concurrent.duration._
import sbt.internal.util.JoinThread._
import sbt.internal.util.{ ConsoleAppender, ProgressEvent, ProgressState, Prompt }
private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable {
private[this] val uiThread = new AtomicReference[(UITask, Thread)]
private[sbt] final def onProgressEvent(pe: ProgressEvent): Unit = {
lastProgressEvent.set(pe)
ProgressState.updateProgressState(pe, channel.terminal)
}
private[this] val executor =
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-ui-thread"))
private[this] val lastProgressEvent = new AtomicReference[ProgressEvent]
private[this] val isClosed = new AtomicBoolean(false)
private[sbt] def reset(state: State): Unit = if (!isClosed.get) {
uiThread.synchronized {
val task = channel.makeUIThread(state)
def submit(): Unit = {
val thread: Thread = new Thread(s"sbt-$name-ui-thread") {
setDaemon(true)
override def run(): Unit =
try task.run()
finally {
uiThread.getAndSet(null) match {
case prev @ (_, th) if th != this => uiThread.set(prev)
case _ =>
}
}
}
uiThread.getAndSet((task, thread)) match {
case null => thread.start()
case (prevTask, prevThread) if prevTask.getClass != task.getClass =>
prevTask.close()
prevThread.joinFor(1.second)
thread.start()
case t => uiThread.set(t)
}
}
uiThread.get match {
case null => submit()
case (prevTask, _) if prevTask.getClass == task.getClass =>
case (t, thread) =>
stopThreadImpl()
submit()
}
}
Option(lastProgressEvent.get).foreach(onProgressEvent)
}
private[sbt] def stopThreadImpl(): Unit = uiThread.synchronized {
uiThread.getAndSet(null) match {
case null =>
case (t, thread) =>
t.close()
thread.joinFor(1.second)
}
}
private[sbt] def stopThread(): Unit = uiThread.synchronized(stopThreadImpl())
private[sbt] def onConsolePromptEvent(consolePromptEvent: ConsolePromptEvent): Unit =
// synchronize to ensure that the state isn't modified during the call to reset
// at the bottom
synchronized {
if (terminal.isAnsiSupported) {
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)
}
private[sbt] def onConsoleUnpromptEvent(
consoleUnpromptEvent: ConsoleUnpromptEvent
): Unit = {
terminal.setPrompt(Prompt.Pending)
if (consoleUnpromptEvent.lastSource.fold(true)(_.channelName != name)) {
terminal.progressState.reset()
} else stopThread()
}
override def close(): Unit = if (isClosed.compareAndSet(false, true)) executor.shutdown()
private def terminal = channel.terminal
private def name: String = channel.name
}