mirror of https://github.com/sbt/sbt.git
Merge pull request #5711 from eatkins/jline3-console
Support scala 2.13 and dotty console in thin client
This commit is contained in:
commit
31bea086aa
|
|
@ -304,7 +304,7 @@ val completeProj = (project in file("internal") / "util-complete")
|
|||
testedBaseSettings,
|
||||
name := "Completion",
|
||||
libraryDependencies += jline,
|
||||
libraryDependencies += jline3,
|
||||
libraryDependencies += jline3Reader,
|
||||
mimaSettings,
|
||||
// Parser is used publicly, so we can't break bincompat.
|
||||
mimaBinaryIssueFilters := Seq(
|
||||
|
|
@ -366,7 +366,8 @@ lazy val utilLogging = (project in file("internal") / "util-logging")
|
|||
libraryDependencies ++=
|
||||
Seq(
|
||||
jline,
|
||||
jline3,
|
||||
jline3Terminal,
|
||||
jline3Jansi,
|
||||
log4jApi,
|
||||
log4jCore,
|
||||
disruptor,
|
||||
|
|
@ -661,6 +662,7 @@ lazy val actionsProj = (project in file("main-actions"))
|
|||
testedBaseSettings,
|
||||
name := "Actions",
|
||||
libraryDependencies += sjsonNewScalaJson.value,
|
||||
libraryDependencies += jline3Terminal,
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Seq(
|
||||
// Removed unused private[sbt] nested class
|
||||
|
|
@ -1017,6 +1019,7 @@ lazy val mainProj = (project in file("main"))
|
|||
// internal logging apis,
|
||||
exclude[IncompatibleSignatureProblem]("sbt.internal.LogManager*"),
|
||||
exclude[MissingTypesProblem]("sbt.internal.RelayAppender"),
|
||||
exclude[MissingClassProblem]("sbt.internal.TaskProgress$ProgressThread")
|
||||
)
|
||||
)
|
||||
.configure(
|
||||
|
|
@ -1103,7 +1106,6 @@ lazy val sbtClientProj = (project in file("client"))
|
|||
crossPaths := false,
|
||||
exportJars := true,
|
||||
libraryDependencies += jansi,
|
||||
libraryDependencies += jline3Jansi,
|
||||
libraryDependencies += scalatest % "test",
|
||||
/*
|
||||
* On windows, the raw classpath is too large to be a command argument to an
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ object LineReader {
|
|||
historyPath: Option[File],
|
||||
parser: Parser[_],
|
||||
terminal: Terminal,
|
||||
prompt: Prompt = Prompt.Running,
|
||||
): LineReader = {
|
||||
val term = JLine3(terminal)
|
||||
// We may want to consider insourcing LineReader.java from jline. We don't otherwise
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util;
|
||||
|
||||
import org.jline.terminal.TerminalBuilder;
|
||||
|
||||
/**
|
||||
* This exists to a provide a wrapper to TerminalBuilder.setTerminalOverride that will not emit a
|
||||
* deprecation warning when called from scala.
|
||||
*/
|
||||
public class DeprecatedJLine {
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setTerminalOverride(final org.jline.terminal.Terminal terminal) {
|
||||
TerminalBuilder.setTerminalOverride(terminal);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ object ConsoleOut {
|
|||
override def println(s: String): Unit = get.println(s)
|
||||
override def println(): Unit = get.println()
|
||||
override def flush(): Unit = get.flush()
|
||||
override def toString: String = s"ProxyConsoleOut"
|
||||
}
|
||||
|
||||
def overwriteContaining(s: String): (String, String) => Boolean =
|
||||
|
|
@ -70,6 +71,7 @@ object ConsoleOut {
|
|||
last = Some(s)
|
||||
current.setLength(0)
|
||||
}
|
||||
override def toString: String = s"SystemOutOverwrite@${System.identityHashCode(this)}"
|
||||
}
|
||||
|
||||
def terminalOut: ConsoleOut = new ConsoleOut {
|
||||
|
|
@ -78,6 +80,7 @@ object ConsoleOut {
|
|||
override def println(s: String): Unit = Terminal.get.printStream.println(s)
|
||||
override def println(): Unit = Terminal.get.printStream.println()
|
||||
override def flush(): Unit = Terminal.get.printStream.flush()
|
||||
override def toString: String = s"TerminalOut"
|
||||
}
|
||||
|
||||
private[this] val consoleOutPerTerminal = new ConcurrentHashMap[Terminal, ConsoleOut]
|
||||
|
|
@ -89,6 +92,7 @@ object ConsoleOut {
|
|||
override def println(s: String): Unit = terminal.printStream.println(s)
|
||||
override def println(): Unit = terminal.printStream.println()
|
||||
override def flush(): Unit = terminal.printStream.flush()
|
||||
override def toString: String = s"TerminalOut($terminal)"
|
||||
}
|
||||
consoleOutPerTerminal.put(terminal, res)
|
||||
res
|
||||
|
|
@ -100,6 +104,7 @@ object ConsoleOut {
|
|||
def println(s: String) = out.println(s)
|
||||
def println() = out.println()
|
||||
def flush() = out.flush()
|
||||
override def toString: String = s"PrintStreamConsoleOut($out)"
|
||||
}
|
||||
def printWriterOut(out: PrintWriter): ConsoleOut = new ConsoleOut {
|
||||
val lockObject = out
|
||||
|
|
@ -107,6 +112,7 @@ object ConsoleOut {
|
|||
def println(s: String) = { out.println(s); flush() }
|
||||
def println() = { out.println(); flush() }
|
||||
def flush() = { out.flush() }
|
||||
override def toString: String = s"PrintWriterConsoleOut($out)"
|
||||
}
|
||||
def bufferedWriterOut(out: BufferedWriter): ConsoleOut = new ConsoleOut {
|
||||
val lockObject = out
|
||||
|
|
@ -114,5 +120,6 @@ object ConsoleOut {
|
|||
def println(s: String) = { out.write(s); println() }
|
||||
def println() = { out.newLine(); flush() }
|
||||
def flush() = { out.flush() }
|
||||
override def toString: String = s"BufferedWriterConsoleOut($out)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.{ EOFException, InputStream, OutputStream, PrintWriter }
|
||||
import java.io.{ InputStream, OutputStream, PrintWriter }
|
||||
import java.nio.charset.Charset
|
||||
import java.util.{ Arrays, EnumSet }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||
import org.jline.utils.InfoCmp.Capability
|
||||
import org.jline.utils.{ NonBlocking, OSUtils }
|
||||
import org.jline.utils.{ ClosedException, NonBlockingReader, OSUtils }
|
||||
import org.jline.terminal.{ Attributes, Size, Terminal => JTerminal }
|
||||
import org.jline.terminal.Terminal.SignalHandler
|
||||
import org.jline.terminal.impl.AbstractTerminal
|
||||
|
|
@ -20,8 +20,9 @@ import org.jline.terminal.impl.jansi.JansiSupportImpl
|
|||
import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.util.Try
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
|
||||
private[util] object JLine3 {
|
||||
private[sbt] object JLine3 {
|
||||
private val capabilityMap = Capability
|
||||
.values()
|
||||
.map { c =>
|
||||
|
|
@ -77,6 +78,8 @@ private[util] object JLine3 {
|
|||
new AbstractTerminal(term.name, "ansi", Charset.forName("UTF-8"), SignalHandler.SIG_DFL) {
|
||||
val closed = new AtomicBoolean(false)
|
||||
setOnClose { () =>
|
||||
doClose()
|
||||
reader.close()
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
// This is necessary to shutdown the non blocking input reader
|
||||
// so that it doesn't keep blocking
|
||||
|
|
@ -89,29 +92,76 @@ private[util] object JLine3 {
|
|||
parseInfoCmp()
|
||||
override val input: InputStream = new InputStream {
|
||||
override def read: Int = {
|
||||
val res = try term.inputStream.read
|
||||
catch { case _: InterruptedException => -2 }
|
||||
val res = term.inputStream match {
|
||||
case w: Terminal.WriteableInputStream =>
|
||||
val result = new LinkedBlockingQueue[Integer]
|
||||
try {
|
||||
w.read(result)
|
||||
result.poll match {
|
||||
case null => throw new ClosedException
|
||||
case i => i.toInt
|
||||
}
|
||||
} catch {
|
||||
case _: InterruptedException =>
|
||||
w.cancel()
|
||||
throw new ClosedException
|
||||
}
|
||||
case _ => throw new ClosedException
|
||||
}
|
||||
if (res == 4 && term.prompt.render().endsWith(term.prompt.mkPrompt()))
|
||||
throw new EOFException
|
||||
throw new ClosedException
|
||||
res
|
||||
}
|
||||
}
|
||||
override val output: OutputStream = new OutputStream {
|
||||
override def write(b: Int): Unit = write(Array[Byte](b.toByte))
|
||||
override def write(b: Array[Byte]): Unit = if (!closed.get) term.withPrintStream { ps =>
|
||||
ps.write(b)
|
||||
term.prompt match {
|
||||
case a: Prompt.AskUser => a.write(b)
|
||||
case _ =>
|
||||
}
|
||||
ps.write(b)
|
||||
}
|
||||
override def write(b: Array[Byte], offset: Int, len: Int) =
|
||||
write(Arrays.copyOfRange(b, offset, offset + len))
|
||||
override def flush(): Unit = term.withPrintStream(_.flush())
|
||||
}
|
||||
|
||||
override val reader =
|
||||
NonBlocking.nonBlocking(term.name, input, Charset.defaultCharset())
|
||||
override val reader = new NonBlockingReader {
|
||||
val buffer = new LinkedBlockingQueue[Integer]
|
||||
val thread = new AtomicReference[Thread]
|
||||
private def fillBuffer(): Unit = thread.synchronized {
|
||||
thread.set(Thread.currentThread)
|
||||
buffer.put(
|
||||
try input.read()
|
||||
catch { case _: InterruptedException => -3 }
|
||||
)
|
||||
}
|
||||
override def close(): Unit = thread.get match {
|
||||
case null =>
|
||||
case t => t.interrupt()
|
||||
}
|
||||
override def read(timeout: Long, peek: Boolean) = {
|
||||
if (buffer.isEmpty && !peek) fillBuffer()
|
||||
(if (peek) buffer.peek else buffer.take) match {
|
||||
case null => -2
|
||||
case i => if (i == -3) throw new ClosedException else i
|
||||
}
|
||||
}
|
||||
override def peek(timeout: Long): Int = buffer.peek() match {
|
||||
case null => -1
|
||||
case i => i.toInt
|
||||
}
|
||||
override def readBuffered(buf: Array[Char]): Int = {
|
||||
if (buffer.isEmpty) fillBuffer()
|
||||
buffer.take match {
|
||||
case i if i == -1 => -1
|
||||
case i =>
|
||||
buf(0) = i.toChar
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
override val writer: PrintWriter = new PrintWriter(output, true)
|
||||
/*
|
||||
* For now assume that the terminal capabilities for client and server
|
||||
|
|
|
|||
|
|
@ -26,14 +26,16 @@ private[sbt] final class ProgressState(
|
|||
val padding: AtomicInteger,
|
||||
val blankZone: Int,
|
||||
val currentLineBytes: AtomicReference[ArrayBuffer[Byte]],
|
||||
val maxItems: Int,
|
||||
) {
|
||||
def this(blankZone: Int) =
|
||||
this(
|
||||
new AtomicReference(Nil),
|
||||
new AtomicInteger(0),
|
||||
blankZone,
|
||||
new AtomicReference(new ArrayBuffer[Byte]),
|
||||
)
|
||||
def this(blankZone: Int, maxItems: Int) = this(
|
||||
new AtomicReference(Nil),
|
||||
new AtomicInteger(0),
|
||||
blankZone,
|
||||
new AtomicReference(new ArrayBuffer[Byte]),
|
||||
maxItems,
|
||||
)
|
||||
def this(blankZone: Int) = this(blankZone, 8)
|
||||
def currentLine: Option[String] =
|
||||
new String(currentLineBytes.get.toArray, "UTF-8").linesIterator.toSeq.lastOption
|
||||
.map(EscHelpers.stripColorsAndMoves)
|
||||
|
|
@ -78,7 +80,7 @@ private[sbt] final class ProgressState(
|
|||
}
|
||||
|
||||
private[util] def getPrompt(terminal: Terminal): Array[Byte] = {
|
||||
if (terminal.prompt != Prompt.Running && terminal.prompt != Prompt.Batch) {
|
||||
if (terminal.prompt.isInstanceOf[Prompt.AskUser]) {
|
||||
val prefix = if (terminal.isAnsiSupported) s"$DeleteLine$CursorLeft1000" else ""
|
||||
prefix.getBytes ++ terminal.prompt.render().getBytes("UTF-8")
|
||||
} else Array.empty
|
||||
|
|
@ -108,8 +110,8 @@ private[sbt] final class ProgressState(
|
|||
val lines = printProgress(terminal, lastLine)
|
||||
toWrite ++= (ClearScreenAfterCursor + lines).getBytes("UTF-8")
|
||||
}
|
||||
toWrite ++= getPrompt(terminal)
|
||||
}
|
||||
toWrite ++= getPrompt(terminal)
|
||||
printStream.write(toWrite.toArray)
|
||||
printStream.flush()
|
||||
} else printStream.write(bytes)
|
||||
|
|
@ -136,6 +138,9 @@ private[sbt] final class ProgressState(
|
|||
}
|
||||
|
||||
private[sbt] object ProgressState {
|
||||
private val MIN_COMMAND_WIDTH = 10
|
||||
private val SERVER_IS_RUNNING = "sbt server is running "
|
||||
private val SERVER_IS_RUNNING_LENGTH = SERVER_IS_RUNNING.length + 2
|
||||
|
||||
/**
|
||||
* Receives a new task report and replaces the old one. In the event that the new
|
||||
|
|
@ -158,15 +163,24 @@ private[sbt] object ProgressState {
|
|||
if (!pe.skipIfActive.getOrElse(false) || (!isRunning && !isBatch)) {
|
||||
terminal.withPrintStream { ps =>
|
||||
val commandFromThisTerminal = pe.channelName.fold(true)(_ == terminal.name)
|
||||
val info = if ((isRunning || isBatch || noPrompt) && commandFromThisTerminal) {
|
||||
pe.items.map { item =>
|
||||
val info = if (commandFromThisTerminal) {
|
||||
val base = pe.items.map { item =>
|
||||
val elapsed = item.elapsedMicros / 1000000L
|
||||
s" | => ${item.name} ${elapsed}s"
|
||||
}
|
||||
val limit = state.maxItems
|
||||
if (base.size > limit)
|
||||
s" | ... (${base.size - limit} other tasks)" +: base.takeRight(limit)
|
||||
else base
|
||||
} else {
|
||||
pe.command.toSeq.flatMap { cmd =>
|
||||
val width = terminal.getWidth
|
||||
val sanitized = if ((cmd.length + SERVER_IS_RUNNING_LENGTH) > width) {
|
||||
if (SERVER_IS_RUNNING_LENGTH + cmd.length < width) cmd
|
||||
else cmd.take(MIN_COMMAND_WIDTH) + "..."
|
||||
} else cmd
|
||||
val tail = if (isWatch) Nil else "enter 'cancel' to stop evaluation" :: Nil
|
||||
s"sbt server is running '$cmd'" :: tail
|
||||
s"$SERVER_IS_RUNNING '$sanitized'" :: tail
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,5 +34,6 @@ 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 Pending extends NoPrompt
|
||||
private[sbt] case object NoPrompt extends NoPrompt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import java.io.{ InputStream, InterruptedIOException, IOException, OutputStream,
|
|||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.{ Arrays, Locale }
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||
import java.util.concurrent.{ ArrayBlockingQueue, Executors, LinkedBlockingQueue, TimeUnit }
|
||||
import java.util.concurrent.{ Executors, LinkedBlockingQueue, TimeUnit }
|
||||
|
||||
import jline.DefaultTerminal2
|
||||
import jline.console.ConsoleReader
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
|
|
@ -140,8 +141,8 @@ trait Terminal extends AutoCloseable {
|
|||
private[sbt] def withPrintStream[T](f: PrintStream => T): T
|
||||
private[sbt] def withRawOutput[R](f: => R): R
|
||||
private[sbt] def restore(): Unit = {}
|
||||
private[sbt] val progressState = new ProgressState(1)
|
||||
private[this] val promptHolder: AtomicReference[Prompt] = new AtomicReference(Prompt.Running)
|
||||
private[sbt] def progressState: ProgressState
|
||||
private[this] val promptHolder: AtomicReference[Prompt] = new AtomicReference(Prompt.Pending)
|
||||
private[sbt] final def prompt: Prompt = promptHolder.get
|
||||
private[sbt] final def setPrompt(newPrompt: Prompt): Unit =
|
||||
if (prompt != Prompt.NoPrompt) promptHolder.set(newPrompt)
|
||||
|
|
@ -174,10 +175,7 @@ object Terminal {
|
|||
try Terminal.console.printStream.println(s"[info] $string")
|
||||
catch { case _: IOException => }
|
||||
}
|
||||
private[sbt] def set(terminal: Terminal): Terminal = {
|
||||
jline.TerminalFactory.set(terminal.toJLine)
|
||||
activeTerminal.getAndSet(terminal)
|
||||
}
|
||||
private[sbt] def set(terminal: Terminal): Terminal = activeTerminal.getAndSet(terminal)
|
||||
implicit class TerminalOps(private val term: Terminal) extends AnyVal {
|
||||
def ansi(richString: => String, string: => String): String =
|
||||
if (term.isAnsiSupported) richString else string
|
||||
|
|
@ -317,6 +315,7 @@ object Terminal {
|
|||
|
||||
private[this] object ProxyTerminal extends Terminal {
|
||||
private def t: Terminal = activeTerminal.get
|
||||
override private[sbt] def progressState: ProgressState = t.progressState
|
||||
override def getWidth: Int = t.getWidth
|
||||
override def getHeight: Int = t.getHeight
|
||||
override def getLineHeightAndWidth(line: String): (Int, Int) = t.getLineHeightAndWidth(line)
|
||||
|
|
@ -350,6 +349,7 @@ object Terminal {
|
|||
override def getLastLine: Option[String] = t.getLastLine
|
||||
override def getLines: Seq[String] = t.getLines
|
||||
override private[sbt] def name: String = t.name
|
||||
override def toString: String = s"ProxyTerminal(current = $t)"
|
||||
}
|
||||
private[sbt] def get: Terminal = ProxyTerminal
|
||||
|
||||
|
|
@ -395,50 +395,31 @@ object Terminal {
|
|||
private[sbt] class WriteableInputStream(in: InputStream, name: String)
|
||||
extends InputStream
|
||||
with AutoCloseable {
|
||||
final def write(bytes: Int*): Unit = waiting.synchronized {
|
||||
waiting.poll match {
|
||||
case null =>
|
||||
bytes.foreach(b => buffer.put(b))
|
||||
case w =>
|
||||
if (bytes.length > 1) bytes.tail.foreach(b => buffer.put(b))
|
||||
bytes.headOption.foreach(b => w.put(b))
|
||||
}
|
||||
final def write(bytes: Int*): Unit = readThread.synchronized {
|
||||
bytes.foreach(b => buffer.put(b))
|
||||
}
|
||||
private[this] val executor =
|
||||
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader"))
|
||||
private[this] val buffer = new LinkedBlockingQueue[Integer]
|
||||
private[this] val closed = new AtomicBoolean(false)
|
||||
private[this] val readQueue = new LinkedBlockingQueue[Unit]
|
||||
private[this] val waiting = new ArrayBlockingQueue[LinkedBlockingQueue[Integer]](1)
|
||||
private[this] val readThread = new AtomicReference[Thread]
|
||||
/*
|
||||
* Starts a loop that waits for consumers of the InputStream to call read.
|
||||
* When read is called, we enqueue a `LinkedBlockingQueue[Int]` to which
|
||||
* the runnable can return a byte from stdin. If the read caller is interrupted,
|
||||
* they remove the result from the waiting set and any byte read will be
|
||||
* enqueued in the buffer. It is done this way so that we only read from
|
||||
* System.in when a caller actually asks for bytes. If we constantly poll
|
||||
* from System.in, then when the user calls reboot from the console, the
|
||||
* first character they type after reboot is swallowed by the previous
|
||||
* sbt main program. If the user calls reboot from a remote client, we
|
||||
* can't avoid losing the first byte inputted in the console. A more
|
||||
* robust fix would be to override System.in at the launcher level instead
|
||||
* of at the sbt level. At the moment, the use case of a user calling
|
||||
* reboot from a network client and the adding input at the server console
|
||||
* seems pathological enough that it isn't worth putting more effort into
|
||||
* fixing.
|
||||
*
|
||||
* Starts a loop that fills a buffer with bytes from stdin. We only read from
|
||||
* the underlying stream when the buffer is empty and there is an active reader.
|
||||
* If the reader detaches without consuming any bytes, we just buffer the
|
||||
* next byte that we read from the stream. One known issue with this approach
|
||||
* is that if a remote client triggers a reboot, we cannot necessarily stop this
|
||||
* loop from consuming the next byte from standard in even if sbt has fully
|
||||
* rebooted and the byte will never be consumed. We try to fix this in withStreams
|
||||
* by setting the terminal to raw mode, which the input stream makes it non blocking,
|
||||
* but this approach only works on posix platforms.
|
||||
*/
|
||||
private[this] val runnable: Runnable = () => {
|
||||
@tailrec def impl(): Unit = {
|
||||
val _ = readQueue.take
|
||||
val b = in.read
|
||||
// The downstream consumer may have been interrupted. Buffer the result
|
||||
// when that hapens.
|
||||
waiting.poll match {
|
||||
case null => buffer.put(b)
|
||||
case q => q.put(b)
|
||||
}
|
||||
buffer.put(b)
|
||||
if (b != -1 && !Thread.interrupted()) impl()
|
||||
else closed.set(true)
|
||||
}
|
||||
|
|
@ -446,30 +427,28 @@ object Terminal {
|
|||
catch { case _: InterruptedException => closed.set(true) }
|
||||
}
|
||||
executor.submit(runnable)
|
||||
override def read(): Int =
|
||||
if (closed.get) -1
|
||||
else
|
||||
synchronized {
|
||||
def read(result: LinkedBlockingQueue[Integer]): Unit =
|
||||
if (!closed.get)
|
||||
readThread.synchronized {
|
||||
readThread.set(Thread.currentThread)
|
||||
try buffer.poll match {
|
||||
case null =>
|
||||
val result = new LinkedBlockingQueue[Integer]
|
||||
waiting.synchronized(waiting.put(result))
|
||||
readQueue.put(())
|
||||
try result.take.toInt
|
||||
catch {
|
||||
case e: InterruptedException =>
|
||||
waiting.remove(result)
|
||||
-1
|
||||
}
|
||||
result.put(buffer.take)
|
||||
case b if b == -1 => throw new ClosedChannelException
|
||||
case b => b.toInt
|
||||
case b => result.put(b)
|
||||
} finally readThread.set(null)
|
||||
}
|
||||
def cancel(): Unit = waiting.synchronized {
|
||||
override def read(): Int = {
|
||||
val result = new LinkedBlockingQueue[Integer]
|
||||
read(result)
|
||||
result.poll match {
|
||||
case null => -1
|
||||
case i => i.toInt
|
||||
}
|
||||
}
|
||||
def cancel(): Unit = readThread.synchronized {
|
||||
Option(readThread.getAndSet(null)).foreach(_.interrupt())
|
||||
waiting.forEach(_.put(-2))
|
||||
waiting.clear()
|
||||
readQueue.clear()
|
||||
}
|
||||
|
||||
|
|
@ -489,7 +468,7 @@ object Terminal {
|
|||
try {
|
||||
System.setOut(proxyPrintStream)
|
||||
System.setErr(proxyErrorStream)
|
||||
scala.Console.withErr(proxyErrorStream)(scala.Console.withOut(proxyOutputStream)(f))
|
||||
scala.Console.withErr(proxyErrorStream)(scala.Console.withOut(proxyPrintStream)(f))
|
||||
} finally {
|
||||
System.setOut(originalOut)
|
||||
System.setErr(originalErr)
|
||||
|
|
@ -520,7 +499,6 @@ object Terminal {
|
|||
* System.out through the terminal's input and output streams.
|
||||
*/
|
||||
private[this] val activeTerminal = new AtomicReference[Terminal](consoleTerminalHolder.get)
|
||||
jline.TerminalFactory.set(consoleTerminalHolder.get.toJLine)
|
||||
|
||||
/**
|
||||
* The boot input stream allows a remote client to forward input to the sbt process while
|
||||
|
|
@ -694,13 +672,13 @@ object Terminal {
|
|||
if (alive)
|
||||
try terminal.init()
|
||||
catch {
|
||||
case _: InterruptedException =>
|
||||
case _: InterruptedException | _: java.io.IOError =>
|
||||
}
|
||||
override def restore(): Unit =
|
||||
if (alive)
|
||||
try terminal.restore()
|
||||
catch {
|
||||
case _: InterruptedException =>
|
||||
case _: InterruptedException | _: java.io.IOError =>
|
||||
}
|
||||
override def reset(): Unit =
|
||||
try terminal.reset()
|
||||
|
|
@ -731,7 +709,11 @@ object Terminal {
|
|||
}
|
||||
term.restore()
|
||||
term.setEchoEnabled(true)
|
||||
new ConsoleTerminal(term, nonBlockingIn, originalOut)
|
||||
new ConsoleTerminal(
|
||||
term,
|
||||
if (System.console == null) nullWriteableInputStream else nonBlockingIn,
|
||||
originalOut
|
||||
)
|
||||
}
|
||||
|
||||
private[sbt] def reset(): Unit = {
|
||||
|
|
@ -774,19 +756,25 @@ object Terminal {
|
|||
|
||||
private val capabilityMap =
|
||||
org.jline.utils.InfoCmp.Capability.values().map(c => c.toString -> c).toMap
|
||||
private val consoleProgressState = new AtomicReference[ProgressState](new ProgressState(1))
|
||||
private[sbt] def setConsoleProgressState(progressState: ProgressState): Unit =
|
||||
consoleProgressState.set(progressState)
|
||||
|
||||
@deprecated("For compatibility only", "1.4.0")
|
||||
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
|
||||
private class ConsoleTerminal(
|
||||
val term: jline.Terminal with jline.Terminal2,
|
||||
in: InputStream,
|
||||
in: WriteableInputStream,
|
||||
out: OutputStream
|
||||
) extends TerminalImpl(in, out, originalErr, "console0") {
|
||||
private[util] lazy val system = JLine3.system
|
||||
private[this] def isCI = sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI")
|
||||
override def getWidth: Int = system.getSize.getColumns
|
||||
override def getHeight: Int = system.getSize.getRows
|
||||
override def isAnsiSupported: Boolean = term.isAnsiSupported && !isCI
|
||||
override private[sbt] def getSizeImpl: (Int, Int) = {
|
||||
val size = system.getSize
|
||||
(size.getColumns, size.getRows)
|
||||
}
|
||||
private[this] val isCI = sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI")
|
||||
override lazy val isAnsiSupported: Boolean = term.isAnsiSupported && !isCI
|
||||
override private[sbt] def progressState: ProgressState = consoleProgressState.get
|
||||
override def isEchoEnabled: Boolean = system.echo()
|
||||
override def isSuccessEnabled: Boolean = true
|
||||
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||
|
|
@ -801,7 +789,7 @@ object Terminal {
|
|||
override private[sbt] def restore(): Unit = term.restore()
|
||||
|
||||
override private[sbt] def getAttributes: Map[String, String] =
|
||||
JLine3.toMap(system.getAttributes)
|
||||
Try(JLine3.toMap(system.getAttributes)).getOrElse(Map.empty)
|
||||
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
|
||||
system.setAttributes(JLine3.attributesFromMap(attributes))
|
||||
override private[sbt] def setSize(width: Int, height: Int): Unit =
|
||||
|
|
@ -836,17 +824,26 @@ object Terminal {
|
|||
}
|
||||
}
|
||||
private[sbt] abstract class TerminalImpl private[sbt] (
|
||||
val in: InputStream,
|
||||
val in: WriteableInputStream,
|
||||
val out: OutputStream,
|
||||
override val errorStream: OutputStream,
|
||||
override private[sbt] val name: String
|
||||
) extends Terminal {
|
||||
private[sbt] def getSizeImpl: (Int, Int)
|
||||
private[this] val sizeRefreshPeriod = 1.second
|
||||
private[this] val size =
|
||||
new AtomicReference[((Int, Int), Deadline)](((1, 1), Deadline.now - 1.day))
|
||||
private[this] def setSize() = size.set((Try(getSizeImpl).getOrElse((1, 1)), Deadline.now))
|
||||
private[this] def getSize = size.get match {
|
||||
case (s, d) if (d + sizeRefreshPeriod).isOverdue =>
|
||||
setSize()
|
||||
size.get._1
|
||||
case (s, _) => s
|
||||
}
|
||||
override def getWidth: Int = getSize._1
|
||||
override def getHeight: Int = getSize._2
|
||||
private[this] val rawMode = new AtomicBoolean(false)
|
||||
private[this] val writeLock = new AnyRef
|
||||
private[this] val writeableInputStream = in match {
|
||||
case w: WriteableInputStream => w
|
||||
case _ => new WriteableInputStream(in, name)
|
||||
}
|
||||
def throwIfClosed[R](f: => R): R = if (isStopped.get) throw new ClosedChannelException else f
|
||||
override def getLastLine: Option[String] = progressState.currentLine
|
||||
override def getLines: Seq[String] = progressState.getLines
|
||||
|
|
@ -881,12 +878,13 @@ object Terminal {
|
|||
}
|
||||
override def flush(): Unit = combinedOutputStream.flush()
|
||||
}
|
||||
private def doWrite(bytes: Array[Byte]): Unit =
|
||||
progressState.write(TerminalImpl.this, bytes, rawPrintStream, hasProgress.get && !rawMode.get)
|
||||
private def doWrite(bytes: Array[Byte]): Unit = withPrintStream { ps =>
|
||||
progressState.write(TerminalImpl.this, bytes, ps, hasProgress.get && !rawMode.get)
|
||||
}
|
||||
override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream)
|
||||
override def inputStream: InputStream = writeableInputStream
|
||||
override def inputStream: InputStream = in
|
||||
|
||||
private[sbt] def write(bytes: Int*): Unit = writeableInputStream.write(bytes: _*)
|
||||
private[sbt] def write(bytes: Int*): Unit = in.write(bytes: _*)
|
||||
private[this] val isStopped = new AtomicBoolean(false)
|
||||
|
||||
override def getLineHeightAndWidth(line: String): (Int, Int) = getWidth match {
|
||||
|
|
@ -907,11 +905,19 @@ object Terminal {
|
|||
writeLock.synchronized(f(rawPrintStream))
|
||||
|
||||
override def close(): Unit = if (isStopped.compareAndSet(false, true)) {
|
||||
writeableInputStream.close()
|
||||
in.close()
|
||||
}
|
||||
}
|
||||
private lazy val nullInputStream: InputStream = () => {
|
||||
try this.synchronized(this.wait)
|
||||
catch { case _: InterruptedException => }
|
||||
-1
|
||||
}
|
||||
private lazy val nullWriteableInputStream =
|
||||
new WriteableInputStream(nullInputStream, "null-writeable-input-stream")
|
||||
private[sbt] val NullTerminal = new Terminal {
|
||||
override def close(): Unit = {}
|
||||
override private[sbt] def progressState: ProgressState = new ProgressState(1)
|
||||
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = false
|
||||
override def getHeight: Int = 0
|
||||
override def getLastLine: Option[String] = None
|
||||
|
|
@ -920,11 +926,7 @@ object Terminal {
|
|||
override def getNumericCapability(capability: String, jline3: Boolean): Integer = null
|
||||
override def getStringCapability(capability: String, jline3: Boolean): String = null
|
||||
override def getWidth: Int = 0
|
||||
override def inputStream: java.io.InputStream = () => {
|
||||
try this.synchronized(this.wait)
|
||||
catch { case _: InterruptedException => }
|
||||
-1
|
||||
}
|
||||
override def inputStream: java.io.InputStream = nullInputStream
|
||||
override def isAnsiSupported: Boolean = false
|
||||
override def isColorEnabled: Boolean = false
|
||||
override def isEchoEnabled: Boolean = false
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ package sbt
|
|||
import java.io.File
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import sbt.internal.inc.{ AnalyzingCompiler, PlainVirtualFile }
|
||||
import sbt.internal.util.Terminal
|
||||
import sbt.internal.util.{ DeprecatedJLine, Terminal }
|
||||
import sbt.util.Logger
|
||||
import xsbti.compile.{ Compilers, Inputs }
|
||||
|
||||
|
|
@ -67,6 +67,8 @@ final class Console(compiler: AnalyzingCompiler) {
|
|||
try {
|
||||
sys.props("scala.color") = if (terminal.isColorEnabled) "true" else "false"
|
||||
terminal.withRawOutput {
|
||||
jline.TerminalFactory.set(terminal.toJLine)
|
||||
DeprecatedJLine.setTerminalOverride(sbt.internal.util.JLine3(terminal))
|
||||
terminal.withRawInput(Run.executeTrapExit(console0, log))
|
||||
}
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -389,6 +389,10 @@ object State {
|
|||
s get BasicKeys.classLoaderCache getOrElse (throw new IllegalStateException(
|
||||
"Tried to get classloader cache for uninitialized state."
|
||||
))
|
||||
private[sbt] def extendedClassLoaderCache: ClassLoaderCache =
|
||||
s get BasicKeys.extendedClassLoaderCache getOrElse (throw new IllegalStateException(
|
||||
"Tried to get extended classloader cache for uninitialized state."
|
||||
))
|
||||
def initializeClassLoaderCache: State = {
|
||||
s.get(BasicKeys.extendedClassLoaderCache).foreach(_.close())
|
||||
val cache = newClassLoaderCache
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import java.io.File
|
|||
import java.lang.management.ManagementFactory
|
||||
import java.lang.ref.{ Reference, ReferenceQueue, SoftReference }
|
||||
import java.net.URLClassLoader
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.concurrent.atomic.{ AtomicInteger, AtomicReference }
|
||||
|
||||
import sbt.internal.inc.classpath.{
|
||||
AbstractClassLoaderCache,
|
||||
|
|
@ -30,9 +30,12 @@ private object ClassLoaderCache {
|
|||
private def threadID = new AtomicInteger(0)
|
||||
}
|
||||
private[sbt] class ClassLoaderCache(
|
||||
override val commonParent: ClassLoader,
|
||||
val parent: ClassLoader,
|
||||
private val miniProvider: Option[(File, ClassLoader)]
|
||||
) extends AbstractClassLoaderCache {
|
||||
private[this] val parentHolder = new AtomicReference(parent)
|
||||
def commonParent = parentHolder.get()
|
||||
def setParent(parent: ClassLoader): Unit = parentHolder.set(parent)
|
||||
def this(commonParent: ClassLoader) = this(commonParent, None)
|
||||
def this(scalaProvider: ScalaProvider) =
|
||||
this(scalaProvider.launcher.topLoader, {
|
||||
|
|
@ -51,8 +54,9 @@ private[sbt] class ClassLoaderCache(
|
|||
}
|
||||
}
|
||||
private class Key(val fileStamps: Seq[(File, Long)], val parent: ClassLoader) {
|
||||
def this(files: List[File]) =
|
||||
this(files.map(f => f -> IO.getModifiedTimeOrZero(f)), commonParent)
|
||||
def this(files: List[File], parent: ClassLoader) =
|
||||
this(files.map(f => f -> IO.getModifiedTimeOrZero(f)), parent)
|
||||
def this(files: List[File]) = this(files, commonParent)
|
||||
lazy val files: Seq[File] = fileStamps.map(_._1)
|
||||
lazy val maxStamp: Long = fileStamps.maxBy(_._2)._2
|
||||
class CachedClassLoader
|
||||
|
|
@ -169,10 +173,19 @@ private[sbt] class ClassLoaderCache(
|
|||
val key = new Key(files, parent)
|
||||
get(key, mkLoader)
|
||||
}
|
||||
override def apply(files: List[File]): ClassLoader = {
|
||||
val key = new Key(files)
|
||||
def apply(files: List[File], parent: ClassLoader): ClassLoader = {
|
||||
val key = new Key(files, parent)
|
||||
get(key, () => key.toClassLoader)
|
||||
}
|
||||
override def apply(files: List[File]): ClassLoader = {
|
||||
files match {
|
||||
case d :: s :: Nil if d.getName.startsWith("dotty-library") =>
|
||||
apply(files, classOf[org.jline.terminal.Terminal].getClassLoader)
|
||||
case _ =>
|
||||
val key = new Key(files)
|
||||
get(key, () => key.toClassLoader)
|
||||
}
|
||||
}
|
||||
override def cachedCustomClassloader(
|
||||
files: List[File],
|
||||
mkLoader: () => ClassLoader
|
||||
|
|
|
|||
|
|
@ -47,11 +47,12 @@ import Serialization.{
|
|||
systemErrFlush,
|
||||
terminalCapabilities,
|
||||
terminalCapabilitiesResponse,
|
||||
terminalGetSize,
|
||||
terminalPropertiesQuery,
|
||||
terminalPropertiesResponse,
|
||||
terminalSetSize,
|
||||
getTerminalAttributes,
|
||||
setTerminalAttributes,
|
||||
setTerminalSize,
|
||||
}
|
||||
import NetworkClient.Arguments
|
||||
|
||||
|
|
@ -657,7 +658,13 @@ class NetworkClient(
|
|||
cchars = attrs.getOrElse("cchars", ""),
|
||||
)
|
||||
sendCommandResponse("", response, msg.id)
|
||||
case (`setTerminalSize`, Some(json)) =>
|
||||
case (`terminalGetSize`, _) =>
|
||||
val response = TerminalGetSizeResponse(
|
||||
Terminal.console.getWidth,
|
||||
Terminal.console.getHeight,
|
||||
)
|
||||
sendCommandResponse("", response, msg.id)
|
||||
case (`terminalSetSize`, Some(json)) =>
|
||||
Converter.fromJson[TerminalSetSizeCommand](json) match {
|
||||
case Success(size) =>
|
||||
Terminal.console.setSize(size.width, size.height)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import java.io.File
|
|||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
//import jline.console.history.PersistentHistory
|
||||
import sbt.BasicCommandStrings.{ Cancel, TerminateAction, Shutdown }
|
||||
import sbt.BasicKeys.{ historyPath, terminalShellPrompt }
|
||||
import sbt.State
|
||||
|
|
@ -23,55 +22,71 @@ import sbt.internal.util.complete.{ Parser }
|
|||
import scala.annotation.tailrec
|
||||
|
||||
private[sbt] trait UITask extends Runnable with AutoCloseable {
|
||||
private[sbt] def channel: CommandChannel
|
||||
private[sbt] def reader: UITask.Reader
|
||||
private[sbt] val channel: CommandChannel
|
||||
private[sbt] val reader: UITask.Reader
|
||||
private[this] final def handleInput(s: Either[String, String]): Boolean = s match {
|
||||
case Left(m) => channel.onFastTrackTask(m)
|
||||
case Right(cmd) => channel.onCommand(cmd)
|
||||
}
|
||||
private[this] val isStopped = new AtomicBoolean(false)
|
||||
override def run(): Unit = {
|
||||
@tailrec def impl(): Unit = {
|
||||
@tailrec def impl(): Unit = if (!isStopped.get) {
|
||||
val res = reader.readLine()
|
||||
if (!handleInput(res) && !isStopped.get) impl()
|
||||
}
|
||||
try impl()
|
||||
catch { case _: InterruptedException | _: ClosedChannelException => isStopped.set(true) }
|
||||
}
|
||||
override def close(): Unit = isStopped.set(true)
|
||||
override def close(): Unit = {
|
||||
isStopped.set(true)
|
||||
reader.close()
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] object UITask {
|
||||
trait Reader { def readLine(): Either[String, String] }
|
||||
trait Reader extends AutoCloseable {
|
||||
def readLine(): Either[String, String]
|
||||
override def close(): Unit = {}
|
||||
}
|
||||
object Reader {
|
||||
// Avoid filling the stack trace since it isn't helpful here
|
||||
object interrupted extends InterruptedException
|
||||
def terminalReader(parser: Parser[_])(
|
||||
terminal: Terminal,
|
||||
state: State
|
||||
): Reader = { () =>
|
||||
try {
|
||||
val clear = terminal.ansi(ClearPromptLine, "")
|
||||
@tailrec def impl(): Either[String, String] = {
|
||||
val reader = LineReader.createReader(history(state), parser, terminal, terminal.prompt)
|
||||
(try reader.readLine(clear + terminal.prompt.mkPrompt())
|
||||
finally reader.close) match {
|
||||
case None if terminal == Terminal.console && System.console == null =>
|
||||
// No stdin is attached to the process so just ignore the result and
|
||||
// block until the thread is interrupted.
|
||||
this.synchronized(this.wait())
|
||||
Right("") // should be unreachable
|
||||
// JLine returns null on ctrl+d when there is no other input. This interprets
|
||||
// ctrl+d with no imput as an exit
|
||||
case None => Left(TerminateAction)
|
||||
case Some(s: String) =>
|
||||
s.trim() match {
|
||||
case "" => impl()
|
||||
case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd)
|
||||
case cmd => Right(cmd)
|
||||
}
|
||||
): Reader = new Reader {
|
||||
val closed = new AtomicBoolean(false)
|
||||
def readLine(): Either[String, String] =
|
||||
try {
|
||||
val clear = terminal.ansi(ClearPromptLine, "")
|
||||
@tailrec def impl(): Either[String, String] = {
|
||||
val thread = Thread.currentThread
|
||||
if (thread.isInterrupted || closed.get) throw interrupted
|
||||
val reader = LineReader.createReader(history(state), parser, terminal)
|
||||
if (thread.isInterrupted || closed.get) throw interrupted
|
||||
(try reader.readLine(clear + terminal.prompt.mkPrompt())
|
||||
finally reader.close) match {
|
||||
case None if terminal == Terminal.console && System.console == null =>
|
||||
// No stdin is attached to the process so just ignore the result and
|
||||
// block until the thread is interrupted.
|
||||
this.synchronized(this.wait())
|
||||
Right("") // should be unreachable
|
||||
// JLine returns null on ctrl+d when there is no other input. This interprets
|
||||
// ctrl+d with no imput as an exit
|
||||
case None => Left(TerminateAction)
|
||||
case Some(s: String) =>
|
||||
s.trim() match {
|
||||
case "" => impl()
|
||||
case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd)
|
||||
case cmd => Right(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl()
|
||||
} catch { case e: InterruptedException => Right("") }
|
||||
val res = impl()
|
||||
terminal.setPrompt(Prompt.Pending)
|
||||
res
|
||||
} catch { case e: InterruptedException => Right("") }
|
||||
override def close(): Unit = closed.set(true)
|
||||
}
|
||||
}
|
||||
private[this] def history(s: State): Option[File] =
|
||||
|
|
@ -87,7 +102,7 @@ private[sbt] object UITask {
|
|||
state: State,
|
||||
override val channel: CommandChannel,
|
||||
) extends UITask {
|
||||
override private[sbt] def reader: UITask.Reader = {
|
||||
override private[sbt] lazy val reader: UITask.Reader = {
|
||||
UITask.Reader.terminalReader(state.combinedParser)(channel.terminal, state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, Running }
|
||||
import sbt.internal.util.Prompt
|
||||
|
||||
private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable {
|
||||
private[this] val uiThread = new AtomicReference[(UITask, Thread)]
|
||||
|
|
@ -31,15 +31,21 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable
|
|||
uiThread.synchronized {
|
||||
val task = channel.makeUIThread(state)
|
||||
def submit(): Thread = {
|
||||
val thread = new Thread(() => {
|
||||
task.run()
|
||||
uiThread.set(null)
|
||||
}, s"sbt-$name-ui-thread")
|
||||
thread.setDaemon(true)
|
||||
thread.start()
|
||||
val thread: Thread = new Thread(s"sbt-$name-ui-thread") {
|
||||
setDaemon(true)
|
||||
override def run(): Unit =
|
||||
try task.run()
|
||||
finally uiThread.get match {
|
||||
case (_, t) if t == this => uiThread.set(null)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
uiThread.getAndSet((task, thread)) match {
|
||||
case null =>
|
||||
case (_, t) => t.interrupt()
|
||||
case null => thread.start()
|
||||
case (task, t) if t.getClass != task.getClass =>
|
||||
stopThreadImpl()
|
||||
thread.start()
|
||||
case t => uiThread.set(t)
|
||||
}
|
||||
thread
|
||||
}
|
||||
|
|
@ -47,39 +53,52 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable
|
|||
case null => uiThread.set((task, submit()))
|
||||
case (t, _) if t.getClass == task.getClass =>
|
||||
case (t, thread) =>
|
||||
thread.interrupt()
|
||||
stopThreadImpl()
|
||||
uiThread.set((task, submit()))
|
||||
}
|
||||
}
|
||||
Option(lastProgressEvent.get).foreach(onProgressEvent)
|
||||
}
|
||||
|
||||
private[sbt] def stopThread(): Unit = uiThread.synchronized {
|
||||
private[sbt] def stopThreadImpl(): Unit = uiThread.synchronized {
|
||||
uiThread.getAndSet(null) match {
|
||||
case null =>
|
||||
case (t, thread) =>
|
||||
t.close()
|
||||
Util.ignoreResult(thread.interrupt())
|
||||
}
|
||||
}
|
||||
try thread.join(1000)
|
||||
catch { case _: InterruptedException => }
|
||||
|
||||
private[sbt] def onConsolePromptEvent(consolePromptEvent: ConsolePromptEvent): Unit = {
|
||||
channel.terminal.withPrintStream { ps =>
|
||||
ps.print(ConsoleAppender.ClearScreenAfterCursor)
|
||||
ps.flush()
|
||||
// This join should always work, but if it doesn't log an error because
|
||||
// it can cause problems if the thread isn't joined
|
||||
if (thread.isAlive) System.err.println(s"Unable to join thread $thread")
|
||||
()
|
||||
}
|
||||
val state = consolePromptEvent.state
|
||||
terminal.prompt match {
|
||||
case Running => terminal.setPrompt(AskUser(() => UITask.shellPrompt(terminal, state)))
|
||||
case _ =>
|
||||
}
|
||||
onProgressEvent(ProgressEvent("Info", Vector(), None, None, None))
|
||||
reset(state)
|
||||
}
|
||||
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 {
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
class JLineLoader extends URLClassLoader {
|
||||
JLineLoader(final URL[] urls, final ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
result.append("JLineLoader(");
|
||||
final URL[] urls = getURLs();
|
||||
for (int i = 0; i < urls.length; ++i) {
|
||||
result.append(urls[i].toString());
|
||||
if (i < urls.length - 1) result.append(", ");
|
||||
}
|
||||
result.append(")");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,16 +22,19 @@ public final class MetaBuildLoader extends URLClassLoader {
|
|||
private final URLClassLoader fullScalaLoader;
|
||||
private final URLClassLoader libraryLoader;
|
||||
private final URLClassLoader interfaceLoader;
|
||||
private final URLClassLoader jlineLoader;
|
||||
|
||||
MetaBuildLoader(
|
||||
final URL[] urls,
|
||||
final URLClassLoader fullScalaLoader,
|
||||
final URLClassLoader libraryLoader,
|
||||
final URLClassLoader interfaceLoader) {
|
||||
final URLClassLoader interfaceLoader,
|
||||
final URLClassLoader jlineLoader) {
|
||||
super(urls, fullScalaLoader);
|
||||
this.fullScalaLoader = fullScalaLoader;
|
||||
this.libraryLoader = libraryLoader;
|
||||
this.interfaceLoader = interfaceLoader;
|
||||
this.jlineLoader = jlineLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -45,6 +48,7 @@ public final class MetaBuildLoader extends URLClassLoader {
|
|||
fullScalaLoader.close();
|
||||
libraryLoader.close();
|
||||
interfaceLoader.close();
|
||||
jlineLoader.close();
|
||||
}
|
||||
|
||||
static {
|
||||
|
|
@ -61,20 +65,26 @@ public final class MetaBuildLoader extends URLClassLoader {
|
|||
*/
|
||||
public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException {
|
||||
final Pattern pattern =
|
||||
Pattern.compile("^(test-interface-[0-9.]+|jline-[0-9.]+-sbt-.*|jansi-[0-9.]+)\\.jar");
|
||||
Pattern.compile(
|
||||
"^(test-interface-[0-9.]+|jline-(terminal-)?[0-9.]+-sbt-.*|jansi-[0-9.]+)\\.jar");
|
||||
final File[] cp = appProvider.mainClasspath();
|
||||
final URL[] interfaceURLs = new URL[3];
|
||||
final URL[] interfaceURLs = new URL[1];
|
||||
final URL[] jlineURLs = new URL[3];
|
||||
final File[] extra =
|
||||
appProvider.id().classpathExtra() == null ? new File[0] : appProvider.id().classpathExtra();
|
||||
final Set<File> bottomClasspath = new LinkedHashSet<>();
|
||||
|
||||
{
|
||||
int interfaceIndex = 0;
|
||||
int jlineIndex = 0;
|
||||
for (final File file : cp) {
|
||||
final String name = file.getName();
|
||||
if (pattern.matcher(name).find()) {
|
||||
if (name.contains("test-interface") && pattern.matcher(name).find()) {
|
||||
interfaceURLs[interfaceIndex] = file.toURI().toURL();
|
||||
interfaceIndex += 1;
|
||||
} else if (pattern.matcher(name).find()) {
|
||||
jlineURLs[jlineIndex] = file.toURI().toURL();
|
||||
jlineIndex += 1;
|
||||
} else {
|
||||
bottomClasspath.add(file);
|
||||
}
|
||||
|
|
@ -108,6 +118,7 @@ public final class MetaBuildLoader extends URLClassLoader {
|
|||
if (topLoader == null) topLoader = scalaProvider.launcher().topLoader();
|
||||
|
||||
final TestInterfaceLoader interfaceLoader = new TestInterfaceLoader(interfaceURLs, topLoader);
|
||||
final JLineLoader jlineLoader = new JLineLoader(jlineURLs, interfaceLoader);
|
||||
final File[] siJars = scalaProvider.jars();
|
||||
final URL[] lib = new URL[1];
|
||||
int scalaRestCount = siJars.length - 1;
|
||||
|
|
@ -131,8 +142,8 @@ public final class MetaBuildLoader extends URLClassLoader {
|
|||
}
|
||||
}
|
||||
assert lib[0] != null : "no scala-library.jar";
|
||||
final ScalaLibraryClassLoader libraryLoader = new ScalaLibraryClassLoader(lib, interfaceLoader);
|
||||
final ScalaLibraryClassLoader libraryLoader = new ScalaLibraryClassLoader(lib, jlineLoader);
|
||||
final FullScalaLoader fullScalaLoader = new FullScalaLoader(scalaRest, libraryLoader);
|
||||
return new MetaBuildLoader(rest, fullScalaLoader, libraryLoader, interfaceLoader);
|
||||
return new MetaBuildLoader(rest, fullScalaLoader, libraryLoader, interfaceLoader, jlineLoader);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
package sbt
|
||||
|
||||
import java.io.{ File, PrintWriter }
|
||||
import java.net.{ URI, URL, URLClassLoader }
|
||||
import java.net.{ URI, URL }
|
||||
import java.nio.file.{ Paths, Path => NioPath }
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
|
@ -34,9 +34,8 @@ import sbt.Scope.{ GlobalScope, ThisScope, fillTaskAxis }
|
|||
import sbt.coursierint._
|
||||
import sbt.internal.CommandStrings.ExportStream
|
||||
import sbt.internal._
|
||||
import sbt.internal.classpath.AlternativeZincUtil
|
||||
import sbt.internal.classpath.{ AlternativeZincUtil, ClassLoaderCache }
|
||||
import sbt.internal.inc.JavaInterfaceUtil._
|
||||
import sbt.internal.inc.classpath.{ ClassLoaderCache, ClasspathFilter, ClasspathUtil }
|
||||
import sbt.internal.inc.{
|
||||
CompileOutput,
|
||||
MappedFileConverter,
|
||||
|
|
@ -45,6 +44,8 @@ import sbt.internal.inc.{
|
|||
ZincLmUtil,
|
||||
ZincUtil
|
||||
}
|
||||
import sbt.internal.inc.classpath.{ ClasspathFilter, ClasspathUtil }
|
||||
import sbt.internal.inc.{ MappedFileConverter, PlainVirtualFile, Stamps, ZincLmUtil, ZincUtil }
|
||||
import sbt.internal.io.{ Source, WatchState }
|
||||
import sbt.internal.librarymanagement.mavenint.{
|
||||
PomExtraDependencyAttributes,
|
||||
|
|
@ -96,7 +97,7 @@ import sjsonnew._
|
|||
import sjsonnew.support.scalajson.unsafe.Converter
|
||||
|
||||
import scala.collection.immutable.ListMap
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.control.NonFatal
|
||||
import scala.xml.NodeSeq
|
||||
|
||||
|
|
@ -386,12 +387,21 @@ object Defaults extends BuildCommon {
|
|||
},
|
||||
turbo :== SysProp.turbo,
|
||||
usePipelining :== SysProp.pipelining,
|
||||
useScalaReplJLine :== false,
|
||||
scalaInstanceTopLoader := {
|
||||
if (!useScalaReplJLine.value) classOf[org.jline.terminal.Terminal].getClassLoader
|
||||
else appConfiguration.value.provider.scalaProvider.launcher.topLoader.getParent
|
||||
},
|
||||
useSuperShell := { if (insideCI.value) false else Terminal.console.isSupershellEnabled },
|
||||
superShellThreshold :== SysProp.supershellThreshold,
|
||||
superShellMaxTasks :== SysProp.supershellMaxTasks,
|
||||
superShellSleep :== SysProp.supershellSleep.millis,
|
||||
progressReports := {
|
||||
val rs = EvaluateTask.taskTimingProgress.toVector ++ EvaluateTask.taskTraceEvent.toVector
|
||||
rs map { Keys.TaskProgress(_) }
|
||||
},
|
||||
progressState := Some(new ProgressState(SysProp.supershellBlankZone)),
|
||||
// progressState is deprecated
|
||||
SettingKey[Option[ProgressState]]("progressState") := None,
|
||||
Previous.cache := new Previous(
|
||||
Def.streamsManagerKey.value,
|
||||
Previous.references.value.getReferences
|
||||
|
|
@ -888,8 +898,15 @@ object Defaults extends BuildCommon {
|
|||
val libraryJars = allJars.filter(_.getName == "scala-library.jar")
|
||||
allJars.filter(_.getName == "scala-compiler.jar") match {
|
||||
case Array(compilerJar) if libraryJars.nonEmpty =>
|
||||
val cache = state.value.classLoaderCache
|
||||
mkScalaInstance(version, allJars, libraryJars, compilerJar, cache)
|
||||
val cache = state.value.extendedClassLoaderCache
|
||||
mkScalaInstance(
|
||||
version,
|
||||
allJars,
|
||||
libraryJars,
|
||||
compilerJar,
|
||||
cache,
|
||||
scalaInstanceTopLoader.value
|
||||
)
|
||||
case _ => ScalaInstance(version, scalaProvider)
|
||||
}
|
||||
} else
|
||||
|
|
@ -931,7 +948,8 @@ object Defaults extends BuildCommon {
|
|||
allJars,
|
||||
Array(libraryJar),
|
||||
compilerJar,
|
||||
state.value.classLoaderCache
|
||||
state.value.extendedClassLoaderCache,
|
||||
scalaInstanceTopLoader.value,
|
||||
)
|
||||
}
|
||||
private[this] def mkScalaInstance(
|
||||
|
|
@ -940,15 +958,11 @@ object Defaults extends BuildCommon {
|
|||
libraryJars: Array[File],
|
||||
compilerJar: File,
|
||||
classLoaderCache: ClassLoaderCache,
|
||||
topLoader: ClassLoader,
|
||||
): ScalaInstance = {
|
||||
val allJarsDistinct = allJars.distinct
|
||||
val libraryLoader = classLoaderCache(libraryJars.toList)
|
||||
class ScalaLoader
|
||||
extends URLClassLoader(allJarsDistinct.map(_.toURI.toURL).toArray, libraryLoader)
|
||||
val fullLoader = classLoaderCache.cachedCustomClassloader(
|
||||
allJarsDistinct.toList,
|
||||
() => new ScalaLoader
|
||||
)
|
||||
val libraryLoader = classLoaderCache(libraryJars.toList, topLoader)
|
||||
val fullLoader = classLoaderCache(allJarsDistinct.toList, libraryLoader)
|
||||
new ScalaInstance(
|
||||
version,
|
||||
fullLoader,
|
||||
|
|
@ -970,7 +984,8 @@ object Defaults extends BuildCommon {
|
|||
dummy.allJars,
|
||||
dummy.libraryJars,
|
||||
dummy.compilerJar,
|
||||
state.value.classLoaderCache
|
||||
state.value.extendedClassLoaderCache,
|
||||
scalaInstanceTopLoader.value,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ object EvaluateTask {
|
|||
extracted,
|
||||
structure
|
||||
)
|
||||
val reporters = maker.map(_.progress) ++ Some(TaskProgress) ++
|
||||
val reporters = maker.map(_.progress) ++ state.get(Keys.taskProgress) ++
|
||||
(if (SysProp.taskTimings)
|
||||
new TaskTimings(reportOnShutdown = false, state.globalLogging.full) :: Nil
|
||||
else Nil)
|
||||
|
|
|
|||
|
|
@ -555,10 +555,15 @@ object Keys {
|
|||
def apply(progress: ExecuteProgress[Task]): TaskProgress = new TaskProgress(progress)
|
||||
}
|
||||
private[sbt] val currentTaskProgress = AttributeKey[TaskProgress]("current-task-progress")
|
||||
private[sbt] val taskProgress = AttributeKey[sbt.internal.TaskProgress]("active-task-progress")
|
||||
val useSuperShell = settingKey[Boolean]("Enables (true) or disables the super shell.")
|
||||
val superShellMaxTasks = settingKey[Int]("The max number of tasks to display in the supershell progress report")
|
||||
val superShellSleep = settingKey[FiniteDuration]("The minimum duration to sleep between progress reports")
|
||||
val superShellThreshold = settingKey[FiniteDuration]("The minimum amount of time a task must be running to appear in the supershell progress report")
|
||||
val turbo = settingKey[Boolean]("Enables (true) or disables optional performance features.")
|
||||
// This key can be used to add custom ExecuteProgress instances
|
||||
val progressReports = settingKey[Seq[TaskProgress]]("A function that returns a list of progress reporters.").withRank(DTask)
|
||||
@deprecated("unused", "1.4.0")
|
||||
private[sbt] val progressState = settingKey[Option[ProgressState]]("The optional progress state if supershell is enabled.").withRank(Invisible)
|
||||
private[sbt] val postProgressReports = settingKey[Unit]("Internally used to modify logger.").withRank(DTask)
|
||||
@deprecated("No longer used", "1.3.0")
|
||||
|
|
@ -570,6 +575,9 @@ object Keys {
|
|||
val includeLintKeys = settingKey[Set[Def.KeyedInitialize[_]]]("Task keys that are included into lintUnused task")
|
||||
val lintUnusedKeysOnLoad = settingKey[Boolean]("Toggles whether or not to check for unused keys during startup")
|
||||
|
||||
val useScalaReplJLine = settingKey[Boolean]("Toggles whether or not to use sbt's forked jline in the scala repl. Enabling this flag may break the thin client in the scala console.").withRank(KeyRanks.Invisible)
|
||||
val scalaInstanceTopLoader = settingKey[ClassLoader]("The top classloader for the scala instance").withRank(KeyRanks.Invisible)
|
||||
|
||||
val stateStreams = AttributeKey[Streams]("stateStreams", "Streams manager, which provides streams for different contexts. Setting this on State will override the default Streams implementation.")
|
||||
val resolvedScoped = Def.resolvedScoped
|
||||
val pluginData = taskKey[PluginData]("Information from the plugin build needed in the main build definition.").withRank(DTask)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import java.util.Properties
|
|||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import sbt.BasicCommandStrings.{ Shell, Shutdown, TemplateCommand, networkExecPrefix }
|
||||
import sbt.BasicCommandStrings.{ Shell, Shutdown, TemplateCommand }
|
||||
import sbt.Project.LoadAction
|
||||
import sbt.compiler.EvalImports
|
||||
import sbt.internal.Aggregation.AnyKeys
|
||||
|
|
@ -932,13 +932,28 @@ object BuiltinCommands {
|
|||
val s3 = addCacheStoreFactoryFactory(Project.setProject(session, structure, s2))
|
||||
val s4 = s3.put(Keys.useLog4J.key, Project.extract(s3).get(Keys.useLog4J))
|
||||
val s5 = setupGlobalFileTreeRepository(s4)
|
||||
CheckBuildSources.init(LintUnused.lintUnusedFunc(s5))
|
||||
// This is a workaround for the console task in dotty which uses the classloader cache.
|
||||
// We need to override the top loader in that case so that it gets the forked jline.
|
||||
s5.extendedClassLoaderCache.setParent(Project.extract(s5).get(Keys.scalaInstanceTopLoader))
|
||||
addSuperShellParams(CheckBuildSources.init(LintUnused.lintUnusedFunc(s5)))
|
||||
}
|
||||
|
||||
private val setupGlobalFileTreeRepository: State => State = { state =>
|
||||
state.get(sbt.nio.Keys.globalFileTreeRepository).foreach(_.close())
|
||||
state.put(sbt.nio.Keys.globalFileTreeRepository, FileTreeRepository.default)
|
||||
}
|
||||
private val addSuperShellParams: State => State = (s: State) => {
|
||||
val extracted = Project.extract(s)
|
||||
import scala.concurrent.duration._
|
||||
val sleep = extracted.getOpt(Keys.superShellSleep).getOrElse(SysProp.supershellSleep.millis)
|
||||
val threshold =
|
||||
extracted.getOpt(Keys.superShellThreshold).getOrElse(SysProp.supershellThreshold)
|
||||
val maxItems = extracted.getOpt(Keys.superShellMaxTasks).getOrElse(SysProp.supershellMaxTasks)
|
||||
Terminal.setConsoleProgressState(new ProgressState(1, maxItems))
|
||||
s.put(Keys.superShellSleep.key, sleep)
|
||||
.put(Keys.superShellThreshold.key, threshold)
|
||||
.put(Keys.superShellMaxTasks.key, maxItems)
|
||||
}
|
||||
private val addCacheStoreFactoryFactory: State => State = (s: State) => {
|
||||
val size = Project
|
||||
.extract(s)
|
||||
|
|
@ -996,13 +1011,7 @@ object BuiltinCommands {
|
|||
}
|
||||
|
||||
private def getExec(state: State, interval: Duration): Exec = {
|
||||
val exec: Exec =
|
||||
StandardMain.exchange.blockUntilNextExec(interval, Some(state), state.globalLogging.full)
|
||||
if (exec.source.fold(true)(_.channelName != ConsoleChannel.defaultName) &&
|
||||
!exec.commandLine.startsWith(networkExecPrefix)) {
|
||||
Terminal.consoleLog(s"received remote command: ${exec.commandLine}")
|
||||
}
|
||||
exec
|
||||
StandardMain.exchange.blockUntilNextExec(interval, Some(state), state.globalLogging.full)
|
||||
}
|
||||
|
||||
def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 =>
|
||||
|
|
|
|||
|
|
@ -15,15 +15,17 @@ import sbt.internal.ShutdownHooks
|
|||
import sbt.internal.langserver.ErrorCodes
|
||||
import sbt.internal.protocol.JsonRpcResponseError
|
||||
import sbt.internal.nio.CheckBuildSources.CheckBuildSourcesKey
|
||||
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking, Terminal }
|
||||
import sbt.internal.{ ConsoleUnpromptEvent, ShutdownHooks }
|
||||
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking, Prompt, Terminal }
|
||||
import sbt.internal.{ ShutdownHooks, TaskProgress }
|
||||
import sbt.io.{ IO, Using }
|
||||
import sbt.protocol._
|
||||
import sbt.util.{ Logger, LoggerContext }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.control.NonFatal
|
||||
import sbt.internal.FastTrackCommands
|
||||
import sbt.internal.SysProp
|
||||
|
||||
object MainLoop {
|
||||
|
||||
|
|
@ -150,9 +152,17 @@ object MainLoop {
|
|||
|
||||
def next(state: State): State = {
|
||||
val context = LoggerContext(useLog4J = state.get(Keys.useLog4J.key).getOrElse(false))
|
||||
val superShellSleep =
|
||||
state.get(Keys.superShellSleep.key).getOrElse(SysProp.supershellSleep.millis)
|
||||
val superShellThreshold =
|
||||
state.get(Keys.superShellThreshold.key).getOrElse(SysProp.supershellThreshold)
|
||||
val taskProgress = new TaskProgress(superShellSleep, superShellThreshold)
|
||||
try {
|
||||
ErrorHandling.wideConvert {
|
||||
state.put(Keys.loggerContext, context).process(processCommand)
|
||||
state
|
||||
.put(Keys.loggerContext, context)
|
||||
.put(Keys.taskProgress, taskProgress)
|
||||
.process(processCommand)
|
||||
} match {
|
||||
case Right(s) => s.remove(Keys.loggerContext)
|
||||
case Left(t: xsbti.FullReload) => throw t
|
||||
|
|
@ -186,7 +196,10 @@ object MainLoop {
|
|||
state.log.error(msg)
|
||||
state.log.error("\n")
|
||||
state.handleError(oom)
|
||||
} finally context.close()
|
||||
} finally {
|
||||
context.close()
|
||||
taskProgress.close()
|
||||
}
|
||||
}
|
||||
|
||||
/** This is the main function State transfer function of the sbt command processing. */
|
||||
|
|
@ -206,14 +219,18 @@ object MainLoop {
|
|||
state.put(sbt.Keys.currentTaskProgress, new Keys.TaskProgress(progress))
|
||||
} else state
|
||||
}
|
||||
StandardMain.exchange.setState(progressState)
|
||||
StandardMain.exchange.setExec(Some(exec))
|
||||
StandardMain.exchange.unprompt(ConsoleUnpromptEvent(exec.source))
|
||||
exchange.setState(progressState)
|
||||
exchange.setExec(Some(exec))
|
||||
val restoreTerminal = channelName.flatMap(exchange.channelForName) match {
|
||||
case Some(c) =>
|
||||
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)
|
||||
c.terminal.setPrompt(prevPrompt)
|
||||
c.terminal.flush()
|
||||
}
|
||||
case _ => () => ()
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ package sbt
|
|||
package internal
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import scala.collection.concurrent.TrieMap
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
import scala.collection.immutable.VectorBuilder
|
||||
import scala.concurrent.duration._
|
||||
|
||||
private[sbt] abstract class AbstractTaskExecuteProgress extends ExecuteProgress[Task] {
|
||||
import AbstractTaskExecuteProgress.Timer
|
||||
|
|
@ -18,10 +21,51 @@ private[sbt] abstract class AbstractTaskExecuteProgress extends ExecuteProgress[
|
|||
private[this] val showScopedKey = Def.showShortKey(None)
|
||||
private[this] val anonOwners = new ConcurrentHashMap[Task[_], Task[_]]
|
||||
private[this] val calledBy = new ConcurrentHashMap[Task[_], Task[_]]
|
||||
private[this] val activeTasksMap = new ConcurrentHashMap[Task[_], Unit]
|
||||
protected val timings = new ConcurrentHashMap[Task[_], Timer]
|
||||
private[this] val timings = new ConcurrentHashMap[Task[_], Timer]
|
||||
private[sbt] def timingsByName: mutable.Map[String, AtomicLong] = {
|
||||
val result = new ConcurrentHashMap[String, AtomicLong]
|
||||
timings.forEach { (task, timing) =>
|
||||
val duration = timing.durationNanos
|
||||
result.putIfAbsent(taskName(task), new AtomicLong(duration)) match {
|
||||
case null =>
|
||||
case t => t.getAndAdd(duration); ()
|
||||
}
|
||||
}
|
||||
result.asScala
|
||||
}
|
||||
private[sbt] def anyTimings = !timings.isEmpty
|
||||
def currentTimings: Iterator[(Task[_], Timer)] = timings.asScala.iterator
|
||||
|
||||
def activeTasks: Set[Task[_]] = activeTasksMap.keySet.asScala.toSet
|
||||
private[internal] def exceededThreshold(task: Task[_], threshold: FiniteDuration): Boolean =
|
||||
timings.get(task) match {
|
||||
case null => false
|
||||
case t => t.durationMicros > threshold.toMicros
|
||||
}
|
||||
private[internal] def timings(
|
||||
tasks: java.util.Set[Task[_]],
|
||||
thresholdMicros: Long
|
||||
): Vector[(Task[_], Long)] = {
|
||||
val result = new VectorBuilder[(Task[_], Long)]
|
||||
val now = System.nanoTime
|
||||
tasks.forEach { t =>
|
||||
timings.get(t) match {
|
||||
case null =>
|
||||
case timing =>
|
||||
if (timing.isActive) {
|
||||
val elapsed = (now - timing.startNanos) / 1000
|
||||
if (elapsed > thresholdMicros) result += t -> elapsed
|
||||
}
|
||||
}
|
||||
}
|
||||
result.result()
|
||||
}
|
||||
def activeTasks(now: Long) = {
|
||||
val result = new VectorBuilder[(Task[_], FiniteDuration)]
|
||||
timings.forEach { (task, timing) =>
|
||||
if (timing.isActive) result += task -> (now - timing.startNanos).nanos
|
||||
}
|
||||
result.result
|
||||
}
|
||||
|
||||
override def afterRegistered(
|
||||
task: Task[_],
|
||||
|
|
@ -38,15 +82,17 @@ private[sbt] abstract class AbstractTaskExecuteProgress extends ExecuteProgress[
|
|||
|
||||
override def beforeWork(task: Task[_]): Unit = {
|
||||
timings.put(task, new Timer)
|
||||
activeTasksMap.put(task, ())
|
||||
()
|
||||
}
|
||||
|
||||
protected def clearTimings: Boolean = false
|
||||
override def afterWork[A](task: Task[A], result: Either[Task[A], Result[A]]): Unit = {
|
||||
timings.get(task) match {
|
||||
case null =>
|
||||
case t => t.stop()
|
||||
}
|
||||
activeTasksMap.remove(task)
|
||||
if (clearTimings) timings.remove(task)
|
||||
else
|
||||
timings.get(task) match {
|
||||
case null =>
|
||||
case t => t.stop()
|
||||
}
|
||||
|
||||
// we need this to infer anonymous task names
|
||||
result.left.foreach { t =>
|
||||
|
|
@ -54,14 +100,14 @@ private[sbt] abstract class AbstractTaskExecuteProgress extends ExecuteProgress[
|
|||
}
|
||||
}
|
||||
|
||||
protected def reset(): Unit = {
|
||||
activeTasksMap.clear()
|
||||
timings.clear()
|
||||
private[this] val taskNameCache = new ConcurrentHashMap[Task[_], String]
|
||||
protected def taskName(t: Task[_]): String = taskNameCache.get(t) match {
|
||||
case null =>
|
||||
val name = taskName0(t)
|
||||
taskNameCache.putIfAbsent(t, name)
|
||||
name
|
||||
case name => name
|
||||
}
|
||||
|
||||
private[this] val taskNameCache = TrieMap.empty[Task[_], String]
|
||||
protected def taskName(t: Task[_]): String =
|
||||
taskNameCache.getOrElseUpdate(t, taskName0(t))
|
||||
private[this] def taskName0(t: Task[_]): String = {
|
||||
def definedName(node: Task[_]): Option[String] =
|
||||
node.info.name orElse TaskName.transformNode(node).map(showScopedKey.show)
|
||||
|
|
@ -80,6 +126,7 @@ object AbstractTaskExecuteProgress {
|
|||
def stop(): Unit = {
|
||||
endNanos = System.nanoTime()
|
||||
}
|
||||
def isActive = endNanos == 0L
|
||||
def durationNanos: Long = endNanos - startNanos
|
||||
def startMicros: Long = (startNanos.toDouble / 1000).toLong
|
||||
def durationMicros: Long = (durationNanos.toDouble / 1000).toLong
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
val exec = impl(interval match {
|
||||
impl(interval match {
|
||||
case d: FiniteDuration => Some(d.fromNow)
|
||||
case _ => None
|
||||
}, 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 =
|
||||
|
|
@ -208,7 +201,8 @@ private[sbt] final class CommandExchange {
|
|||
instance,
|
||||
handlers,
|
||||
s.log,
|
||||
mkAskUser(name)
|
||||
mkAskUser(name),
|
||||
Option(lastState.get),
|
||||
)
|
||||
subscribe(channel)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1231,7 +1231,7 @@ private[sbt] object ContinuousCommands {
|
|||
state: State
|
||||
) extends Thread(s"sbt-${channel.name}-watch-ui-thread")
|
||||
with UITask {
|
||||
override private[sbt] def reader: UITask.Reader = () => {
|
||||
override private[sbt] lazy val reader: UITask.Reader = () => {
|
||||
def stop = Right(s"${ContinuousCommands.stopWatch} ${channel.name}")
|
||||
val exitAction: Watch.Action = {
|
||||
Watch.apply(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ package internal
|
|||
import java.util.Locale
|
||||
|
||||
import scala.util.control.NonFatal
|
||||
import scala.concurrent.duration._
|
||||
import sbt.internal.util.ConsoleAppender
|
||||
import sbt.internal.util.complete.SizeParser
|
||||
|
||||
|
|
@ -103,7 +104,9 @@ object SysProp {
|
|||
def dumbTerm: Boolean = sys.env.get("TERM").contains("dumb")
|
||||
def supershell: Boolean = booleanOpt("sbt.supershell").getOrElse(!dumbTerm && color)
|
||||
|
||||
def supershellSleep: Long = long("sbt.supershell.sleep", 100L)
|
||||
def supershellMaxTasks: Int = int("sbt.supershell.maxitems", 8)
|
||||
def supershellSleep: Long = long("sbt.supershell.sleep", 500.millis.toMillis)
|
||||
def supershellThreshold: FiniteDuration = long("sbt.supershell.threshold", 100L).millis
|
||||
def supershellBlankZone: Int = int("sbt.supershell.blankzone", 1)
|
||||
|
||||
def defaultUseCoursier: Boolean = {
|
||||
|
|
|
|||
|
|
@ -9,99 +9,100 @@ package sbt
|
|||
package internal
|
||||
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger, AtomicReference }
|
||||
import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import sbt.internal.util._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object TaskProgress extends TaskProgress
|
||||
import java.util.concurrent.{ ConcurrentHashMap, Executors, TimeoutException }
|
||||
|
||||
/**
|
||||
* implements task progress display on the shell.
|
||||
*/
|
||||
private[sbt] class TaskProgress private ()
|
||||
private[sbt] class TaskProgress(sleepDuration: FiniteDuration, threshold: FiniteDuration)
|
||||
extends AbstractTaskExecuteProgress
|
||||
with ExecuteProgress[Task] {
|
||||
with ExecuteProgress[Task]
|
||||
with AutoCloseable {
|
||||
private[this] val lastTaskCount = new AtomicInteger(0)
|
||||
private[this] val currentProgressThread = new AtomicReference[Option[ProgressThread]](None)
|
||||
private[this] val sleepDuration = SysProp.supershellSleep.millis
|
||||
private[this] val threshold = 10.millis
|
||||
private[this] val tasks = new LinkedBlockingQueue[Task[_]]
|
||||
private[this] final class ProgressThread
|
||||
extends Thread("task-progress-report-thread")
|
||||
with AutoCloseable {
|
||||
private[this] val isClosed = new AtomicBoolean(false)
|
||||
private[this] val firstTime = new AtomicBoolean(true)
|
||||
private[this] val hasReported = new AtomicBoolean(false)
|
||||
private[this] def doReport(): Unit = { hasReported.set(true); report() }
|
||||
setDaemon(true)
|
||||
start()
|
||||
private def resetThread(): Unit =
|
||||
currentProgressThread.synchronized {
|
||||
currentProgressThread.getAndSet(None) match {
|
||||
case Some(t) if t != this => currentProgressThread.set(Some(t))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
@tailrec override def run(): Unit = {
|
||||
if (!isClosed.get() && (!hasReported.get || active.nonEmpty)) {
|
||||
try {
|
||||
if (activeExceedingThreshold.nonEmpty) doReport()
|
||||
val duration =
|
||||
if (firstTime.compareAndSet(true, activeExceedingThreshold.isEmpty)) threshold
|
||||
else sleepDuration
|
||||
val limit = duration.fromNow
|
||||
while (Deadline.now < limit && !isClosed.get && active.nonEmpty) {
|
||||
var task = tasks.poll((limit - Deadline.now).toMillis, TimeUnit.MILLISECONDS)
|
||||
while (task != null) {
|
||||
if (containsSkipTasks(Vector(task)) || lastTaskCount.get == 0) doReport()
|
||||
task = tasks.poll
|
||||
tasks.clear()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case _: InterruptedException =>
|
||||
isClosed.set(true)
|
||||
// One last report after close in case the last one hadn't gone through yet.
|
||||
doReport()
|
||||
|
||||
}
|
||||
run()
|
||||
} else {
|
||||
resetThread()
|
||||
private[this] val reportLoop = new AtomicReference[AutoCloseable]
|
||||
private[this] val active = new ConcurrentHashMap[Task[_], AutoCloseable]
|
||||
private[this] val nextReport = new AtomicReference(Deadline.now)
|
||||
private[this] val scheduler =
|
||||
Executors.newSingleThreadScheduledExecutor(r => new Thread(r, "sbt-progress-report-scheduler"))
|
||||
private[this] val pending = new java.util.Vector[java.util.concurrent.Future[_]]
|
||||
private def schedule[R](duration: FiniteDuration, recurring: Boolean)(f: => R): AutoCloseable = {
|
||||
val cancelled = new AtomicBoolean(false)
|
||||
val runnable: Runnable = () => {
|
||||
if (!cancelled.get) {
|
||||
try Util.ignoreResult(f)
|
||||
catch { case _: InterruptedException => }
|
||||
}
|
||||
}
|
||||
|
||||
def addTask(task: Task[_]): Unit = tasks.put(task)
|
||||
|
||||
override def close(): Unit = {
|
||||
isClosed.set(true)
|
||||
interrupt()
|
||||
report()
|
||||
appendProgress(ProgressEvent("Info", Vector(), None, None, None))
|
||||
resetThread()
|
||||
val delay = duration.toMillis
|
||||
val future =
|
||||
if (recurring) scheduler.schedule(runnable, delay, TimeUnit.MILLISECONDS)
|
||||
else scheduler.scheduleAtFixedRate(runnable, delay, delay, TimeUnit.MILLISECONDS)
|
||||
pending.add(future)
|
||||
() => Util.ignoreResult(future.cancel(true))
|
||||
}
|
||||
private[this] val executor =
|
||||
Executors.newSingleThreadExecutor(r => new Thread(r, "sbt-task-progress-report-thread"))
|
||||
override def close(): Unit = {
|
||||
Option(reportLoop.get).foreach(_.close())
|
||||
pending.forEach(f => Util.ignoreResult(f.cancel(true)))
|
||||
pending.clear()
|
||||
scheduler.shutdownNow()
|
||||
executor.shutdownNow()
|
||||
if (!executor.awaitTermination(1, TimeUnit.SECONDS) ||
|
||||
!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
|
||||
throw new TimeoutException
|
||||
}
|
||||
}
|
||||
|
||||
override protected def clearTimings: Boolean = true
|
||||
override def initial(): Unit = ()
|
||||
|
||||
private[this] def doReport(): Unit = {
|
||||
val runnable: Runnable = () => {
|
||||
if (nextReport.get.isOverdue) {
|
||||
report()
|
||||
}
|
||||
}
|
||||
Util.ignoreResult(pending.add(executor.submit(runnable)))
|
||||
}
|
||||
override def beforeWork(task: Task[_]): Unit = {
|
||||
maybeStartThread()
|
||||
super.beforeWork(task)
|
||||
tasks.put(task)
|
||||
reportLoop.get match {
|
||||
case null =>
|
||||
val loop = schedule(sleepDuration, recurring = true)(doReport())
|
||||
reportLoop.getAndSet(loop) match {
|
||||
case null =>
|
||||
case l =>
|
||||
reportLoop.set(l)
|
||||
loop.close()
|
||||
}
|
||||
case s =>
|
||||
}
|
||||
}
|
||||
override def afterReady(task: Task[_]): Unit = maybeStartThread()
|
||||
|
||||
override def afterCompleted[A](task: Task[A], result: Result[A]): Unit = maybeStartThread()
|
||||
override def afterReady(task: Task[_]): Unit =
|
||||
Util.ignoreResult(active.put(task, schedule(threshold, recurring = false)(doReport())))
|
||||
override def stop(): Unit = {}
|
||||
|
||||
override def stop(): Unit = currentProgressThread.synchronized {
|
||||
currentProgressThread.getAndSet(None).foreach(_.close())
|
||||
}
|
||||
override def afterCompleted[A](task: Task[A], result: Result[A]): Unit =
|
||||
active.remove(task) match {
|
||||
case null =>
|
||||
case a =>
|
||||
a.close()
|
||||
if (exceededThreshold(task, threshold)) report()
|
||||
}
|
||||
|
||||
override def afterAllCompleted(results: RMap[Task, Result]): Unit = {
|
||||
reset()
|
||||
reportLoop.getAndSet(null) match {
|
||||
case null =>
|
||||
case l => l.close()
|
||||
}
|
||||
// send an empty progress report to clear out the previous report
|
||||
appendProgress(ProgressEvent("Info", Vector(), Some(lastTaskCount.get), None, None))
|
||||
}
|
||||
|
|
@ -117,51 +118,39 @@ private[sbt] class TaskProgress private ()
|
|||
"consoleQuick",
|
||||
"state"
|
||||
)
|
||||
private[this] def maybeStartThread(): Unit = {
|
||||
currentProgressThread.get() match {
|
||||
case None =>
|
||||
currentProgressThread.synchronized {
|
||||
currentProgressThread.get() match {
|
||||
case None => currentProgressThread.set(Some(new ProgressThread))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
private[this] def appendProgress(event: ProgressEvent): Unit =
|
||||
StandardMain.exchange.updateProgress(event)
|
||||
private[this] def active: Vector[Task[_]] = activeTasks.toVector.filterNot(Def.isDummy)
|
||||
private[this] def activeExceedingThreshold: Vector[(Task[_], Long)] = active.flatMap { task =>
|
||||
timings.get(task) match {
|
||||
case null => None
|
||||
case t =>
|
||||
val elapsed = t.currentElapsedMicros
|
||||
if (elapsed.micros > threshold) Some[(Task[_], Long)](task -> elapsed) else None
|
||||
private[this] def report(): Unit = {
|
||||
val currentTasks = timings(active.keySet, threshold.toMicros)
|
||||
val ltc = lastTaskCount.get
|
||||
if (currentTasks.nonEmpty || ltc != 0) {
|
||||
val currentTasksCount = currentTasks.size
|
||||
def event(tasks: Vector[(Task[_], Long)]): ProgressEvent = {
|
||||
if (tasks.nonEmpty) nextReport.set(Deadline.now + sleepDuration)
|
||||
val toWrite = tasks.sortBy(_._2)
|
||||
val distinct = new java.util.LinkedHashMap[String, ProgressItem]
|
||||
toWrite.foreach {
|
||||
case (task, elapsed) =>
|
||||
val name = taskName(task)
|
||||
distinct.put(name, ProgressItem(name, elapsed))
|
||||
}
|
||||
ProgressEvent(
|
||||
"Info",
|
||||
distinct.values.asScala.toVector,
|
||||
Some(ltc),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(containsSkipTasks(active.keySet))
|
||||
)
|
||||
}
|
||||
lastTaskCount.set(currentTasksCount)
|
||||
appendProgress(event(currentTasks))
|
||||
}
|
||||
}
|
||||
private[this] def report(): Unit = {
|
||||
val currentTasks = activeExceedingThreshold
|
||||
val ltc = lastTaskCount.get
|
||||
val currentTasksCount = currentTasks.size
|
||||
def event(tasks: Vector[(Task[_], Long)]): ProgressEvent = ProgressEvent(
|
||||
"Info",
|
||||
tasks
|
||||
.map { case (task, elapsed) => ProgressItem(taskName(task), elapsed) }
|
||||
.sortBy(_.elapsedMicros),
|
||||
Some(ltc),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(containsSkipTasks(active))
|
||||
)
|
||||
if (active.nonEmpty) maybeStartThread()
|
||||
lastTaskCount.set(currentTasksCount)
|
||||
appendProgress(event(currentTasks))
|
||||
}
|
||||
|
||||
private[this] def containsSkipTasks(tasks: Vector[Task[_]]): Boolean = {
|
||||
tasks.map(taskName).exists { n =>
|
||||
private[this] def containsSkipTasks(tasks: java.util.Set[Task[_]]): Boolean = {
|
||||
tasks.iterator.asScala.map(taskName).exists { n =>
|
||||
val shortName = n.lastIndexOf('/') match {
|
||||
case -1 => n
|
||||
case i =>
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ private[sbt] final class TaskTimings(reportOnShutdown: Boolean, logger: Logger)
|
|||
override def log(level: Level.Value, message: => String): Unit =
|
||||
ConsoleOut.systemOut.println(message)
|
||||
})
|
||||
import AbstractTaskExecuteProgress.Timer
|
||||
private[this] var start = 0L
|
||||
private[this] val threshold = SysProp.taskTimingsThreshold
|
||||
private[this] val omitPaths = SysProp.taskTimingsOmitPaths
|
||||
|
|
@ -61,15 +60,12 @@ private[sbt] final class TaskTimings(reportOnShutdown: Boolean, logger: Logger)
|
|||
private[this] def report() = {
|
||||
val total = divide(System.nanoTime - start)
|
||||
logger.info(s"Total time: $total $unit")
|
||||
import collection.JavaConverters._
|
||||
def sumTimes(in: Seq[(Task[_], Timer)]) = in.map(_._2.durationNanos).sum
|
||||
val timingsByName = timings.asScala.toSeq.groupBy { case (t, _) => taskName(t) } mapValues (sumTimes)
|
||||
val times = timingsByName.toSeq
|
||||
.sortBy(_._2)
|
||||
.sortBy(_._2.get)
|
||||
.reverse
|
||||
.map {
|
||||
case (name, time) =>
|
||||
(if (omitPaths) reFilePath.replaceFirstIn(name, "") else name, divide(time))
|
||||
(if (omitPaths) reFilePath.replaceFirstIn(name, "") else name, divide(time.get))
|
||||
}
|
||||
.filter { _._2 > threshold }
|
||||
if (times.size > 0) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import java.nio.file.Files
|
|||
import sbt.internal.util.{ RMap, ConsoleOut }
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
import scala.collection.JavaConverters._
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.JString
|
||||
import sjsonnew.support.scalajson.unsafe.CompactPrinter
|
||||
|
||||
|
|
@ -39,7 +38,7 @@ private[sbt] final class TaskTraceEvent
|
|||
ShutdownHooks.add(() => report())
|
||||
|
||||
private[this] def report() = {
|
||||
if (timings.asScala.nonEmpty) {
|
||||
if (anyTimings) {
|
||||
writeTraceEvent()
|
||||
}
|
||||
}
|
||||
|
|
@ -63,10 +62,10 @@ private[sbt] final class TaskTraceEvent
|
|||
CompactPrinter.print(new JString(name), sb)
|
||||
s"""{"name": ${sb.toString}, "cat": "$cat", "ph": "X", "ts": ${(t.startMicros)}, "dur": ${(t.durationMicros)}, "pid": 0, "tid": ${t.threadId}}"""
|
||||
}
|
||||
val entryIterator = timings.entrySet().iterator()
|
||||
val entryIterator = currentTimings
|
||||
while (entryIterator.hasNext) {
|
||||
val entry = entryIterator.next()
|
||||
trace.append(durationEvent(taskName(entry.getKey), "task", entry.getValue))
|
||||
val (key, value) = entryIterator.next()
|
||||
trace.append(durationEvent(taskName(key), "task", value))
|
||||
if (entryIterator.hasNext) trace.append(",")
|
||||
}
|
||||
trace.append("]}")
|
||||
|
|
|
|||
|
|
@ -59,17 +59,16 @@ private[sbt] class XMainConfiguration {
|
|||
val topLoader = configuration.provider.scalaProvider.launcher.topLoader
|
||||
val updatedConfiguration =
|
||||
try {
|
||||
val method = topLoader.getClass.getMethod("getEarlyJars")
|
||||
val method = topLoader.getClass.getMethod("getJLineJars")
|
||||
val jars = method.invoke(topLoader).asInstanceOf[Array[URL]]
|
||||
var canReuseConfiguration = jars.length == 3
|
||||
var j = 0
|
||||
while (j < jars.length && canReuseConfiguration) {
|
||||
val s = jars(j).toString
|
||||
canReuseConfiguration =
|
||||
s.contains("jline") || s.contains("test-interface") || s.contains("jansi")
|
||||
canReuseConfiguration = s.contains("jline") || s.contains("jansi")
|
||||
j += 1
|
||||
}
|
||||
if (canReuseConfiguration) configuration else makeConfiguration(configuration)
|
||||
if (canReuseConfiguration && j == 3) configuration else makeConfiguration(configuration)
|
||||
} catch {
|
||||
case _: NoSuchMethodException => makeConfiguration(configuration)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
|
|||
|
||||
import BasicJsonProtocol._
|
||||
import Serialization.{ attach, promptChannel }
|
||||
import sbt.internal.util.ProgressState
|
||||
|
||||
final class NetworkChannel(
|
||||
val name: String,
|
||||
|
|
@ -58,7 +59,8 @@ final class NetworkChannel(
|
|||
instance: ServerInstance,
|
||||
handlers: Seq[ServerHandler],
|
||||
val log: Logger,
|
||||
mkUIThreadImpl: (State, CommandChannel) => UITask
|
||||
mkUIThreadImpl: (State, CommandChannel) => UITask,
|
||||
state: Option[State],
|
||||
) extends CommandChannel { self =>
|
||||
def this(
|
||||
name: String,
|
||||
|
|
@ -77,7 +79,8 @@ final class NetworkChannel(
|
|||
instance,
|
||||
handlers,
|
||||
log,
|
||||
new UITask.AskUserTask(_, _)
|
||||
new UITask.AskUserTask(_, _),
|
||||
None
|
||||
)
|
||||
|
||||
private val running = new AtomicBoolean(true)
|
||||
|
|
@ -110,7 +113,7 @@ final class NetworkChannel(
|
|||
}
|
||||
private[sbt] def write(byte: Byte) = inputBuffer.add(byte)
|
||||
|
||||
private[this] val terminalHolder = new AtomicReference(Terminal.NullTerminal)
|
||||
private[this] val terminalHolder = new AtomicReference[Terminal](Terminal.NullTerminal)
|
||||
override private[sbt] def terminal: Terminal = terminalHolder.get
|
||||
override val userThread: UserThread = new UserThread(this)
|
||||
|
||||
|
|
@ -152,8 +155,8 @@ final class NetworkChannel(
|
|||
if (interactive.get || ContinuousCommands.isInWatch(state, this)) mkUIThreadImpl(state, command)
|
||||
else
|
||||
new UITask {
|
||||
override private[sbt] def channel = NetworkChannel.this
|
||||
override def reader: UITask.Reader = () => {
|
||||
override private[sbt] val channel = NetworkChannel.this
|
||||
override private[sbt] lazy val reader: UITask.Reader = () => {
|
||||
try {
|
||||
this.synchronized(this.wait)
|
||||
Left(TerminateAction)
|
||||
|
|
@ -650,6 +653,8 @@ final class NetworkChannel(
|
|||
}
|
||||
override def available(): Int = inputBuffer.size
|
||||
}
|
||||
private[this] lazy val writeableInputStream: Terminal.WriteableInputStream =
|
||||
new Terminal.WriteableInputStream(inputStream, name)
|
||||
import sjsonnew.BasicJsonProtocol._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
|
@ -726,7 +731,8 @@ final class NetworkChannel(
|
|||
write(java.util.Arrays.copyOfRange(b, off, off + len))
|
||||
}
|
||||
}
|
||||
private class NetworkTerminal extends TerminalImpl(inputStream, outputStream, errorStream, name) {
|
||||
private class NetworkTerminal
|
||||
extends TerminalImpl(writeableInputStream, outputStream, errorStream, name) {
|
||||
private[this] val pending = new AtomicBoolean(false)
|
||||
private[this] val closed = new AtomicBoolean(false)
|
||||
private[this] val properties = new AtomicReference[TerminalPropertiesResponse]
|
||||
|
|
@ -784,6 +790,10 @@ final class NetworkChannel(
|
|||
)
|
||||
}
|
||||
private[this] val blockedThreads = ConcurrentHashMap.newKeySet[Thread]
|
||||
override private[sbt] val progressState: ProgressState = new ProgressState(
|
||||
1,
|
||||
state.flatMap(_.get(Keys.superShellMaxTasks.key)).getOrElse(SysProp.supershellMaxTasks)
|
||||
)
|
||||
override def getWidth: Int = getProperty(_.width, 0).getOrElse(0)
|
||||
override def getHeight: Int = getProperty(_.height, 0).getOrElse(0)
|
||||
override def isAnsiSupported: Boolean = getProperty(_.isAnsiSupported, false).getOrElse(false)
|
||||
|
|
@ -872,6 +882,14 @@ final class NetworkChannel(
|
|||
try queue.take
|
||||
catch { case _: InterruptedException => }
|
||||
}
|
||||
override private[sbt] def getSizeImpl: (Int, Int) =
|
||||
if (!closed.get) {
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
val queue = VirtualTerminal.getTerminalSize(name, jsonRpcRequest)
|
||||
val res = try queue.take
|
||||
catch { case _: InterruptedException => TerminalGetSizeResponse(1, 1) }
|
||||
(res.width, res.height)
|
||||
} else (1, 1)
|
||||
override def setSize(width: Int, height: Int): Unit =
|
||||
if (!closed.get) {
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import sbt.protocol.Serialization.{
|
|||
attach,
|
||||
systemIn,
|
||||
terminalCapabilities,
|
||||
terminalGetSize,
|
||||
terminalPropertiesQuery,
|
||||
terminalSetSize,
|
||||
}
|
||||
import sjsonnew.support.scalajson.unsafe.Converter
|
||||
import sbt.protocol.{
|
||||
|
|
@ -30,10 +32,13 @@ import sbt.protocol.{
|
|||
TerminalCapabilitiesQuery,
|
||||
TerminalCapabilitiesResponse,
|
||||
TerminalPropertiesResponse,
|
||||
TerminalGetSizeQuery,
|
||||
TerminalGetSizeResponse,
|
||||
TerminalSetAttributesCommand,
|
||||
TerminalSetSizeCommand,
|
||||
}
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
import sbt.protocol.TerminalGetSizeResponse
|
||||
|
||||
object VirtualTerminal {
|
||||
private[this] val pendingTerminalProperties =
|
||||
|
|
@ -46,6 +51,8 @@ object VirtualTerminal {
|
|||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
|
||||
private[this] val pendingTerminalSetSize =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
|
||||
private[this] val pendingTerminalGetSize =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalGetSizeResponse]]
|
||||
private[sbt] def sendTerminalPropertiesQuery(
|
||||
channelName: String,
|
||||
jsonRpcRequest: (String, String, String) => Unit
|
||||
|
|
@ -111,9 +118,22 @@ object VirtualTerminal {
|
|||
val id = UUID.randomUUID.toString
|
||||
val queue = new ArrayBlockingQueue[Unit](1)
|
||||
pendingTerminalSetSize.put((channelName, id), queue)
|
||||
jsonRpcRequest(id, terminalCapabilities, query)
|
||||
jsonRpcRequest(id, terminalSetSize, query)
|
||||
queue
|
||||
}
|
||||
|
||||
private[sbt] def getTerminalSize(
|
||||
channelName: String,
|
||||
jsonRpcRequest: (String, String, TerminalGetSizeQuery) => Unit,
|
||||
): ArrayBlockingQueue[TerminalGetSizeResponse] = {
|
||||
val id = UUID.randomUUID.toString
|
||||
val query = TerminalGetSizeQuery()
|
||||
val queue = new ArrayBlockingQueue[TerminalGetSizeResponse](1)
|
||||
pendingTerminalGetSize.put((channelName, id), queue)
|
||||
jsonRpcRequest(id, terminalGetSize, query)
|
||||
queue
|
||||
}
|
||||
|
||||
val handler = ServerHandler { cb =>
|
||||
ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb))
|
||||
}
|
||||
|
|
@ -166,6 +186,13 @@ object VirtualTerminal {
|
|||
case null =>
|
||||
case buffer => buffer.put(())
|
||||
}
|
||||
case r if pendingTerminalGetSize.get((callback.name, r.id)) != null =>
|
||||
val response =
|
||||
r.result.flatMap(Converter.fromJson[TerminalGetSizeResponse](_).toOption)
|
||||
pendingTerminalGetSize.remove((callback.name, r.id)) match {
|
||||
case null =>
|
||||
case buffer => buffer.put(response.getOrElse(TerminalGetSizeResponse(1, 1)))
|
||||
}
|
||||
}
|
||||
private val notificationHandler: Handler[JsonRpcNotificationMessage] =
|
||||
callback => {
|
||||
|
|
|
|||
|
|
@ -84,8 +84,10 @@ object Dependencies {
|
|||
val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash")
|
||||
|
||||
val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-5e51b9d4f9631ebfa29753ce4accc57808e7fd6b"
|
||||
val jline3 = "org.jline" % "jline" % "3.15.0"
|
||||
val jline3Jansi = "org.jline" % "jline-terminal-jansi" % "3.15.0"
|
||||
val jline3Version = "3.16.0" // Once the base jline version is upgraded, we can use the official jline-terminal
|
||||
val jline3Terminal = "org.scala-sbt.jline3" % "jline-terminal" % s"$jline3Version-sbt-211a082ed6326908dc84ca017ce4430728f18a8a"
|
||||
val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version
|
||||
val jline3Reader = "org.jline" % "jline-reader" % jline3Version
|
||||
val jansi = "org.fusesource.jansi" % "jansi" % "1.18"
|
||||
val scalatest = "org.scalatest" %% "scalatest" % "3.0.8"
|
||||
val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol
|
||||
final class TerminalGetSizeQuery private () extends sbt.protocol.CommandMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case _: TerminalGetSizeQuery => true
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (17 + "sbt.protocol.TerminalGetSizeQuery".##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalGetSizeQuery()"
|
||||
}
|
||||
private[this] def copy(): TerminalGetSizeQuery = {
|
||||
new TerminalGetSizeQuery()
|
||||
}
|
||||
|
||||
}
|
||||
object TerminalGetSizeQuery {
|
||||
|
||||
def apply(): TerminalGetSizeQuery = new TerminalGetSizeQuery()
|
||||
}
|
||||
36
protocol/src/main/contraband-scala/sbt/protocol/TerminalGetSizeResponse.scala
generated
Normal file
36
protocol/src/main/contraband-scala/sbt/protocol/TerminalGetSizeResponse.scala
generated
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol
|
||||
final class TerminalGetSizeResponse private (
|
||||
val width: Int,
|
||||
val height: Int) extends sbt.protocol.EventMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: TerminalGetSizeResponse => (this.width == x.width) && (this.height == x.height)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (17 + "sbt.protocol.TerminalGetSizeResponse".##) + width.##) + height.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalGetSizeResponse(" + width + ", " + height + ")"
|
||||
}
|
||||
private[this] def copy(width: Int = width, height: Int = height): TerminalGetSizeResponse = {
|
||||
new TerminalGetSizeResponse(width, height)
|
||||
}
|
||||
def withWidth(width: Int): TerminalGetSizeResponse = {
|
||||
copy(width = width)
|
||||
}
|
||||
def withHeight(height: Int): TerminalGetSizeResponse = {
|
||||
copy(height = height)
|
||||
}
|
||||
}
|
||||
object TerminalGetSizeResponse {
|
||||
|
||||
def apply(width: Int, height: Int): TerminalGetSizeResponse = new TerminalGetSizeResponse(width, height)
|
||||
}
|
||||
|
|
@ -6,6 +6,6 @@
|
|||
package sbt.protocol.codec
|
||||
|
||||
import _root_.sjsonnew.JsonFormat
|
||||
trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats =>
|
||||
implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat8[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery, sbt.protocol.TerminalSetAttributesCommand, sbt.protocol.TerminalAttributesQuery, sbt.protocol.TerminalSetSizeCommand]("type")
|
||||
trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats =>
|
||||
implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat9[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery, sbt.protocol.TerminalSetAttributesCommand, sbt.protocol.TerminalAttributesQuery, sbt.protocol.TerminalGetSizeQuery, sbt.protocol.TerminalSetSizeCommand]("type")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@
|
|||
package sbt.protocol.codec
|
||||
|
||||
import _root_.sjsonnew.JsonFormat
|
||||
trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.internal.util.codec.JValueFormats with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats with sbt.protocol.codec.TerminalPropertiesResponseFormats with sbt.protocol.codec.TerminalCapabilitiesResponseFormats with sbt.protocol.codec.TerminalSetAttributesResponseFormats with sbt.protocol.codec.TerminalAttributesResponseFormats with sbt.protocol.codec.TerminalSetSizeResponseFormats =>
|
||||
implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat10[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure, sbt.protocol.TerminalPropertiesResponse, sbt.protocol.TerminalCapabilitiesResponse, sbt.protocol.TerminalSetAttributesResponse, sbt.protocol.TerminalAttributesResponse, sbt.protocol.TerminalSetSizeResponse]("type")
|
||||
trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.internal.util.codec.JValueFormats with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats with sbt.protocol.codec.TerminalPropertiesResponseFormats with sbt.protocol.codec.TerminalCapabilitiesResponseFormats with sbt.protocol.codec.TerminalSetAttributesResponseFormats with sbt.protocol.codec.TerminalAttributesResponseFormats with sbt.protocol.codec.TerminalGetSizeResponseFormats with sbt.protocol.codec.TerminalSetSizeResponseFormats =>
|
||||
implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat11[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure, sbt.protocol.TerminalPropertiesResponse, sbt.protocol.TerminalCapabilitiesResponse, sbt.protocol.TerminalSetAttributesResponse, sbt.protocol.TerminalAttributesResponse, sbt.protocol.TerminalGetSizeResponse, sbt.protocol.TerminalSetSizeResponse]("type")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
|||
with sbt.protocol.codec.TerminalCapabilitiesQueryFormats
|
||||
with sbt.protocol.codec.TerminalSetAttributesCommandFormats
|
||||
with sbt.protocol.codec.TerminalAttributesQueryFormats
|
||||
with sbt.protocol.codec.TerminalGetSizeQueryFormats
|
||||
with sbt.protocol.codec.TerminalSetSizeCommandFormats
|
||||
with sbt.protocol.codec.CommandMessageFormats
|
||||
with sbt.protocol.codec.CompletionParamsFormats
|
||||
|
|
@ -25,6 +26,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
|||
with sbt.protocol.codec.TerminalCapabilitiesResponseFormats
|
||||
with sbt.protocol.codec.TerminalSetAttributesResponseFormats
|
||||
with sbt.protocol.codec.TerminalAttributesResponseFormats
|
||||
with sbt.protocol.codec.TerminalGetSizeResponseFormats
|
||||
with sbt.protocol.codec.TerminalSetSizeResponseFormats
|
||||
with sbt.protocol.codec.EventMessageFormats
|
||||
with sbt.protocol.codec.SettingQueryResponseFormats
|
||||
|
|
|
|||
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalGetSizeQueryFormats.scala
generated
Normal file
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalGetSizeQueryFormats.scala
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait TerminalGetSizeQueryFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalGetSizeQueryFormat: JsonFormat[sbt.protocol.TerminalGetSizeQuery] = new JsonFormat[sbt.protocol.TerminalGetSizeQuery] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalGetSizeQuery = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalGetSizeQuery()
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalGetSizeQuery, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
29
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalGetSizeResponseFormats.scala
generated
Normal file
29
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalGetSizeResponseFormats.scala
generated
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait TerminalGetSizeResponseFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalGetSizeResponseFormat: JsonFormat[sbt.protocol.TerminalGetSizeResponse] = new JsonFormat[sbt.protocol.TerminalGetSizeResponse] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalGetSizeResponse = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
val width = unbuilder.readField[Int]("width")
|
||||
val height = unbuilder.readField[Int]("height")
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalGetSizeResponse(width, height)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalGetSizeResponse, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("width", obj.width)
|
||||
builder.addField("height", obj.height)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +126,12 @@ type TerminalAttributesResponse implements EventMessage {
|
|||
cchars: String!,
|
||||
}
|
||||
|
||||
type TerminalGetSizeQuery implements CommandMessage {}
|
||||
type TerminalGetSizeResponse implements EventMessage {
|
||||
width: Int!
|
||||
height: Int!
|
||||
}
|
||||
|
||||
type TerminalSetSizeCommand implements CommandMessage {
|
||||
width: Int!
|
||||
height: Int!
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ object Serialization {
|
|||
val promptChannel = "sbt/promptChannel"
|
||||
val setTerminalAttributes = "sbt/setTerminalAttributes"
|
||||
val getTerminalAttributes = "sbt/getTerminalAttributes"
|
||||
val setTerminalSize = "sbt/setTerminalSize"
|
||||
val terminalGetSize = "sbt/terminalGetSize"
|
||||
val terminalSetSize = "sbt/terminalSetSize"
|
||||
val CancelAll = "__CancelAll"
|
||||
|
||||
@deprecated("unused", since = "1.4.0")
|
||||
|
|
|
|||
Loading…
Reference in New Issue