mirror of https://github.com/sbt/sbt.git
Merge pull request #5671 from eatkins/upgrade-to-jline3
Upgrade to jline3
This commit is contained in:
commit
ab5e57afb1
25
build.sbt
25
build.sbt
|
|
@ -108,7 +108,7 @@ def commonBaseSettings: Seq[Setting[_]] = Def.settings(
|
|||
)
|
||||
def commonSettings: Seq[Setting[_]] =
|
||||
commonBaseSettings :+
|
||||
addCompilerPlugin("org.spire-math" % "kind-projector" % "0.9.4" cross CrossVersion.binary)
|
||||
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full)
|
||||
def utilCommonSettings: Seq[Setting[_]] =
|
||||
commonBaseSettings :+ (crossScalaVersions := (scala212 :: scala213 :: Nil))
|
||||
|
||||
|
|
@ -246,9 +246,14 @@ lazy val bundledLauncherProj =
|
|||
val collectionProj = (project in file("internal") / "util-collection")
|
||||
.settings(
|
||||
testedBaseSettings,
|
||||
utilCommonSettings,
|
||||
Util.keywordsSettings,
|
||||
name := "Collections",
|
||||
libraryDependencies ++= Seq(sjsonNewScalaJson.value),
|
||||
libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match {
|
||||
case Some((2, major)) if major <= 12 => Seq()
|
||||
case _ => Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0")
|
||||
}),
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Seq(
|
||||
// Added private[sbt] method to capture State attributes.
|
||||
|
|
@ -289,6 +294,7 @@ val completeProj = (project in file("internal") / "util-complete")
|
|||
testedBaseSettings,
|
||||
name := "Completion",
|
||||
libraryDependencies += jline,
|
||||
libraryDependencies += jline3,
|
||||
mimaSettings,
|
||||
// Parser is used publicly, so we can't break bincompat.
|
||||
mimaBinaryIssueFilters := Seq(
|
||||
|
|
@ -343,12 +349,20 @@ lazy val utilPosition = (project in file("internal") / "util-position")
|
|||
|
||||
lazy val utilLogging = (project in file("internal") / "util-logging")
|
||||
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
||||
.dependsOn(utilInterface)
|
||||
.dependsOn(utilInterface, collectionProj)
|
||||
.settings(
|
||||
utilCommonSettings,
|
||||
name := "Util Logging",
|
||||
libraryDependencies ++=
|
||||
Seq(jline, log4jApi, log4jCore, disruptor, sjsonNewScalaJson.value, scalaReflect.value),
|
||||
Seq(
|
||||
jline,
|
||||
jline3,
|
||||
log4jApi,
|
||||
log4jCore,
|
||||
disruptor,
|
||||
sjsonNewScalaJson.value,
|
||||
scalaReflect.value
|
||||
),
|
||||
libraryDependencies ++= Seq(scalacheck % "test", scalatest % "test"),
|
||||
libraryDependencies ++= (scalaVersion.value match {
|
||||
case v if v.startsWith("2.12.") => List(compilerPlugin(silencerPlugin))
|
||||
|
|
@ -1047,8 +1061,7 @@ lazy val sbtClientProj = (project in file("client"))
|
|||
crossPaths := false,
|
||||
exportJars := true,
|
||||
libraryDependencies += jansi,
|
||||
libraryDependencies += "net.java.dev.jna" % "jna" % "5.5.0",
|
||||
libraryDependencies += "net.java.dev.jna" % "jna-platform" % "5.5.0",
|
||||
libraryDependencies += jline3Jansi,
|
||||
libraryDependencies += scalatest % "test",
|
||||
/*
|
||||
* On windows, the raw classpath is too large to be a command argument to an
|
||||
|
|
@ -1099,8 +1112,8 @@ lazy val sbtClientProj = (project in file("client"))
|
|||
}
|
||||
thread.start()
|
||||
proc.waitFor(5, java.util.concurrent.TimeUnit.MINUTES)
|
||||
assert(proc.exitValue == 0, s"Exit value ${proc.exitValue} was nonzero")
|
||||
nativeExecutablePath.value
|
||||
file("").toPath
|
||||
},
|
||||
graalNativeImageOptions := Seq(
|
||||
"--no-fallback",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
{
|
||||
"resources":[
|
||||
{"pattern":"jline/console/completer/CandidateListCompletionHandler.properties"},
|
||||
{"pattern":"library.properties"},
|
||||
{"pattern":"org/jline/utils/ansi.caps"},
|
||||
{"pattern":"org/jline/utils/capabilities.txt"},
|
||||
{"pattern":"org/jline/utils/colors.txt"},
|
||||
{"pattern":"org/jline/utils/dumb-color.caps"},
|
||||
{"pattern":"org/jline/utils/xterm.caps"},
|
||||
{"pattern":"org/jline/utils/xterm-256color.caps"},
|
||||
{"pattern":"org/jline/utils/windows-256color.caps"},
|
||||
{"pattern":"org/jline/utils/screen-256color.caps"},
|
||||
{"pattern":"org/jline/utils/windows.caps"},
|
||||
{"pattern":"org/jline/utils/windows-conemu.caps"},
|
||||
{"pattern":"org/jline/utils/dumb.caps"},
|
||||
{"pattern":"org/jline/utils/windows-vtp.caps"},
|
||||
{"pattern":"org/jline/utils/screen.caps"},
|
||||
{"pattern":"library.properties"},
|
||||
{"pattern":"darwin/x86_64/libsbtipcsocket.dylib"},
|
||||
{"pattern":"linux/x86_64/libsbtipcsocket.so"},
|
||||
{"pattern":"win32/x86_64/sbtipcsocket.dll"}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
import scala.collection.parallel.ParSeq
|
||||
|
||||
private[util] object Par {
|
||||
def apply[R](s: Seq[R]): ParSeq[R] = s.par
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
import scala.collection.parallel.CollectionConverters._
|
||||
import scala.collection.parallel.ParSeq
|
||||
|
||||
private[util] object Par {
|
||||
def apply[R](s: Seq[R]): ParSeq[R] = s.par
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ object IMap {
|
|||
put(k, f(this get k getOrElse init))
|
||||
|
||||
def mapValues[V2[_]](f: V ~> V2) =
|
||||
new IMap0[K, V2](backing.mapValues(x => f(x)))
|
||||
new IMap0[K, V2](Map(backing.iterator.map { case (k, v) => k -> f(v) }.toArray: _*))
|
||||
|
||||
def mapSeparate[VL[_], VR[_]](f: V ~> λ[T => Either[VL[T], VR[T]]]) = {
|
||||
val mapped = backing.iterator.map {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
package sbt.internal.util
|
||||
|
||||
import scala.language.existentials
|
||||
|
||||
import Types._
|
||||
import sbt.util.Show
|
||||
import Util.{ nil, nilSeq }
|
||||
|
|
@ -242,7 +240,7 @@ trait Init[ScopeType] {
|
|||
if (s.definitive) Vector(s) else ss :+ s
|
||||
|
||||
def addLocal(init: Seq[Setting[_]])(implicit scopeLocal: ScopeLocal): Seq[Setting[_]] =
|
||||
init.par.map(_.dependencies flatMap scopeLocal).toVector.flatten ++ init
|
||||
Par(init).map(_.dependencies flatMap scopeLocal).toVector.flatten ++ init
|
||||
|
||||
def delegate(sMap: ScopedMap)(
|
||||
implicit delegates: ScopeType => Seq[ScopeType],
|
||||
|
|
@ -465,7 +463,7 @@ trait Init[ScopeType] {
|
|||
def dependencies = settings.flatMap(_.dependencies)
|
||||
// This is mainly for use in the cyclic reference error message
|
||||
override def toString =
|
||||
s"Derived settings for ${key.label}, ${definedAtString(settings.map(_.setting))}"
|
||||
s"Derived settings for ${key.label}, ${definedAtString(settings.map(_.setting).toSeq)}"
|
||||
}
|
||||
|
||||
// separate `derived` settings from normal settings (`defs`)
|
||||
|
|
|
|||
|
|
@ -8,16 +8,28 @@
|
|||
package sbt.internal.util
|
||||
|
||||
import java.io._
|
||||
import java.util.{ List => JList }
|
||||
|
||||
import jline.console.ConsoleReader
|
||||
import jline.console.history.{ FileHistory, MemoryHistory }
|
||||
import org.jline.reader.{
|
||||
Candidate,
|
||||
Completer,
|
||||
EndOfFileException,
|
||||
LineReader => JLineReader,
|
||||
LineReaderBuilder,
|
||||
ParsedLine,
|
||||
UserInterruptException,
|
||||
}
|
||||
import sbt.internal.util.complete.Parser
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
import java.nio.channels.ClosedByInterruptException
|
||||
|
||||
trait LineReader {
|
||||
trait LineReader extends AutoCloseable {
|
||||
def readLine(prompt: String, mask: Option[Char] = None): Option[String]
|
||||
override def close(): Unit = {}
|
||||
}
|
||||
|
||||
object LineReader {
|
||||
|
|
@ -25,7 +37,67 @@ object LineReader {
|
|||
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
|
||||
val MaxHistorySize = 500
|
||||
|
||||
private def completer(parser: Parser[_]): Completer = new Completer {
|
||||
def complete(lr: JLineReader, pl: ParsedLine, candidates: JList[Candidate]): Unit = {
|
||||
Parser.completions(parser, pl.line(), 10).get.foreach { c =>
|
||||
/*
|
||||
* For commands like `~` that delegate parsing to another parser, the `~` may be
|
||||
* excluded from the completion result. For example,
|
||||
* ~testOnly <TAB>
|
||||
* might return results like
|
||||
* 'testOnly ;'
|
||||
* 'testOnly com.foo.FooSpec'
|
||||
* ...
|
||||
* If we use the raw display, JLine will reject the completions because they are
|
||||
* missing the leading `~`. To workaround this, we append to the result to the
|
||||
* line provided the line does not end with " ". This fixes the missing `~` in
|
||||
* the prefix problem. We also need to split the line on space and take the
|
||||
* last token and append to that otherwise the completion will double print
|
||||
* the prefix, so that `testOnly com<Tab>` might expand to something like:
|
||||
* `testOnly testOnly\ com.foo.FooSpec` instead of `testOnly com.foo.FooSpec`.
|
||||
*/
|
||||
if (c.append.nonEmpty) {
|
||||
if (!pl.line().endsWith(" ")) {
|
||||
candidates.add(new Candidate(pl.line().split(" ").last + c.append))
|
||||
} else {
|
||||
candidates.add(new Candidate(c.append))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def createReader(
|
||||
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
|
||||
// directly need jline3 for sbt.
|
||||
val reader = LineReaderBuilder.builder().terminal(term).completer(completer(parser)).build()
|
||||
historyPath.foreach(f => reader.setVariable(JLineReader.HISTORY_FILE, f))
|
||||
new LineReader {
|
||||
override def readLine(prompt: String, mask: Option[Char]): Option[String] = {
|
||||
try terminal.withRawInput {
|
||||
Option(mask.map(reader.readLine(prompt, _)).getOrElse(reader.readLine(prompt)))
|
||||
} catch {
|
||||
case e: EndOfFileException =>
|
||||
if (terminal == Terminal.console && System.console == null) None
|
||||
else Some("exit")
|
||||
case _: IOError => Some("exit")
|
||||
case _: UserInterruptException | _: ClosedByInterruptException |
|
||||
_: UncheckedIOException =>
|
||||
throw new InterruptedException
|
||||
} finally {
|
||||
terminal.prompt.reset()
|
||||
term.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createJLine2Reader(
|
||||
historyPath: Option[File],
|
||||
terminal: Terminal,
|
||||
prompt: Prompt = Prompt.Running,
|
||||
|
|
@ -42,7 +114,6 @@ object LineReader {
|
|||
cr.setHistoryEnabled(true)
|
||||
cr
|
||||
}
|
||||
|
||||
def simple(terminal: Terminal): LineReader = new SimpleReader(None, HandleCONT, terminal)
|
||||
def simple(
|
||||
historyPath: Option[File],
|
||||
|
|
@ -169,7 +240,7 @@ private[sbt] object JLine {
|
|||
}
|
||||
|
||||
@deprecated("Avoid referencing JLine directly.", "1.4.0")
|
||||
def withJLine[T](action: => T): T = Terminal.get.withRawSystemIn(action)
|
||||
def withJLine[T](action: => T): T = Terminal.get.withRawInput(action)
|
||||
|
||||
@deprecated("Use LineReader.simple instead", "1.4.0")
|
||||
def simple(
|
||||
|
|
@ -230,7 +301,7 @@ final class FullReader(
|
|||
Terminal.console
|
||||
)
|
||||
protected[this] val reader: ConsoleReader = {
|
||||
val cr = LineReader.createReader(historyPath, terminal)
|
||||
val cr = LineReader.createJLine2Reader(historyPath, terminal)
|
||||
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
|
||||
cr
|
||||
}
|
||||
|
|
@ -244,7 +315,7 @@ class SimpleReader private[sbt] (
|
|||
def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
|
||||
this(historyPath, handleCONT, Terminal.console)
|
||||
protected[this] val reader: ConsoleReader =
|
||||
LineReader.createReader(historyPath, terminal)
|
||||
LineReader.createJLine2Reader(historyPath, terminal)
|
||||
}
|
||||
|
||||
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ package sbt.internal.util
|
|||
|
||||
import java.io.{ PrintStream, PrintWriter }
|
||||
import java.lang.StringBuilder
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger, AtomicReference }
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger }
|
||||
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender
|
||||
import org.apache.logging.log4j.core.{ LogEvent => XLogEvent }
|
||||
|
|
@ -18,8 +19,6 @@ import org.apache.logging.log4j.{ Level => XLevel }
|
|||
import sbt.internal.util.ConsoleAppender._
|
||||
import sbt.util._
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
object ConsoleLogger {
|
||||
// These are provided so other modules do not break immediately.
|
||||
@deprecated("Use EscHelpers.ESC instead", "0.13.x")
|
||||
|
|
@ -396,7 +395,8 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
override def append(event: XLogEvent): Unit = {
|
||||
val level = ConsoleAppender.toLevel(event.getLevel)
|
||||
val message = event.getMessage
|
||||
appendMessage(level, message)
|
||||
try appendMessage(level, message)
|
||||
catch { case _: ClosedChannelException => }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -553,135 +553,3 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
}
|
||||
|
||||
final class SuppressedTraceContext(val traceLevel: Int, val useFormat: Boolean)
|
||||
private[sbt] final class ProgressState(
|
||||
val progressLines: AtomicReference[Seq[String]],
|
||||
val padding: AtomicInteger,
|
||||
val blankZone: Int,
|
||||
val currentLineBytes: AtomicReference[ArrayBuffer[Byte]],
|
||||
) {
|
||||
def this(blankZone: Int) =
|
||||
this(
|
||||
new AtomicReference(Nil),
|
||||
new AtomicInteger(0),
|
||||
blankZone,
|
||||
new AtomicReference(new ArrayBuffer[Byte]),
|
||||
)
|
||||
def reset(): Unit = {
|
||||
progressLines.set(Nil)
|
||||
padding.set(0)
|
||||
currentLineBytes.set(new ArrayBuffer[Byte])
|
||||
}
|
||||
private[util] def clearBytes(): Unit = {
|
||||
val pad = padding.get
|
||||
if (currentLineBytes.get.isEmpty && pad > 0) padding.decrementAndGet()
|
||||
currentLineBytes.set(new ArrayBuffer[Byte])
|
||||
}
|
||||
|
||||
private[util] def addBytes(terminal: Terminal, bytes: ArrayBuffer[Byte]): Unit = {
|
||||
val previous = currentLineBytes.get
|
||||
val padding = this.padding.get
|
||||
val prevLineCount = if (padding > 0) terminal.lineCount(new String(previous.toArray)) else 0
|
||||
previous ++= bytes
|
||||
if (padding > 0) {
|
||||
val newLineCount = terminal.lineCount(new String(previous.toArray))
|
||||
val diff = newLineCount - prevLineCount
|
||||
this.padding.set(math.max(padding - diff, 0))
|
||||
}
|
||||
}
|
||||
|
||||
private[util] def printPrompt(terminal: Terminal, printStream: PrintStream): Unit =
|
||||
if (terminal.prompt != Prompt.Running && terminal.prompt != Prompt.Batch) {
|
||||
val prefix = if (terminal.isAnsiSupported) s"$DeleteLine$CursorLeft1000" else ""
|
||||
val pmpt = prefix.getBytes ++ terminal.prompt.render().getBytes
|
||||
pmpt.foreach(b => printStream.write(b & 0xFF))
|
||||
}
|
||||
private[util] def reprint(terminal: Terminal, printStream: PrintStream): Unit = {
|
||||
printPrompt(terminal, printStream)
|
||||
if (progressLines.get.nonEmpty) {
|
||||
val lines = printProgress(terminal, terminal.getLastLine.getOrElse(""))
|
||||
printStream.print(ClearScreenAfterCursor + lines)
|
||||
}
|
||||
}
|
||||
|
||||
private[util] def printProgress(
|
||||
terminal: Terminal,
|
||||
lastLine: String
|
||||
): String = {
|
||||
val previousLines = progressLines.get
|
||||
if (previousLines.nonEmpty) {
|
||||
val currentLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
||||
val (height, width) = terminal.getLineHeightAndWidth(lastLine)
|
||||
val left = cursorLeft(1000) // resets the position to the left
|
||||
val offset = width > 0
|
||||
val pad = math.max(padding.get - height, 0)
|
||||
val start = (if (offset) "\n" else "")
|
||||
val totalSize = currentLength + blankZone + pad
|
||||
val blank = left + s"\n$DeleteLine" * (totalSize - currentLength)
|
||||
val lines = previousLines.mkString(DeleteLine, s"\n$DeleteLine", s"\n$DeleteLine")
|
||||
val resetCursorUp = cursorUp(totalSize + (if (offset) 1 else 0))
|
||||
val resetCursor = resetCursorUp + left + lastLine
|
||||
start + blank + lines + resetCursor
|
||||
} else {
|
||||
ClearScreenAfterCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] object ProgressState {
|
||||
|
||||
/**
|
||||
* Receives a new task report and replaces the old one. In the event that the new
|
||||
* report has fewer lines than the previous report, padding lines are added on top
|
||||
* so that the console log lines remain contiguous. When a console line is printed
|
||||
* at the info or greater level, we can decrement the padding because the console
|
||||
* line will have filled in the blank line.
|
||||
*/
|
||||
private[sbt] def updateProgressState(
|
||||
pe: ProgressEvent,
|
||||
terminal: Terminal
|
||||
): Unit = {
|
||||
val state = terminal.progressState
|
||||
val isRunning = terminal.prompt == Prompt.Running
|
||||
val isBatch = terminal.prompt == Prompt.Batch
|
||||
val isWatch = terminal.prompt == Prompt.Watch
|
||||
if (terminal.isSupershellEnabled) {
|
||||
if (!pe.skipIfActive.getOrElse(false) || (!isRunning && !isBatch)) {
|
||||
terminal.withPrintStream { ps =>
|
||||
val info = if (isRunning || isBatch && pe.channelName.fold(true)(_ == terminal.name)) {
|
||||
pe.items.map { item =>
|
||||
val elapsed = item.elapsedMicros / 1000000L
|
||||
s" | => ${item.name} ${elapsed}s"
|
||||
}
|
||||
} else {
|
||||
pe.command.toSeq.flatMap { cmd =>
|
||||
val tail = if (isWatch) Nil else "enter 'cancel' to stop evaluation" :: Nil
|
||||
s"sbt server is running '$cmd'" :: tail
|
||||
}
|
||||
}
|
||||
|
||||
val currentLength = info.foldLeft(0)(_ + terminal.lineCount(_))
|
||||
val previousLines = state.progressLines.getAndSet(info)
|
||||
val prevLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
||||
val lastLine = terminal.prompt match {
|
||||
case Prompt.Running | Prompt.Batch => terminal.getLastLine.getOrElse("")
|
||||
case a => a.render()
|
||||
}
|
||||
val prevSize = prevLength + state.padding.get
|
||||
|
||||
val newPadding = math.max(0, prevSize - currentLength)
|
||||
state.padding.set(newPadding)
|
||||
state.printPrompt(terminal, ps)
|
||||
ps.print(state.printProgress(terminal, lastLine))
|
||||
ps.flush()
|
||||
}
|
||||
} else if (state.progressLines.get.nonEmpty) {
|
||||
state.progressLines.set(Nil)
|
||||
terminal.withPrintStream { ps =>
|
||||
val lastLine = terminal.getLastLine.getOrElse("")
|
||||
ps.print(lastLine + ClearScreenAfterCursor)
|
||||
ps.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,9 +181,10 @@ object EscHelpers {
|
|||
else res(index) = 32
|
||||
case 'm' =>
|
||||
case ';' => state = csi
|
||||
case _ =>
|
||||
case b => state = csi
|
||||
}
|
||||
digit.clear()
|
||||
case b if state == esc => state = 0
|
||||
case b =>
|
||||
res(index) = b
|
||||
index += 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.{ EOFException, InputStream, OutputStream, PrintWriter }
|
||||
import java.nio.charset.Charset
|
||||
import java.util.{ Arrays, EnumSet }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import org.jline.utils.InfoCmp.Capability
|
||||
import org.jline.utils.{ NonBlocking, OSUtils }
|
||||
import org.jline.terminal.{ Attributes, Size, Terminal => JTerminal }
|
||||
import org.jline.terminal.Terminal.SignalHandler
|
||||
import org.jline.terminal.impl.AbstractTerminal
|
||||
import org.jline.terminal.impl.jansi.JansiSupportImpl
|
||||
import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.util.Try
|
||||
|
||||
private[util] object JLine3 {
|
||||
private val capabilityMap = Capability
|
||||
.values()
|
||||
.map { c =>
|
||||
c.toString -> c
|
||||
}
|
||||
.toMap
|
||||
|
||||
private[util] def system = {
|
||||
/*
|
||||
* For reasons that are unclear to me, TerminalBuilder fails to build
|
||||
* windows terminals. The instructions about the classpath did not work:
|
||||
* https://stackoverflow.com/questions/52851232/jline3-issues-with-windows-terminal
|
||||
* We can deconstruct what TerminalBuilder does and inline it for now.
|
||||
* It is possible that this workaround will break WSL but I haven't checked that.
|
||||
*/
|
||||
if (Util.isNonCygwinWindows) {
|
||||
val support = new JansiSupportImpl
|
||||
val winConsole = support.isWindowsConsole();
|
||||
try {
|
||||
val term = JansiWinSysTerminal.createTerminal(
|
||||
"console",
|
||||
"ansi",
|
||||
OSUtils.IS_CONEMU,
|
||||
Charset.forName("UTF-8"),
|
||||
-1,
|
||||
false,
|
||||
SignalHandler.SIG_DFL,
|
||||
true
|
||||
)
|
||||
term.disableScrolling()
|
||||
term
|
||||
} catch {
|
||||
case _: Exception =>
|
||||
org.jline.terminal.TerminalBuilder
|
||||
.builder()
|
||||
.system(false)
|
||||
.paused(true)
|
||||
.jansi(true)
|
||||
.streams(Terminal.console.inputStream, Terminal.console.outputStream)
|
||||
.build()
|
||||
}
|
||||
} else {
|
||||
org.jline.terminal.TerminalBuilder
|
||||
.builder()
|
||||
.system(System.console != null)
|
||||
.paused(true)
|
||||
.jna(false)
|
||||
.jansi(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
private[sbt] def apply(term: Terminal): JTerminal = {
|
||||
new AbstractTerminal(term.name, "ansi", Charset.forName("UTF-8"), SignalHandler.SIG_DFL) {
|
||||
val closed = new AtomicBoolean(false)
|
||||
setOnClose { () =>
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
// This is necessary to shutdown the non blocking input reader
|
||||
// so that it doesn't keep blocking
|
||||
term.inputStream match {
|
||||
case w: Terminal.WriteableInputStream => w.cancel()
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
parseInfoCmp()
|
||||
override val input: InputStream = new InputStream {
|
||||
override def read: Int = {
|
||||
val res = try term.inputStream.read
|
||||
catch { case _: InterruptedException => -2 }
|
||||
if (res == 4 && term.prompt.render().endsWith(term.prompt.mkPrompt()))
|
||||
throw new EOFException
|
||||
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 =>
|
||||
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 writer: PrintWriter = new PrintWriter(output, true)
|
||||
/*
|
||||
* For now assume that the terminal capabilities for client and server
|
||||
* are the same.
|
||||
*/
|
||||
override def getStringCapability(cap: Capability): String = {
|
||||
term.getStringCapability(cap.toString, jline3 = true)
|
||||
}
|
||||
override def getNumericCapability(cap: Capability): Integer = {
|
||||
term.getNumericCapability(cap.toString, jline3 = true)
|
||||
}
|
||||
override def getBooleanCapability(cap: Capability): Boolean = {
|
||||
term.getBooleanCapability(cap.toString, jline3 = true)
|
||||
}
|
||||
def getAttributes(): Attributes = attributesFromMap(term.getAttributes)
|
||||
def getSize(): Size = new Size(term.getWidth, term.getHeight)
|
||||
def setAttributes(a: Attributes): Unit = term.setAttributes(toMap(a))
|
||||
def setSize(size: Size): Unit = term.setSize(size.getColumns, size.getRows)
|
||||
|
||||
/**
|
||||
* Override enterRawMode because the default implementation modifies System.in
|
||||
* to be non-blocking which means it immediately returns -1 if there is no
|
||||
* data available, which is not desirable for us.
|
||||
*/
|
||||
override def enterRawMode(): Attributes = enterRawModeImpl(this)
|
||||
}
|
||||
}
|
||||
private def enterRawModeImpl(term: JTerminal): Attributes = {
|
||||
val prvAttr = term.getAttributes()
|
||||
val newAttr = new Attributes(prvAttr)
|
||||
newAttr.setLocalFlags(
|
||||
EnumSet
|
||||
.of(Attributes.LocalFlag.ICANON, Attributes.LocalFlag.ECHO, Attributes.LocalFlag.IEXTEN),
|
||||
false
|
||||
)
|
||||
newAttr.setInputFlags(
|
||||
EnumSet
|
||||
.of(Attributes.InputFlag.IXON, Attributes.InputFlag.ICRNL, Attributes.InputFlag.INLCR),
|
||||
false
|
||||
)
|
||||
term.setAttributes(newAttr)
|
||||
prvAttr
|
||||
}
|
||||
private[util] def enterRawMode(term: JTerminal): Map[String, String] =
|
||||
toMap(enterRawModeImpl(term))
|
||||
private[util] def toMap(jattributes: Attributes): Map[String, String] = {
|
||||
val result = new java.util.LinkedHashMap[String, String]
|
||||
result.put(
|
||||
"iflag",
|
||||
jattributes.getInputFlags.iterator.asScala.map(_.name.toLowerCase).mkString(" ")
|
||||
)
|
||||
result.put(
|
||||
"oflag",
|
||||
jattributes.getOutputFlags.iterator.asScala.map(_.name.toLowerCase).mkString(" ")
|
||||
)
|
||||
result.put(
|
||||
"cflag",
|
||||
jattributes.getControlFlags.iterator.asScala.map(_.name.toLowerCase).mkString(" ")
|
||||
)
|
||||
result.put(
|
||||
"lflag",
|
||||
jattributes.getLocalFlags.iterator.asScala.map(_.name.toLowerCase).mkString(" ")
|
||||
)
|
||||
result.put(
|
||||
"cchars",
|
||||
jattributes.getControlChars.entrySet.iterator.asScala
|
||||
.map { e =>
|
||||
s"${e.getKey.name.toLowerCase},${e.getValue}"
|
||||
}
|
||||
.mkString(" ")
|
||||
)
|
||||
result.asScala.toMap
|
||||
}
|
||||
private[this] val iflagMap: Map[String, Attributes.InputFlag] =
|
||||
Attributes.InputFlag.values.map(f => f.name.toLowerCase -> f).toMap
|
||||
private[this] val oflagMap: Map[String, Attributes.OutputFlag] =
|
||||
Attributes.OutputFlag.values.map(f => f.name.toLowerCase -> f).toMap
|
||||
private[this] val cflagMap: Map[String, Attributes.ControlFlag] =
|
||||
Attributes.ControlFlag.values.map(f => f.name.toLowerCase -> f).toMap
|
||||
private[this] val lflagMap: Map[String, Attributes.LocalFlag] =
|
||||
Attributes.LocalFlag.values.map(f => f.name.toLowerCase -> f).toMap
|
||||
private[this] val charMap: Map[String, Attributes.ControlChar] =
|
||||
Attributes.ControlChar.values().map(f => f.name.toLowerCase -> f).toMap
|
||||
private[util] def attributesFromMap(map: Map[String, String]): Attributes = {
|
||||
val attributes = new Attributes
|
||||
map.get("iflag").foreach { flags =>
|
||||
flags.split(" ").foreach(f => iflagMap.get(f).foreach(attributes.setInputFlag(_, true)))
|
||||
}
|
||||
map.get("oflag").foreach { flags =>
|
||||
flags.split(" ").foreach(f => oflagMap.get(f).foreach(attributes.setOutputFlag(_, true)))
|
||||
}
|
||||
map.get("cflag").foreach { flags =>
|
||||
flags.split(" ").foreach(f => cflagMap.get(f).foreach(attributes.setControlFlag(_, true)))
|
||||
}
|
||||
map.get("lflag").foreach { flags =>
|
||||
flags.split(" ").foreach(f => lflagMap.get(f).foreach(attributes.setLocalFlag(_, true)))
|
||||
}
|
||||
map.get("cchars").foreach { chars =>
|
||||
chars.split(" ").foreach { keyValue =>
|
||||
keyValue.split(",") match {
|
||||
case Array(k, v) =>
|
||||
Try(v.toInt).foreach(i => charMap.get(k).foreach(c => attributes.setControlChar(c, i)))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
attributes
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.PrintStream
|
||||
import java.util.concurrent.ArrayBlockingQueue
|
||||
import java.util.concurrent.atomic.{ AtomicInteger, AtomicReference }
|
||||
|
||||
import sbt.internal.util.ConsoleAppender.{
|
||||
ClearScreenAfterCursor,
|
||||
CursorLeft1000,
|
||||
DeleteLine,
|
||||
cursorUp
|
||||
}
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
private[sbt] final class ProgressState(
|
||||
val progressLines: AtomicReference[Seq[String]],
|
||||
val padding: AtomicInteger,
|
||||
val blankZone: Int,
|
||||
val currentLineBytes: AtomicReference[ArrayBuffer[Byte]],
|
||||
) {
|
||||
def this(blankZone: Int) =
|
||||
this(
|
||||
new AtomicReference(Nil),
|
||||
new AtomicInteger(0),
|
||||
blankZone,
|
||||
new AtomicReference(new ArrayBuffer[Byte]),
|
||||
)
|
||||
def currentLine: Option[String] =
|
||||
new String(currentLineBytes.get.toArray, "UTF-8").linesIterator.toSeq.lastOption
|
||||
.map(EscHelpers.stripColorsAndMoves)
|
||||
.filter(_.nonEmpty)
|
||||
def reset(): Unit = {
|
||||
progressLines.set(Nil)
|
||||
padding.set(0)
|
||||
currentLineBytes.set(new ArrayBuffer[Byte])
|
||||
}
|
||||
private[this] val lineBuffer = new ArrayBlockingQueue[String](300)
|
||||
private[util] def getLines: Seq[String] = lineBuffer.asScala.toVector
|
||||
private[this] def appendLine(line: String) = while (!lineBuffer.offer(line)) { lineBuffer.poll }
|
||||
private[util] def clearBytes(): Unit = {
|
||||
val pad = padding.get
|
||||
if (currentLineBytes.get.isEmpty && pad > 0) padding.decrementAndGet()
|
||||
currentLineBytes.set(new ArrayBuffer[Byte])
|
||||
}
|
||||
|
||||
private[this] val lineSeparatorBytes: Array[Byte] = System.lineSeparator.getBytes("UTF-8")
|
||||
private[util] def addBytes(terminal: Terminal, bytes: Seq[Byte]): Unit = {
|
||||
val previous: ArrayBuffer[Byte] = currentLineBytes.get
|
||||
val padding = this.padding.get
|
||||
val prevLineCount = if (padding > 0) terminal.lineCount(new String(previous.toArray)) else 0
|
||||
previous ++= bytes
|
||||
if (padding > 0) {
|
||||
val newLineCount = terminal.lineCount(new String(previous.toArray))
|
||||
val diff = newLineCount - prevLineCount
|
||||
this.padding.set(math.max(padding - diff, 0))
|
||||
}
|
||||
val lines = new String(previous.toArray, "UTF-8")
|
||||
if (lines.contains(System.lineSeparator)) {
|
||||
currentLineBytes.set(new ArrayBuffer[Byte])
|
||||
if (!lines.endsWith(System.lineSeparator)) {
|
||||
val allLines = lines.split(System.lineSeparator)
|
||||
allLines.dropRight(1).foreach(appendLine)
|
||||
allLines.lastOption
|
||||
.foreach(currentLineBytes.get ++= _.getBytes("UTF-8"))
|
||||
} else if (lines.contains(System.lineSeparator)) {
|
||||
lines.split(System.lineSeparator).foreach(appendLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[util] def printPrompt(terminal: Terminal, printStream: PrintStream): Unit =
|
||||
if (terminal.prompt != Prompt.Running && terminal.prompt != Prompt.Batch) {
|
||||
val prefix = if (terminal.isAnsiSupported) s"$DeleteLine$CursorLeft1000" else ""
|
||||
val pmpt = prefix.getBytes ++ terminal.prompt.render().getBytes
|
||||
pmpt.foreach(b => printStream.write(b & 0xFF))
|
||||
}
|
||||
private[util] def write(
|
||||
terminal: Terminal,
|
||||
bytes: Array[Byte],
|
||||
printStream: PrintStream,
|
||||
hasProgress: Boolean
|
||||
): Unit = {
|
||||
addBytes(terminal, bytes)
|
||||
if (hasProgress && terminal.prompt != Prompt.Loading) {
|
||||
terminal.prompt match {
|
||||
case a: Prompt.AskUser if a.render.nonEmpty =>
|
||||
printStream.print(System.lineSeparator + ClearScreenAfterCursor + CursorLeft1000)
|
||||
printStream.flush()
|
||||
case _ =>
|
||||
}
|
||||
printStream.write(bytes)
|
||||
printStream.write(ClearScreenAfterCursor.getBytes("UTF-8"))
|
||||
printStream.flush()
|
||||
if (bytes.endsWith(lineSeparatorBytes)) {
|
||||
if (progressLines.get.nonEmpty) {
|
||||
val lastLine = terminal.prompt match {
|
||||
case a: Prompt.AskUser => a.render()
|
||||
case _ => currentLine.getOrElse("")
|
||||
}
|
||||
val lines = printProgress(terminal, lastLine)
|
||||
printStream.print(ClearScreenAfterCursor + lines)
|
||||
}
|
||||
}
|
||||
printPrompt(terminal, printStream)
|
||||
} else printStream.write(bytes)
|
||||
}
|
||||
|
||||
private[util] def printProgress(terminal: Terminal, lastLine: String): String = {
|
||||
val previousLines = progressLines.get
|
||||
if (previousLines.nonEmpty) {
|
||||
val currentLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
||||
val (height, width) = terminal.getLineHeightAndWidth(lastLine)
|
||||
val offset = width > 0
|
||||
val pad = math.max(padding.get - height, 0)
|
||||
val start = (if (offset) s"\n$CursorLeft1000" else "")
|
||||
val totalSize = currentLength + blankZone + pad
|
||||
val blank = CursorLeft1000 + s"\n$DeleteLine" * (totalSize - currentLength)
|
||||
val lines = previousLines.mkString(DeleteLine, s"\n$DeleteLine", s"\n$DeleteLine")
|
||||
val resetCursorUp = cursorUp(totalSize + (if (offset) 1 else 0))
|
||||
val resetCursor = resetCursorUp + CursorLeft1000 + lastLine
|
||||
start + blank + lines + resetCursor
|
||||
} else {
|
||||
ClearScreenAfterCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] object ProgressState {
|
||||
|
||||
/**
|
||||
* Receives a new task report and replaces the old one. In the event that the new
|
||||
* report has fewer lines than the previous report, padding lines are added on top
|
||||
* so that the console log lines remain contiguous. When a console line is printed
|
||||
* at the info or greater level, we can decrement the padding because the console
|
||||
* line will have filled in the blank line.
|
||||
*/
|
||||
private[sbt] def updateProgressState(
|
||||
pe: ProgressEvent,
|
||||
terminal: Terminal
|
||||
): Unit = {
|
||||
val state = terminal.progressState
|
||||
val isAskUser = terminal.prompt.isInstanceOf[Prompt.AskUser]
|
||||
val isRunning = terminal.prompt == Prompt.Running
|
||||
val isBatch = terminal.prompt == Prompt.Batch
|
||||
val isWatch = terminal.prompt == Prompt.Watch
|
||||
val noPrompt = terminal.prompt == Prompt.NoPrompt
|
||||
if (terminal.isSupershellEnabled) {
|
||||
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 elapsed = item.elapsedMicros / 1000000L
|
||||
s" | => ${item.name} ${elapsed}s"
|
||||
}
|
||||
} else {
|
||||
pe.command.toSeq.flatMap { cmd =>
|
||||
val tail = if (isWatch) Nil else "enter 'cancel' to stop evaluation" :: Nil
|
||||
s"sbt server is running '$cmd'" :: tail
|
||||
}
|
||||
}
|
||||
|
||||
val currentLength = info.foldLeft(0)(_ + terminal.lineCount(_))
|
||||
val previousLines = state.progressLines.getAndSet(info)
|
||||
val prevLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
||||
val prevSize = prevLength + state.padding.get
|
||||
|
||||
val lastLine =
|
||||
if (isAskUser) terminal.prompt.render() else terminal.getLastLine.getOrElse("")
|
||||
state.padding.set(math.max(0, prevSize - currentLength))
|
||||
state.printPrompt(terminal, ps)
|
||||
ps.print(state.printProgress(terminal, lastLine))
|
||||
ps.flush()
|
||||
}
|
||||
} else if (state.progressLines.get.nonEmpty) {
|
||||
state.progressLines.set(Nil)
|
||||
terminal.withPrintStream { ps =>
|
||||
val lastLine = terminal.getLastLine.getOrElse("")
|
||||
ps.print(lastLine + ClearScreenAfterCursor)
|
||||
ps.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,41 +7,33 @@
|
|||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
private[sbt] sealed trait Prompt {
|
||||
def mkPrompt: () => String
|
||||
def render(): String
|
||||
def wrappedOutputStream(terminal: Terminal): OutputStream
|
||||
def reset(): Unit
|
||||
}
|
||||
|
||||
private[sbt] object Prompt {
|
||||
private[sbt] case class AskUser(override val mkPrompt: () => String) extends Prompt {
|
||||
private[this] val bytes = new LinkedBlockingQueue[Int]
|
||||
override def wrappedOutputStream(terminal: Terminal): OutputStream = new OutputStream {
|
||||
override def write(b: Int): Unit = {
|
||||
if (b == 10) bytes.clear()
|
||||
else bytes.put(b)
|
||||
terminal.withPrintStream { p =>
|
||||
p.write(b)
|
||||
p.flush()
|
||||
}
|
||||
}
|
||||
override def flush(): Unit = terminal.withPrintStream(_.flush())
|
||||
private[this] val bytes = new LinkedBlockingQueue[Byte]
|
||||
def write(b: Array[Byte]): Unit = b.foreach(bytes.put)
|
||||
override def render(): String = {
|
||||
val res = new String(bytes.asScala.toArray, "UTF-8")
|
||||
if (res.endsWith(System.lineSeparator)) "" else res
|
||||
}
|
||||
|
||||
override def render(): String =
|
||||
EscHelpers.stripMoves(new String(bytes.asScala.toArray.map(_.toByte)))
|
||||
override def reset(): Unit = bytes.clear()
|
||||
}
|
||||
private[sbt] trait NoPrompt extends Prompt {
|
||||
override val mkPrompt: () => String = () => ""
|
||||
override def render(): String = ""
|
||||
override def wrappedOutputStream(terminal: Terminal): OutputStream = terminal.outputStream
|
||||
override def reset(): Unit = {}
|
||||
}
|
||||
private[sbt] case object Running extends NoPrompt
|
||||
private[sbt] case object Batch extends NoPrompt
|
||||
private[sbt] case object Watch extends NoPrompt
|
||||
private[sbt] case object Loading extends NoPrompt
|
||||
private[sbt] case object NoPrompt extends NoPrompt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,19 +7,17 @@
|
|||
|
||||
package sbt.internal.util
|
||||
|
||||
import java.io.{ InputStream, OutputStream, PrintStream }
|
||||
import java.io.{ InputStream, InterruptedIOException, OutputStream, PrintStream }
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.Locale
|
||||
import java.util.{ Arrays, Locale }
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||
import java.util.concurrent.{ ConcurrentHashMap, Executors, LinkedBlockingQueue, TimeUnit }
|
||||
import java.util.concurrent.{ ArrayBlockingQueue, Executors, LinkedBlockingQueue, TimeUnit }
|
||||
|
||||
import jline.DefaultTerminal2
|
||||
import jline.console.ConsoleReader
|
||||
import sbt.internal.util.ConsoleAppender.{ ClearScreenAfterCursor, CursorLeft1000 }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.util.Try
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
trait Terminal extends AutoCloseable {
|
||||
|
||||
|
|
@ -57,9 +55,8 @@ trait Terminal extends AutoCloseable {
|
|||
def inputStream: InputStream
|
||||
|
||||
/**
|
||||
* Gets the input stream for this Terminal. This could be a wrapper around System.in for the
|
||||
* process or it could be a remote input stream for a network channel.
|
||||
* @return the input stream.
|
||||
* Gets the output stream for this Terminal.
|
||||
* @return the output stream.
|
||||
*/
|
||||
def outputStream: OutputStream
|
||||
|
||||
|
|
@ -112,21 +109,36 @@ trait Terminal extends AutoCloseable {
|
|||
*/
|
||||
private[sbt] def getLastLine: Option[String]
|
||||
|
||||
private[sbt] def getBooleanCapability(capability: String): Boolean
|
||||
private[sbt] def getNumericCapability(capability: String): Int
|
||||
private[sbt] def getStringCapability(capability: String): String
|
||||
/**
|
||||
* Returns the buffered lines that have been written to the terminal. The
|
||||
* main use case is to display the system startup log lines when a client
|
||||
* connects to a booting server. This could also be used to implement a more
|
||||
* tmux like experience where multiple clients connect to the same console.
|
||||
*
|
||||
* @return the lines
|
||||
*/
|
||||
private[sbt] def getLines: Seq[String]
|
||||
|
||||
private[sbt] def getBooleanCapability(capability: String, jline3: Boolean): Boolean
|
||||
private[sbt] def getNumericCapability(capability: String, jline3: Boolean): Integer
|
||||
private[sbt] def getStringCapability(capability: String, jline3: Boolean): String
|
||||
private[sbt] def getAttributes: Map[String, String]
|
||||
private[sbt] def setAttributes(attributes: Map[String, String]): Unit
|
||||
private[sbt] def setSize(width: Int, height: Int): Unit
|
||||
|
||||
private[sbt] def name: String
|
||||
private[sbt] def withRawSystemIn[T](f: => T): T = f
|
||||
private[sbt] def withRawInput[T](f: => T): T = f
|
||||
private[sbt] def withCanonicalIn[T](f: => T): T = f
|
||||
private[sbt] def write(bytes: Int*): Unit
|
||||
private[sbt] def printStream: PrintStream
|
||||
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] final def prompt: Prompt = promptHolder.get
|
||||
private[sbt] final def setPrompt(newPrompt: Prompt): Unit = promptHolder.set(newPrompt)
|
||||
private[sbt] final def setPrompt(newPrompt: Prompt): Unit =
|
||||
if (prompt != Prompt.NoPrompt) promptHolder.set(newPrompt)
|
||||
|
||||
/**
|
||||
* Returns the number of lines that the input string will cover given the current width of the
|
||||
|
|
@ -142,7 +154,8 @@ trait Terminal extends AutoCloseable {
|
|||
val len = l.length
|
||||
if (width > 0 && len > 0) (len - 1 + width) / width else 0
|
||||
}
|
||||
lines.tail.foldLeft(lines.headOption.fold(0)(count))(_ + count(_))
|
||||
if (lines.nonEmpty) lines.tail.foldLeft(lines.headOption.fold(0)(count))(_ + count(_))
|
||||
else 0
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -188,13 +201,13 @@ object Terminal {
|
|||
override def enableInterruptCharacter(): Unit = {}
|
||||
override def getOutputEncoding: String = null
|
||||
override def getBooleanCapability(capability: String): Boolean = {
|
||||
term.getBooleanCapability(capability)
|
||||
term.getBooleanCapability(capability, jline3 = false)
|
||||
}
|
||||
override def getNumericCapability(capability: String): Integer = {
|
||||
term.getNumericCapability(capability)
|
||||
term.getNumericCapability(capability, jline3 = false)
|
||||
}
|
||||
override def getStringCapability(capability: String): String = {
|
||||
term.getStringCapability(capability)
|
||||
term.getStringCapability(capability, jline3 = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -253,18 +266,45 @@ object Terminal {
|
|||
*/
|
||||
private[sbt] def restore(): Unit = console.toJLine.restore()
|
||||
|
||||
private[this] val hasProgress: AtomicBoolean = new AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param progress toggles whether or not the console terminal has progress
|
||||
* @param f the thunk to run
|
||||
* @tparam T the result type of the thunk
|
||||
* @return the result of the thunk
|
||||
*/
|
||||
private[sbt] def withStreams[T](f: => T): T =
|
||||
private[sbt] def withStreams[T](isServer: Boolean)(f: => T): T =
|
||||
if (System.getProperty("sbt.io.virtual", "true") == "true") {
|
||||
hasProgress.set(isServer)
|
||||
try withOut(withIn(f))
|
||||
finally {
|
||||
jline.TerminalFactory.reset()
|
||||
console.close()
|
||||
if (isServer) {
|
||||
console match {
|
||||
case c: ConsoleTerminal if !isWindows =>
|
||||
/*
|
||||
* Entering raw mode in this way causes the standard in InputStream
|
||||
* to become non-blocking. After we set it to non-blocking, we spin
|
||||
* up a thread that reads from the inputstream and the resets it
|
||||
* back to blocking mode. We can then close the console. We do
|
||||
* this on a background thread to avoid blocking sbt's exit.
|
||||
*/
|
||||
val prev = c.system.enterRawMode()
|
||||
val runnable: Runnable = () => {
|
||||
c.inputStream.read()
|
||||
c.system.setAttributes(prev)
|
||||
c.close()
|
||||
}
|
||||
val thread = new Thread(runnable, "sbt-console-background-close")
|
||||
thread.setDaemon(true)
|
||||
thread.start()
|
||||
case c => c.close()
|
||||
}
|
||||
} else {
|
||||
console.close()
|
||||
}
|
||||
}
|
||||
} else f
|
||||
|
||||
|
|
@ -281,18 +321,26 @@ object Terminal {
|
|||
override def isEchoEnabled: Boolean = t.isEchoEnabled
|
||||
override def isSuccessEnabled: Boolean = t.isSuccessEnabled
|
||||
override def isSupershellEnabled: Boolean = t.isSupershellEnabled
|
||||
override def getBooleanCapability(capability: String): Boolean =
|
||||
t.getBooleanCapability(capability)
|
||||
override def getNumericCapability(capability: String): Int = t.getNumericCapability(capability)
|
||||
override def getStringCapability(capability: String): String = t.getStringCapability(capability)
|
||||
override def withRawSystemIn[T](f: => T): T = t.withRawSystemIn(f)
|
||||
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||
t.getBooleanCapability(capability, jline3)
|
||||
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
|
||||
t.getNumericCapability(capability, jline3)
|
||||
override def getStringCapability(capability: String, jline3: Boolean): String =
|
||||
t.getStringCapability(capability, jline3)
|
||||
override private[sbt] def getAttributes: Map[String, String] = t.getAttributes
|
||||
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
|
||||
t.setAttributes(attributes)
|
||||
override private[sbt] def setSize(width: Int, height: Int): Unit = t.setSize(width, height)
|
||||
override def withRawInput[T](f: => T): T = t.withRawInput(f)
|
||||
override def withCanonicalIn[T](f: => T): T = t.withCanonicalIn(f)
|
||||
override def printStream: PrintStream = t.printStream
|
||||
override def withPrintStream[T](f: PrintStream => T): T = t.withPrintStream(f)
|
||||
override private[sbt] def withRawOutput[R](f: => R): R = t.withRawOutput(f)
|
||||
override def restore(): Unit = t.restore()
|
||||
override def close(): Unit = {}
|
||||
override private[sbt] def write(bytes: Int*): Unit = t.write(bytes: _*)
|
||||
override def getLastLine: Option[String] = t.getLastLine
|
||||
override def getLines: Seq[String] = t.getLines
|
||||
override private[sbt] def name: String = t.name
|
||||
}
|
||||
private[sbt] def get: Terminal = ProxyTerminal
|
||||
|
|
@ -322,18 +370,35 @@ object Terminal {
|
|||
}
|
||||
}
|
||||
|
||||
private[this] val originalOut = System.out
|
||||
val sepBytes = System.lineSeparator.getBytes("UTF-8")
|
||||
private class LinePrintStream(outputStream: OutputStream)
|
||||
extends PrintStream(outputStream, true) {
|
||||
override def println(s: String): Unit = synchronized {
|
||||
out.write(s.getBytes("UTF-8") ++ sepBytes)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
private[this] val originalOut = new LinePrintStream(System.out)
|
||||
private[this] val originalIn = System.in
|
||||
private[sbt] class WriteableInputStream(in: InputStream, name: String)
|
||||
extends InputStream
|
||||
with AutoCloseable {
|
||||
final def write(bytes: Int*): Unit = bytes.foreach(i => buffer.put(i))
|
||||
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))
|
||||
}
|
||||
}
|
||||
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 resultQueue = new LinkedBlockingQueue[LinkedBlockingQueue[Int]]
|
||||
private[this] val waiting = ConcurrentHashMap.newKeySet[LinkedBlockingQueue[Int]]
|
||||
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
|
||||
|
|
@ -354,11 +419,14 @@ object Terminal {
|
|||
*/
|
||||
private[this] val runnable: Runnable = () => {
|
||||
@tailrec def impl(): Unit = {
|
||||
val result = resultQueue.take
|
||||
val _ = readQueue.take
|
||||
val b = in.read
|
||||
// The downstream consumer may have been interrupted. Buffer the result
|
||||
// when that hapens.
|
||||
if (waiting.contains(result)) result.put(b) else buffer.put(b)
|
||||
waiting.poll match {
|
||||
case null => buffer.put(b)
|
||||
case q => q.put(b)
|
||||
}
|
||||
if (b != -1 && !Thread.interrupted()) impl()
|
||||
else closed.set(true)
|
||||
}
|
||||
|
|
@ -370,21 +438,28 @@ object Terminal {
|
|||
if (closed.get) -1
|
||||
else
|
||||
synchronized {
|
||||
buffer.poll match {
|
||||
readThread.set(Thread.currentThread)
|
||||
try buffer.poll match {
|
||||
case null =>
|
||||
val result = new LinkedBlockingQueue[Int]
|
||||
waiting.add(result)
|
||||
resultQueue.offer(result)
|
||||
try result.take
|
||||
val result = new LinkedBlockingQueue[Integer]
|
||||
waiting.synchronized(waiting.put(result))
|
||||
readQueue.put(())
|
||||
try result.take.toInt
|
||||
catch {
|
||||
case e: InterruptedException =>
|
||||
waiting.remove(result)
|
||||
throw e
|
||||
-1
|
||||
}
|
||||
case b if b == -1 => throw new ClosedChannelException
|
||||
case b => b
|
||||
}
|
||||
case b => b.toInt
|
||||
} finally readThread.set(null)
|
||||
}
|
||||
def cancel(): Unit = waiting.synchronized {
|
||||
Option(readThread.getAndSet(null)).foreach(_.interrupt())
|
||||
waiting.forEach(_.put(-2))
|
||||
waiting.clear()
|
||||
readQueue.clear()
|
||||
}
|
||||
|
||||
override def available(): Int = {
|
||||
buffer.size
|
||||
|
|
@ -524,7 +599,7 @@ object Terminal {
|
|||
}
|
||||
override def flush(): Unit = os.flush()
|
||||
}
|
||||
private[this] val proxyPrintStream = new PrintStream(proxyOutputStream, true) {
|
||||
private[this] val proxyPrintStream = new LinePrintStream(proxyOutputStream) {
|
||||
override def toString: String = s"proxyPrintStream($proxyOutputStream)"
|
||||
}
|
||||
private[this] lazy val isWindows =
|
||||
|
|
@ -592,9 +667,21 @@ object Terminal {
|
|||
case t: jline.Terminal2 => t
|
||||
case _ => new DefaultTerminal2(terminal)
|
||||
}
|
||||
override def init(): Unit = if (alive) terminal.init()
|
||||
override def restore(): Unit = if (alive) terminal.restore()
|
||||
override def reset(): Unit = if (alive) terminal.reset()
|
||||
override def init(): Unit =
|
||||
if (alive)
|
||||
try terminal.init()
|
||||
catch {
|
||||
case _: InterruptedException =>
|
||||
}
|
||||
override def restore(): Unit =
|
||||
if (alive)
|
||||
try terminal.restore()
|
||||
catch {
|
||||
case _: InterruptedException =>
|
||||
}
|
||||
override def reset(): Unit =
|
||||
try terminal.reset()
|
||||
catch { case _: InterruptedException => }
|
||||
override def isSupported: Boolean = terminal.isSupported
|
||||
override def getWidth: Int = props.map(_.width).getOrElse(terminal.getWidth)
|
||||
override def getHeight: Int = props.map(_.height).getOrElse(terminal.getHeight)
|
||||
|
|
@ -650,18 +737,21 @@ object Terminal {
|
|||
fixTerminalProperty()
|
||||
|
||||
private[sbt] def createReader(term: Terminal, prompt: Prompt): ConsoleReader = {
|
||||
new ConsoleReader(term.inputStream, prompt.wrappedOutputStream(term), term.toJLine) {
|
||||
new ConsoleReader(term.inputStream, term.outputStream, term.toJLine) {
|
||||
override def readLine(prompt: String, mask: Character): String =
|
||||
term.withRawSystemIn(super.readLine(prompt, mask))
|
||||
override def readLine(prompt: String): String = term.withRawSystemIn(super.readLine(prompt))
|
||||
term.withRawInput(super.readLine(prompt, mask))
|
||||
override def readLine(prompt: String): String = term.withRawInput(super.readLine(prompt))
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] def console: Terminal = consoleTerminalHolder.get match {
|
||||
def console: Terminal = consoleTerminalHolder.get match {
|
||||
case null => throw new IllegalStateException("Uninitialized terminal.")
|
||||
case term => term
|
||||
}
|
||||
|
||||
private val capabilityMap =
|
||||
org.jline.utils.InfoCmp.Capability.values().map(c => c.toString -> c).toMap
|
||||
|
||||
@deprecated("For compatibility only", "1.4.0")
|
||||
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
|
||||
private class ConsoleTerminal(
|
||||
|
|
@ -669,28 +759,36 @@ object Terminal {
|
|||
in: InputStream,
|
||||
out: OutputStream
|
||||
) extends TerminalImpl(in, out, "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 = term.getWidth
|
||||
override def getHeight: Int = term.getHeight
|
||||
override def getWidth: Int = system.getSize.getColumns
|
||||
override def getHeight: Int = system.getSize.getRows
|
||||
override def isAnsiSupported: Boolean = term.isAnsiSupported && !isCI
|
||||
override def isEchoEnabled: Boolean = term.isEchoEnabled
|
||||
override def isEchoEnabled: Boolean = system.echo()
|
||||
override def isSuccessEnabled: Boolean = true
|
||||
override def getBooleanCapability(capability: String): Boolean =
|
||||
term.getBooleanCapability(capability)
|
||||
override def getNumericCapability(capability: String): Int =
|
||||
term.getNumericCapability(capability)
|
||||
override def getStringCapability(capability: String): String =
|
||||
term.getStringCapability(capability)
|
||||
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||
if (jline3) capabilityMap.get(capability).fold(false)(system.getBooleanCapability)
|
||||
else term.getBooleanCapability(capability)
|
||||
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
|
||||
if (jline3) capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability)
|
||||
else term.getNumericCapability(capability)
|
||||
override def getStringCapability(capability: String, jline3: Boolean): String =
|
||||
if (jline3) capabilityMap.get(capability).fold(null: String)(system.getStringCapability)
|
||||
else term.getStringCapability(capability)
|
||||
override private[sbt] def restore(): Unit = term.restore()
|
||||
|
||||
override def withRawSystemIn[T](f: => T): T = term.synchronized {
|
||||
try {
|
||||
term.init()
|
||||
term.setEchoEnabled(false)
|
||||
f
|
||||
} finally {
|
||||
term.restore()
|
||||
term.setEchoEnabled(true)
|
||||
override private[sbt] def getAttributes: Map[String, String] =
|
||||
JLine3.toMap(system.getAttributes)
|
||||
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 =
|
||||
system.setSize(new org.jline.terminal.Size(width, height))
|
||||
|
||||
override def withRawInput[T](f: => T): T = term.synchronized {
|
||||
val prev = JLine3.enterRawMode(system)
|
||||
try f
|
||||
catch { case _: InterruptedIOException => throw new InterruptedException } finally {
|
||||
setAttributes(prev)
|
||||
}
|
||||
}
|
||||
override def isColorEnabled: Boolean =
|
||||
|
|
@ -705,29 +803,35 @@ object Terminal {
|
|||
case "true" => true
|
||||
case _ => false
|
||||
})
|
||||
override def close(): Unit = {
|
||||
try system.close()
|
||||
catch { case NonFatal(_) => }
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
private[sbt] abstract class TerminalImpl private[sbt] (
|
||||
val in: InputStream,
|
||||
val out: OutputStream,
|
||||
override private[sbt] val name: String
|
||||
) extends Terminal {
|
||||
private[this] val directWrite = new AtomicBoolean(false)
|
||||
private[this] val currentLine = new AtomicReference(new ArrayBuffer[Byte])
|
||||
private[this] val lineBuffer = new LinkedBlockingQueue[Byte]
|
||||
private[this] val flushQueue = new LinkedBlockingQueue[Seq[Byte]]
|
||||
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
|
||||
|
||||
private val combinedOutputStream = new OutputStream {
|
||||
override def write(b: Int): Unit = {
|
||||
Option(bootOutputStreamHolder.get).foreach(_.write(b))
|
||||
out.write(b)
|
||||
}
|
||||
override def write(b: Array[Byte]): Unit = write(b, 0, b.length)
|
||||
override def write(b: Array[Byte]): Unit = {
|
||||
write(b, 0, b.length)
|
||||
}
|
||||
override def write(b: Array[Byte], offset: Int, len: Int): Unit = {
|
||||
Option(bootOutputStreamHolder.get).foreach(_.write(b, offset, len))
|
||||
out.write(b, offset, len)
|
||||
|
|
@ -740,54 +844,19 @@ object Terminal {
|
|||
|
||||
override val outputStream = new OutputStream {
|
||||
override def write(b: Int): Unit = throwIfClosed {
|
||||
writeLock.synchronized {
|
||||
if (b == Int.MinValue) currentLine.set(new ArrayBuffer[Byte])
|
||||
else doWrite(Vector((b & 0xFF).toByte))
|
||||
if (b == 10) combinedOutputStream.flush()
|
||||
}
|
||||
write(Array((b & 0xFF).toByte))
|
||||
}
|
||||
override def write(b: Array[Byte]): Unit = throwIfClosed(write(b, 0, b.length))
|
||||
override def write(b: Array[Byte], off: Int, len: Int): Unit = {
|
||||
throwIfClosed {
|
||||
writeLock.synchronized {
|
||||
val lo = math.max(0, off)
|
||||
val hi = math.min(math.max(off + len, 0), b.length)
|
||||
doWrite(b.slice(off, off + len).toSeq)
|
||||
}
|
||||
}
|
||||
override def write(b: Array[Byte]): Unit = throwIfClosed {
|
||||
writeLock.synchronized(doWrite(b))
|
||||
}
|
||||
override def write(b: Array[Byte], offset: Int, length: Int): Unit = throwIfClosed {
|
||||
write(Arrays.copyOfRange(b, offset, offset + length))
|
||||
}
|
||||
override def flush(): Unit = combinedOutputStream.flush()
|
||||
private[this] val clear = s"$CursorLeft1000$ClearScreenAfterCursor"
|
||||
private def doWrite(bytes: Seq[Byte]): Unit = {
|
||||
def doWrite(b: Byte): Unit = out.write(b & 0xFF)
|
||||
val remaining = bytes.foldLeft(new ArrayBuffer[Byte]) { (buf, i) =>
|
||||
if (i == 10) {
|
||||
progressState.addBytes(TerminalImpl.this, buf)
|
||||
progressState.clearBytes()
|
||||
val cl = currentLine.get
|
||||
if (buf.nonEmpty && isAnsiSupported && cl.isEmpty) clear.getBytes.foreach(doWrite)
|
||||
combinedOutputStream.write(buf.toArray)
|
||||
combinedOutputStream.write(10)
|
||||
currentLine.get match {
|
||||
case s if s.nonEmpty => currentLine.set(new ArrayBuffer[Byte])
|
||||
case _ =>
|
||||
}
|
||||
progressState.reprint(TerminalImpl.this, rawPrintStream)
|
||||
new ArrayBuffer[Byte]
|
||||
} else buf += i
|
||||
}
|
||||
if (remaining.nonEmpty) {
|
||||
val cl = currentLine.get
|
||||
if (isAnsiSupported && cl.isEmpty) {
|
||||
clear.getBytes.foreach(doWrite)
|
||||
}
|
||||
cl ++= remaining
|
||||
combinedOutputStream.write(remaining.toArray)
|
||||
}
|
||||
combinedOutputStream.flush()
|
||||
}
|
||||
}
|
||||
override private[sbt] val printStream: PrintStream = new PrintStream(outputStream, true)
|
||||
private def doWrite(bytes: Array[Byte]): Unit =
|
||||
progressState.write(TerminalImpl.this, bytes, rawPrintStream, hasProgress.get && !rawMode.get)
|
||||
override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream)
|
||||
override def inputStream: InputStream = writeableInputStream
|
||||
|
||||
private[sbt] def write(bytes: Int*): Unit = writeableInputStream.write(bytes: _*)
|
||||
|
|
@ -801,17 +870,12 @@ object Terminal {
|
|||
case _ => (0, 0)
|
||||
}
|
||||
|
||||
override def getLastLine: Option[String] = currentLine.get match {
|
||||
case bytes if bytes.isEmpty => None
|
||||
case bytes =>
|
||||
// TODO there are ghost characters when the user deletes prompt characters
|
||||
// when they are given the cancellation option
|
||||
Some(new String(bytes.toArray).replaceAllLiterally(ClearScreenAfterCursor, ""))
|
||||
}
|
||||
|
||||
private[this] val rawPrintStream: PrintStream = new PrintStream(combinedOutputStream, true) {
|
||||
override def close(): Unit = {}
|
||||
private[sbt] def withRawOutput[R](f: => R): R = {
|
||||
rawMode.set(true)
|
||||
try f
|
||||
finally rawMode.set(false)
|
||||
}
|
||||
private[this] val rawPrintStream: PrintStream = new LinePrintStream(combinedOutputStream)
|
||||
override def withPrintStream[T](f: PrintStream => T): T =
|
||||
writeLock.synchronized(f(rawPrintStream))
|
||||
|
||||
|
|
@ -821,12 +885,13 @@ object Terminal {
|
|||
}
|
||||
private[sbt] val NullTerminal = new Terminal {
|
||||
override def close(): Unit = {}
|
||||
override def getBooleanCapability(capability: String): Boolean = false
|
||||
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = false
|
||||
override def getHeight: Int = 0
|
||||
override def getLastLine: Option[String] = None
|
||||
override def getLines: Seq[String] = Nil
|
||||
override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0)
|
||||
override def getNumericCapability(capability: String): Int = -1
|
||||
override def getStringCapability(capability: String): String = null
|
||||
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)
|
||||
|
|
@ -839,10 +904,14 @@ object Terminal {
|
|||
override def isSuccessEnabled: Boolean = false
|
||||
override def isSupershellEnabled: Boolean = false
|
||||
override def outputStream: java.io.OutputStream = _ => {}
|
||||
override private[sbt] def getAttributes: Map[String, String] = Map.empty
|
||||
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit = {}
|
||||
override private[sbt] def setSize(width: Int, height: Int): Unit = {}
|
||||
override private[sbt] def name: String = "NullTerminal"
|
||||
override private[sbt] val printStream: java.io.PrintStream =
|
||||
new PrintStream(outputStream, false)
|
||||
override private[sbt] def withPrintStream[T](f: java.io.PrintStream => T): T = f(printStream)
|
||||
override private[sbt] def write(bytes: Int*): Unit = {}
|
||||
override private[sbt] def withRawOutput[R](f: => R): R = f
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,4 +63,11 @@ class CleanStringSpec extends FlatSpec {
|
|||
val colorArrow = new String(Array[Byte](27, 91, 51, 54, 109, 62))
|
||||
assert(EscHelpers.stripMoves(original) == "foo" + colorArrow + " " + scala.Console.RESET)
|
||||
}
|
||||
it should "remove unusual escape characters" in {
|
||||
val original = new String(
|
||||
Array[Byte](27, 91, 63, 49, 108, 27, 62, 27, 91, 63, 49, 48, 48, 48, 108, 27, 91, 63, 50, 48,
|
||||
48, 52, 108)
|
||||
)
|
||||
assert(EscHelpers.stripColorsAndMoves(original).isEmpty)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ final class Console(compiler: AnalyzingCompiler) {
|
|||
val previous = sys.props.get("scala.color").getOrElse("auto")
|
||||
try {
|
||||
sys.props("scala.color") = if (terminal.isColorEnabled) "true" else "false"
|
||||
terminal.withRawSystemIn(Run.executeTrapExit(console0, log))
|
||||
terminal.withRawOutput {
|
||||
terminal.withRawInput(Run.executeTrapExit(console0, log))
|
||||
}
|
||||
} finally {
|
||||
sys.props("scala.color") = previous
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import org.scalasbt.ipcsocket.UnixDomainSocket;
|
|||
import org.scalasbt.ipcsocket.Win32NamedPipeServerSocket;
|
||||
import org.scalasbt.ipcsocket.Win32NamedPipeSocket;
|
||||
import org.scalasbt.ipcsocket.Win32SecurityLevel;
|
||||
import sbt.internal.util.Terminal;
|
||||
import xsbti.AppConfiguration;
|
||||
|
||||
/**
|
||||
|
|
@ -102,6 +103,16 @@ public class BootServerSocket implements AutoCloseable {
|
|||
service.submit(
|
||||
() -> {
|
||||
try {
|
||||
Terminal.console()
|
||||
.getLines()
|
||||
.foreach(
|
||||
l -> {
|
||||
try {
|
||||
write((l + System.lineSeparator()).getBytes("UTF-8"));
|
||||
} catch (final IOException e) {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
final InputStream inputStream = socket.getInputStream();
|
||||
while (alive.get()) {
|
||||
try {
|
||||
|
|
@ -134,6 +145,15 @@ public class BootServerSocket implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
private void write(final byte[] b) {
|
||||
try {
|
||||
if (alive.get()) socket.getOutputStream().write(b);
|
||||
} catch (final IOException e) {
|
||||
alive.set(false);
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private void write(final byte[] b, final int offset, final int len) {
|
||||
try {
|
||||
if (alive.get()) socket.getOutputStream().write(b, offset, len);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ private[sbt] final class ConsoleChannel(
|
|||
|
||||
override val userThread: UserThread = new UserThread(this)
|
||||
private[sbt] def terminal = Terminal.console
|
||||
if (System.console == null) terminal.setPrompt(Prompt.NoPrompt)
|
||||
}
|
||||
private[sbt] object ConsoleChannel {
|
||||
private[sbt] def defaultName = "console0"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ package client
|
|||
import java.io.{ File, IOException, InputStream, PrintStream }
|
||||
import java.lang.ProcessBuilder.Redirect
|
||||
import java.net.Socket
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import java.nio.file.Files
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||
|
|
@ -43,10 +42,14 @@ import Serialization.{
|
|||
promptChannel,
|
||||
systemIn,
|
||||
systemOut,
|
||||
systemOutFlush,
|
||||
terminalCapabilities,
|
||||
terminalCapabilitiesResponse,
|
||||
terminalPropertiesQuery,
|
||||
terminalPropertiesResponse
|
||||
terminalPropertiesResponse,
|
||||
getTerminalAttributes,
|
||||
setTerminalAttributes,
|
||||
setTerminalSize,
|
||||
}
|
||||
import NetworkClient.Arguments
|
||||
|
||||
|
|
@ -199,7 +202,6 @@ class NetworkClient(
|
|||
case _ => (false, None)
|
||||
}
|
||||
if (rebootCommands.nonEmpty) {
|
||||
if (Terminal.console.getLastLine.isDefined) Terminal.console.printStream.println()
|
||||
rebooting.set(true)
|
||||
attached.set(false)
|
||||
connectionHolder.getAndSet(null) match {
|
||||
|
|
@ -212,7 +214,7 @@ class NetworkClient(
|
|||
rebooting.set(false)
|
||||
rebootCommands match {
|
||||
case Some((execId, cmd)) if execId.nonEmpty =>
|
||||
if (batchMode.get && !pendingResults.contains(execId) && cmd.isEmpty) {
|
||||
if (batchMode.get && !pendingResults.containsKey(execId) && cmd.nonEmpty) {
|
||||
console.appendLog(
|
||||
Level.Error,
|
||||
s"received request to re-run unknown command '$cmd' after reboot"
|
||||
|
|
@ -230,8 +232,6 @@ class NetworkClient(
|
|||
} else {
|
||||
if (!rebooting.get() && running.compareAndSet(true, false) && log) {
|
||||
if (!arguments.commandArguments.contains(Shutdown)) {
|
||||
if (Terminal.console.getLastLine.isDefined)
|
||||
Terminal.console.printStream.println()
|
||||
console.appendLog(Level.Error, "sbt server disconnected")
|
||||
exitClean.set(false)
|
||||
}
|
||||
|
|
@ -306,7 +306,6 @@ class NetworkClient(
|
|||
Some(process)
|
||||
case _ =>
|
||||
if (log) {
|
||||
if (Terminal.console.getLastLine.isDefined) Terminal.console.printStream.println()
|
||||
console.appendLog(Level.Info, "sbt server is booting up")
|
||||
}
|
||||
None
|
||||
|
|
@ -522,17 +521,15 @@ class NetworkClient(
|
|||
}
|
||||
} else Vector()
|
||||
case (`systemOut`, Some(json)) =>
|
||||
Converter.fromJson[Seq[Byte]](json) match {
|
||||
case Success(params) =>
|
||||
if (params.nonEmpty) {
|
||||
if (attached.get) {
|
||||
printStream.write(params.toArray)
|
||||
printStream.flush()
|
||||
}
|
||||
}
|
||||
case Failure(_) =>
|
||||
Converter.fromJson[Array[Byte]](json) match {
|
||||
case Success(bytes) if bytes.nonEmpty && attached.get =>
|
||||
synchronized(printStream.write(bytes))
|
||||
case _ =>
|
||||
}
|
||||
Vector.empty
|
||||
case (`systemOutFlush`, _) =>
|
||||
synchronized(printStream.flush())
|
||||
Vector.empty
|
||||
case (`promptChannel`, _) =>
|
||||
batchMode.set(false)
|
||||
Vector.empty
|
||||
|
|
@ -589,16 +586,23 @@ class NetworkClient(
|
|||
}
|
||||
|
||||
def onRequest(msg: JsonRpcRequestMessage): Unit = {
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
(msg.method, msg.params) match {
|
||||
case (`terminalCapabilities`, Some(json)) =>
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
Converter.fromJson[TerminalCapabilitiesQuery](json) match {
|
||||
case Success(terminalCapabilitiesQuery) =>
|
||||
val jline3 = terminalCapabilitiesQuery.jline3
|
||||
val response = TerminalCapabilitiesResponse(
|
||||
terminalCapabilitiesQuery.boolean.map(Terminal.console.getBooleanCapability),
|
||||
terminalCapabilitiesQuery.numeric.map(Terminal.console.getNumericCapability),
|
||||
terminalCapabilitiesQuery.boolean
|
||||
.map(Terminal.console.getBooleanCapability(_, jline3)),
|
||||
terminalCapabilitiesQuery.numeric
|
||||
.map(
|
||||
c => Option(Terminal.console.getNumericCapability(c, jline3)).fold(-1)(_.toInt)
|
||||
),
|
||||
terminalCapabilitiesQuery.string
|
||||
.map(s => Option(Terminal.console.getStringCapability(s)).getOrElse("null")),
|
||||
.map(
|
||||
s => Option(Terminal.console.getStringCapability(s, jline3)).getOrElse("null")
|
||||
),
|
||||
)
|
||||
sendCommandResponse(
|
||||
terminalCapabilitiesResponse,
|
||||
|
|
@ -617,6 +621,37 @@ class NetworkClient(
|
|||
isEchoEnabled = Terminal.console.isEchoEnabled
|
||||
)
|
||||
sendCommandResponse(terminalPropertiesResponse, response, msg.id)
|
||||
case (`setTerminalAttributes`, Some(json)) =>
|
||||
Converter.fromJson[TerminalSetAttributesCommand](json) match {
|
||||
case Success(attributes) =>
|
||||
val attrs = Map(
|
||||
"iflag" -> attributes.iflag,
|
||||
"oflag" -> attributes.oflag,
|
||||
"cflag" -> attributes.cflag,
|
||||
"lflag" -> attributes.lflag,
|
||||
"cchars" -> attributes.cchars,
|
||||
)
|
||||
Terminal.console.setAttributes(attrs)
|
||||
sendCommandResponse("", TerminalSetAttributesResponse(), msg.id)
|
||||
case Failure(_) =>
|
||||
}
|
||||
case (`getTerminalAttributes`, _) =>
|
||||
val attrs = Terminal.console.getAttributes
|
||||
val response = TerminalAttributesResponse(
|
||||
iflag = attrs.getOrElse("iflag", ""),
|
||||
oflag = attrs.getOrElse("oflag", ""),
|
||||
cflag = attrs.getOrElse("cflag", ""),
|
||||
lflag = attrs.getOrElse("lflag", ""),
|
||||
cchars = attrs.getOrElse("cchars", ""),
|
||||
)
|
||||
sendCommandResponse("", response, msg.id)
|
||||
case (`setTerminalSize`, Some(json)) =>
|
||||
Converter.fromJson[TerminalSetSizeCommand](json) match {
|
||||
case Success(size) =>
|
||||
Terminal.console.setSize(size.width, size.height)
|
||||
sendCommandResponse("", TerminalSetSizeResponse(), msg.id)
|
||||
case Failure(_) =>
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
|
@ -850,8 +885,8 @@ class NetworkClient(
|
|||
if (!stopped.get()) read()
|
||||
}
|
||||
}
|
||||
try Terminal.console.withRawSystemIn(read())
|
||||
catch { case _: InterruptedException | _: ClosedChannelException => stopped.set(true) }
|
||||
try Terminal.console.withRawInput(read())
|
||||
catch { case NonFatal(_) => stopped.set(true) }
|
||||
}
|
||||
|
||||
def drain(): Unit = inLock.synchronized {
|
||||
|
|
@ -897,20 +932,18 @@ object NetworkClient {
|
|||
override def success(msg: String): Unit = appender.success(msg)
|
||||
}
|
||||
}
|
||||
private def simpleConsoleInterface(printStream: PrintStream): ConsoleInterface =
|
||||
private def simpleConsoleInterface(doPrintln: String => Unit): ConsoleInterface =
|
||||
new ConsoleInterface {
|
||||
import scala.Console.{ GREEN, RED, RESET, YELLOW }
|
||||
override def appendLog(level: Level.Value, message: => String): Unit = {
|
||||
override def appendLog(level: Level.Value, message: => String): Unit = synchronized {
|
||||
val prefix = level match {
|
||||
case Level.Error => s"[$RED$level$RESET]"
|
||||
case Level.Warn => s"[$YELLOW$level$RESET]"
|
||||
case _ => s"[$RESET$level$RESET]"
|
||||
}
|
||||
message.split("\n").foreach { line =>
|
||||
if (!line.trim.isEmpty) printStream.println(s"$prefix $line")
|
||||
}
|
||||
message.linesIterator.foreach(line => doPrintln(s"$prefix $line"))
|
||||
}
|
||||
override def success(msg: String): Unit = printStream.println(s"[${GREEN}success$RESET] $msg")
|
||||
override def success(msg: String): Unit = doPrintln(s"[${GREEN}success$RESET] $msg")
|
||||
}
|
||||
private[client] class Arguments(
|
||||
val baseDirectory: File,
|
||||
|
|
@ -961,8 +994,29 @@ object NetworkClient {
|
|||
baseDirectory: File,
|
||||
args: Array[String],
|
||||
inputStream: InputStream,
|
||||
errorStream: PrintStream,
|
||||
printStream: PrintStream,
|
||||
errorStream: PrintStream,
|
||||
useJNI: Boolean
|
||||
): Int = {
|
||||
val client =
|
||||
simpleClient(
|
||||
NetworkClient.parseArgs(args).withBaseDirectory(baseDirectory),
|
||||
inputStream,
|
||||
printStream,
|
||||
errorStream,
|
||||
useJNI,
|
||||
)
|
||||
try {
|
||||
if (client.connect(log = true, promptCompleteUsers = false)) client.run()
|
||||
else 1
|
||||
} catch { case _: Exception => 1 } finally client.close()
|
||||
}
|
||||
def client(
|
||||
baseDirectory: File,
|
||||
args: Array[String],
|
||||
inputStream: InputStream,
|
||||
errorStream: PrintStream,
|
||||
terminal: Terminal,
|
||||
useJNI: Boolean
|
||||
): Int = {
|
||||
val client =
|
||||
|
|
@ -970,8 +1024,8 @@ object NetworkClient {
|
|||
NetworkClient.parseArgs(args).withBaseDirectory(baseDirectory),
|
||||
inputStream,
|
||||
errorStream,
|
||||
printStream,
|
||||
useJNI,
|
||||
terminal
|
||||
)
|
||||
try {
|
||||
if (client.connect(log = true, promptCompleteUsers = false)) client.run()
|
||||
|
|
@ -982,17 +1036,27 @@ object NetworkClient {
|
|||
arguments: Arguments,
|
||||
inputStream: InputStream,
|
||||
errorStream: PrintStream,
|
||||
printStream: PrintStream,
|
||||
useJNI: Boolean,
|
||||
): NetworkClient =
|
||||
new NetworkClient(
|
||||
arguments,
|
||||
NetworkClient.simpleConsoleInterface(printStream),
|
||||
inputStream,
|
||||
errorStream,
|
||||
printStream,
|
||||
useJNI,
|
||||
)
|
||||
terminal: Terminal
|
||||
): NetworkClient = {
|
||||
val doPrint: String => Unit = line => {
|
||||
if (terminal.getLastLine.isDefined) terminal.printStream.println()
|
||||
terminal.printStream.println(line)
|
||||
}
|
||||
val interface = NetworkClient.simpleConsoleInterface(doPrint)
|
||||
val printStream = terminal.printStream
|
||||
new NetworkClient(arguments, interface, inputStream, errorStream, printStream, useJNI)
|
||||
}
|
||||
private def simpleClient(
|
||||
arguments: Arguments,
|
||||
inputStream: InputStream,
|
||||
printStream: PrintStream,
|
||||
errorStream: PrintStream,
|
||||
useJNI: Boolean,
|
||||
): NetworkClient = {
|
||||
val interface = NetworkClient.simpleConsoleInterface(printStream.println)
|
||||
new NetworkClient(arguments, interface, inputStream, errorStream, printStream, useJNI)
|
||||
}
|
||||
def main(args: Array[String]): Unit = {
|
||||
val (jnaArg, restOfArgs) = args.partition(_ == "--jna")
|
||||
val useJNI = jnaArg.isEmpty
|
||||
|
|
@ -1005,8 +1069,9 @@ object NetworkClient {
|
|||
System.out.flush()
|
||||
})
|
||||
Runtime.getRuntime.addShutdownHook(hook)
|
||||
System.exit(Terminal.withStreams {
|
||||
try client(base, restOfArgs, System.in, System.err, System.out, useJNI)
|
||||
System.exit(Terminal.withStreams(false) {
|
||||
val term = Terminal.console
|
||||
try client(base, restOfArgs, term.inputStream, System.err, term, useJNI)
|
||||
finally {
|
||||
Runtime.getRuntime.removeShutdownHook(hook)
|
||||
hook.run()
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ import java.io.File
|
|||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import jline.console.history.PersistentHistory
|
||||
//import jline.console.history.PersistentHistory
|
||||
import sbt.BasicCommandStrings.{ Cancel, TerminateAction, Shutdown }
|
||||
import sbt.BasicKeys.{ historyPath, terminalShellPrompt }
|
||||
import sbt.State
|
||||
import sbt.internal.CommandChannel
|
||||
import sbt.internal.util.ConsoleAppender.{ ClearPromptLine, ClearScreenAfterCursor, DeleteLine }
|
||||
import sbt.internal.util._
|
||||
import sbt.internal.util.complete.{ JLineCompletion, Parser }
|
||||
import sbt.internal.util.complete.{ Parser }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
|
|
@ -47,44 +47,31 @@ private[sbt] object UITask {
|
|||
def terminalReader(parser: Parser[_])(
|
||||
terminal: Terminal,
|
||||
state: State
|
||||
): Reader = {
|
||||
val lineReader = LineReader.createReader(history(state), terminal, terminal.prompt)
|
||||
JLineCompletion.installCustomCompletor(lineReader, parser)
|
||||
() => {
|
||||
): Reader = { () =>
|
||||
try {
|
||||
val clear = terminal.ansi(ClearPromptLine, "")
|
||||
try {
|
||||
@tailrec def impl(): Either[String, String] = {
|
||||
lineReader.readLine(clear + terminal.prompt.mkPrompt()) match {
|
||||
case null 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 null => Left(TerminateAction)
|
||||
case s: String =>
|
||||
lineReader.getHistory match {
|
||||
case p: PersistentHistory =>
|
||||
p.add(s)
|
||||
p.flush()
|
||||
case _ =>
|
||||
}
|
||||
s match {
|
||||
case "" => impl()
|
||||
case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd)
|
||||
case cmd =>
|
||||
if (terminal.prompt != Prompt.Batch) terminal.setPrompt(Prompt.Running)
|
||||
terminal.printStream.write(Int.MinValue)
|
||||
Right(cmd)
|
||||
}
|
||||
}
|
||||
@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)
|
||||
}
|
||||
}
|
||||
impl()
|
||||
} catch {
|
||||
case _: InterruptedException => Right("")
|
||||
} finally lineReader.close()
|
||||
}
|
||||
}
|
||||
impl()
|
||||
} catch { case e: InterruptedException => Right("") }
|
||||
}
|
||||
}
|
||||
private[this] def history(s: State): Option[File] =
|
||||
|
|
|
|||
|
|
@ -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.{ AskUser, Loading, Running }
|
||||
|
||||
private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable {
|
||||
private[this] val uiThread = new AtomicReference[(UITask, Thread)]
|
||||
|
|
@ -70,8 +70,9 @@ private[sbt] class UserThread(val channel: CommandChannel) extends AutoCloseable
|
|||
}
|
||||
val state = consolePromptEvent.state
|
||||
terminal.prompt match {
|
||||
case Running => terminal.setPrompt(AskUser(() => UITask.shellPrompt(terminal, state)))
|
||||
case _ =>
|
||||
case Loading | Running =>
|
||||
terminal.setPrompt(AskUser(() => UITask.shellPrompt(terminal, state)))
|
||||
case _ =>
|
||||
}
|
||||
onProgressEvent(ProgressEvent("Info", Vector(), None, None, None))
|
||||
reset(state)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ 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-[0-9.]+-sbt-.*|jansi-[0-9.]+)\\.jar");
|
||||
final File[] cp = appProvider.mainClasspath();
|
||||
final URL[] interfaceURLs = new URL[3];
|
||||
final File[] extra =
|
||||
|
|
|
|||
|
|
@ -1503,14 +1503,14 @@ object Defaults extends BuildCommon {
|
|||
Some(s => {
|
||||
def print(st: String) = { scala.Console.out.print(st); scala.Console.out.flush() }
|
||||
print(s)
|
||||
Terminal.get.withRawSystemIn {
|
||||
Terminal.get.inputStream.read match {
|
||||
case -1 => None
|
||||
Terminal.get.withRawInput {
|
||||
try Terminal.get.inputStream.read match {
|
||||
case -1 | -2 => None
|
||||
case b =>
|
||||
val res = b.toChar.toString
|
||||
println(res)
|
||||
Some(res)
|
||||
}
|
||||
} catch { case e: InterruptedException => None }
|
||||
}
|
||||
}),
|
||||
classes
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ private[sbt] object xMain {
|
|||
BspClient.run(dealiasBaseDirectory(configuration))
|
||||
} else {
|
||||
bootServerSocket.foreach(l => Terminal.setBootStreams(l.inputStream, l.outputStream))
|
||||
Terminal.withStreams {
|
||||
Terminal.withStreams(true) {
|
||||
if (clientModByEnv || userCommands.exists(isClient)) {
|
||||
val args = userCommands.toList.filterNot(isClient)
|
||||
NetworkClient.run(dealiasBaseDirectory(configuration), args)
|
||||
|
|
@ -114,7 +114,7 @@ private[sbt] object xMain {
|
|||
case _: ServerAlreadyBootingException
|
||||
if System.console != null && !Terminal.startedByRemoteClient =>
|
||||
println("sbt server is already booting. Create a new server? y/n (default y)")
|
||||
val exit = Terminal.get.withRawSystemIn(System.in.read) match {
|
||||
val exit = Terminal.get.withRawInput(System.in.read) match {
|
||||
case 110 => Some(Exit(1))
|
||||
case _ => None
|
||||
}
|
||||
|
|
@ -835,7 +835,7 @@ object BuiltinCommands {
|
|||
@tailrec
|
||||
private[this] def doLoadFailed(s: State, loadArg: String): State = {
|
||||
s.log.warn("Project loading failed: (r)etry, (q)uit, (l)ast, or (i)gnore? (default: r)")
|
||||
val result = try Terminal.get.withRawSystemIn(System.in.read) match {
|
||||
val result = try Terminal.get.withRawInput(System.in.read) match {
|
||||
case -1 => 'q'.toInt
|
||||
case b => b
|
||||
} catch { case _: ClosedChannelException => 'q' }
|
||||
|
|
@ -913,6 +913,8 @@ object BuiltinCommands {
|
|||
}
|
||||
|
||||
def doLoadProject(s0: State, action: LoadAction.Value): State = {
|
||||
StandardMain.exchange.unprompt(ConsoleUnpromptEvent(None), force = true)
|
||||
StandardMain.exchange.channels.foreach(_.terminal.setPrompt(Prompt.Loading))
|
||||
welcomeBanner(s0)
|
||||
checkSBTVersionChanged(s0)
|
||||
val (s1, base) = Project.loadAction(SessionVar.clear(s0), action)
|
||||
|
|
@ -933,7 +935,9 @@ object BuiltinCommands {
|
|||
SessionSettings.checkSession(session, s2)
|
||||
val s3 = addCacheStoreFactoryFactory(Project.setProject(session, structure, s2))
|
||||
val s4 = setupGlobalFileTreeRepository(s3)
|
||||
CheckBuildSources.init(LintUnused.lintUnusedFunc(s4))
|
||||
val s5 = CheckBuildSources.init(LintUnused.lintUnusedFunc(s4))
|
||||
StandardMain.exchange.prompt(ConsolePromptEvent(s5))
|
||||
s5
|
||||
}
|
||||
|
||||
private val setupGlobalFileTreeRepository: State => State = { state =>
|
||||
|
|
@ -977,9 +981,11 @@ object BuiltinCommands {
|
|||
val exchange = StandardMain.exchange
|
||||
if (exchange.channels.exists(ContinuousCommands.isInWatch)) {
|
||||
val s1 = exchange.run(s0)
|
||||
def needPrompt(c: CommandChannel) =
|
||||
ContinuousCommands.isInWatch(c) && !ContinuousCommands.isPending(c)
|
||||
exchange.channels.foreach {
|
||||
case c if ContinuousCommands.isPending(c) =>
|
||||
case c => c.prompt(ConsolePromptEvent(s1))
|
||||
case c if needPrompt(c) => c.prompt(ConsolePromptEvent(s1))
|
||||
case _ =>
|
||||
}
|
||||
val exec: Exec = getExec(s1, Duration.Inf)
|
||||
val remaining: List[Exec] =
|
||||
|
|
|
|||
|
|
@ -184,7 +184,8 @@ object MainLoop {
|
|||
/** This is the main function State transfer function of the sbt command processing. */
|
||||
def processCommand(exec: Exec, state: State): State = {
|
||||
val channelName = exec.source map (_.channelName)
|
||||
StandardMain.exchange notifyStatus
|
||||
val exchange = StandardMain.exchange
|
||||
exchange notifyStatus
|
||||
ExecStatusEvent("Processing", channelName, exec.execId, Vector())
|
||||
try {
|
||||
def process(): State = {
|
||||
|
|
@ -197,9 +198,9 @@ 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))
|
||||
exchange.unprompt(ConsoleUnpromptEvent(exec.source), force = false)
|
||||
val newState = Command.process(exec.commandLine, progressState)
|
||||
if (exec.execId.fold(true)(!_.startsWith(networkExecPrefix)) &&
|
||||
!exec.commandLine.startsWith(networkExecPrefix)) {
|
||||
|
|
@ -210,26 +211,25 @@ object MainLoop {
|
|||
newState.remainingCommands.toVector map (_.commandLine),
|
||||
exitCode(newState, state),
|
||||
)
|
||||
StandardMain.exchange.respondStatus(doneEvent)
|
||||
exchange.respondStatus(doneEvent)
|
||||
}
|
||||
StandardMain.exchange.setExec(None)
|
||||
exchange.setExec(None)
|
||||
newState.get(sbt.Keys.currentTaskProgress).foreach(_.progress.stop())
|
||||
newState.remove(sbt.Keys.currentTaskProgress)
|
||||
}
|
||||
state.get(CheckBuildSourcesKey) match {
|
||||
case Some(cbs) =>
|
||||
if (!cbs.needsReload(state, exec.commandLine)) process()
|
||||
if (!cbs.needsReload(state, exec)) process()
|
||||
else {
|
||||
if (exec.commandLine.startsWith(SetTerminal))
|
||||
exec +: Exec("reload", None, None) +: state.remove(CheckBuildSourcesKey)
|
||||
else
|
||||
Exec("reload", None, None) +: exec +: state.remove(CheckBuildSourcesKey)
|
||||
val isSetTerminal = exec.commandLine.startsWith(SetTerminal)
|
||||
if (isSetTerminal) exec +: Exec("reload", None) +: state.remove(CheckBuildSourcesKey)
|
||||
else Exec("reload", None) +: exec +: state.remove(CheckBuildSourcesKey)
|
||||
}
|
||||
case _ => process()
|
||||
}
|
||||
} catch {
|
||||
case err: JsonRpcResponseError =>
|
||||
StandardMain.exchange.respondError(err, exec.execId, channelName.map(CommandSource(_)))
|
||||
exchange.respondError(err, exec.execId, channelName.map(CommandSource(_)))
|
||||
throw err
|
||||
case err: Throwable =>
|
||||
val errorEvent = ExecStatusEvent(
|
||||
|
|
|
|||
|
|
@ -133,10 +133,17 @@ private[sbt] final class CommandExchange {
|
|||
}
|
||||
}
|
||||
// Do not manually run GC until the user has been idling for at least the min gc interval.
|
||||
impl(interval match {
|
||||
val exec = 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 =
|
||||
|
|
@ -350,7 +357,11 @@ private[sbt] final class CommandExchange {
|
|||
case c => c.prompt(event)
|
||||
}
|
||||
}
|
||||
def unprompt(event: ConsoleUnpromptEvent): Unit = channels.foreach(_.unprompt(event))
|
||||
def unprompt(event: ConsoleUnpromptEvent, force: Boolean): Unit = {
|
||||
if (force)
|
||||
channels.foreach(c => c.unprompt(event.copy(lastSource = Some(CommandSource(c.name)))))
|
||||
else channels.foreach(_.unprompt(event))
|
||||
}
|
||||
|
||||
def logMessage(event: LogEvent): Unit = {
|
||||
channels.foreach {
|
||||
|
|
@ -408,6 +419,10 @@ private[sbt] final class CommandExchange {
|
|||
case _ =>
|
||||
}
|
||||
case _ =>
|
||||
channels.foreach {
|
||||
case nc: NetworkChannel => nc.shutdown(true, Some(("", "")))
|
||||
case c => c.shutdown(false)
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] def shutdown(name: String): Unit = {
|
||||
|
|
@ -444,7 +459,9 @@ private[sbt] final class CommandExchange {
|
|||
case mt: FastTrackTask =>
|
||||
mt.task match {
|
||||
case `attach` => mt.channel.prompt(ConsolePromptEvent(lastState.get))
|
||||
case `Cancel` => Option(currentExecRef.get).foreach(cancel)
|
||||
case `Cancel` =>
|
||||
Option(currentExecRef.get).foreach(cancel)
|
||||
mt.channel.prompt(ConsolePromptEvent(lastState.get))
|
||||
case t if t.startsWith(ContinuousCommands.stopWatch) =>
|
||||
ContinuousCommands.stopWatchImpl(mt.channel.name)
|
||||
mt.channel match {
|
||||
|
|
@ -454,6 +471,10 @@ private[sbt] final class CommandExchange {
|
|||
commandQueue.add(Exec(t, None, None))
|
||||
case `TerminateAction` => exit(mt)
|
||||
case `Shutdown` =>
|
||||
val console = Terminal.console
|
||||
val needNewLine = console.prompt.isInstanceOf[Prompt.AskUser]
|
||||
console.setPrompt(Prompt.Batch)
|
||||
if (needNewLine) console.printStream.println()
|
||||
channels.find(_.name == mt.channel.name) match {
|
||||
case Some(c: NetworkChannel) => c.shutdown(false)
|
||||
case _ =>
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
val (nextFileEvent, cleanupFileMonitor): (
|
||||
Int => Option[(Watch.Event, Watch.Action)],
|
||||
() => Unit
|
||||
) = getFileEvents(configs, logger, state, commands, fileStampCache)
|
||||
) = getFileEvents(configs, logger, state, commands, fileStampCache, channel.name)
|
||||
val executor = new WatchExecutor(channel.name)
|
||||
val nextEvent: Int => Watch.Action =
|
||||
combineInputAndFileEvents(nextInputEvent, nextFileEvent, message, logger, logger, executor)
|
||||
|
|
@ -420,7 +420,8 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
logger: Logger,
|
||||
state: State,
|
||||
commands: Seq[String],
|
||||
fileStampCache: FileStamp.Cache
|
||||
fileStampCache: FileStamp.Cache,
|
||||
channel: String,
|
||||
)(implicit extracted: Extracted): (Int => Option[(Watch.Event, Watch.Action)], () => Unit) = {
|
||||
val trackMetaBuild = configs.forall(_.watchSettings.trackMetaBuild)
|
||||
val buildGlobs =
|
||||
|
|
@ -465,16 +466,41 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
}
|
||||
}
|
||||
|
||||
private[this] val antiEntropyWindow = configs.map(_.watchSettings.antiEntropy).max
|
||||
private[this] val monitor = FileEventMonitor.antiEntropy(
|
||||
eventMonitorObservers,
|
||||
configs.map(_.watchSettings.antiEntropy).max,
|
||||
antiEntropyWindow,
|
||||
logger,
|
||||
quarantinePeriod,
|
||||
retentionPeriod
|
||||
)
|
||||
|
||||
override def poll(duration: Duration, filter: Event => Boolean): Seq[Event] =
|
||||
monitor.poll(duration, filter)
|
||||
private[this] val antiEntropyPollPeriod =
|
||||
configs.map(_.watchSettings.antiEntropyPollPeriod).max
|
||||
override def poll(duration: Duration, filter: Event => Boolean): Seq[Event] = {
|
||||
monitor.poll(duration, filter) match {
|
||||
case s if s.nonEmpty =>
|
||||
val limit = antiEntropyWindow.fromNow
|
||||
/*
|
||||
* File events may come in bursts so we poll for a short time to see if there
|
||||
* are other changes detected in the burst. As soon as no changes are detected
|
||||
* during the polling window, we return all of the detected events. The polling
|
||||
* period is by default 5 milliseconds which is short enough to detect bursts
|
||||
* induced by commands like git rebase but fast enough to not lead to a noticable
|
||||
* increase in latency.
|
||||
*/
|
||||
@tailrec def aggregate(res: Seq[Event]): Seq[Event] =
|
||||
if (limit.isOverdue) res
|
||||
else {
|
||||
monitor.poll(antiEntropyPollPeriod) match {
|
||||
case s if s.nonEmpty => aggregate(res ++ s)
|
||||
case _ => res
|
||||
}
|
||||
}
|
||||
aggregate(s)
|
||||
case s => s
|
||||
}
|
||||
}
|
||||
|
||||
override def close(): Unit = {
|
||||
configHandle.close()
|
||||
|
|
@ -526,7 +552,16 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
}
|
||||
|
||||
if (buildGlobs.exists(_.matches(path))) {
|
||||
getWatchEvent(forceTrigger = false).map(e => e -> Watch.Reload).toSeq
|
||||
getWatchEvent(forceTrigger = false).flatMap { e =>
|
||||
state.get(CheckBuildSources.CheckBuildSourcesKey) match {
|
||||
case Some(cbs) =>
|
||||
if (cbs.needsReload(state, Exec("", Some(CommandSource(channel)))))
|
||||
Some(e -> Watch.Reload)
|
||||
else None
|
||||
case None =>
|
||||
Some(e -> Watch.Reload)
|
||||
}
|
||||
}.toSeq
|
||||
} else {
|
||||
val acceptedConfigParameters = configs.flatMap { config =>
|
||||
config.inputs().flatMap {
|
||||
|
|
@ -727,7 +762,7 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
}
|
||||
}
|
||||
|
||||
terminal.withRawSystemIn(impl())
|
||||
terminal.withRawInput(impl())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -858,6 +893,8 @@ private[sbt] object Continuous extends DeprecatedContinuous {
|
|||
// alternative would be SettingKey[() => InputStream], but that doesn't feel right because
|
||||
// one might want the InputStream to depend on other tasks.
|
||||
val inputStream: Option[TaskKey[InputStream]] = key.get(watchInputStream)
|
||||
val antiEntropyPollPeriod: FiniteDuration =
|
||||
key.get(watchAntiEntropyPollPeriod).getOrElse(Watch.defaultAntiEntropyPollPeriod)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1183,7 +1220,6 @@ private[sbt] object ContinuousCommands {
|
|||
) extends Thread(s"sbt-${channel.name}-watch-ui-thread")
|
||||
with UITask {
|
||||
override private[sbt] def reader: UITask.Reader = () => {
|
||||
channel.terminal.printStream.write(Int.MinValue)
|
||||
def stop = Right(s"${ContinuousCommands.stopWatch} ${channel.name}")
|
||||
val exitAction: Watch.Action = {
|
||||
Watch.apply(
|
||||
|
|
@ -1202,7 +1238,7 @@ private[sbt] object ContinuousCommands {
|
|||
case Watch.Trigger => Right(s"$runWatch ${channel.name}")
|
||||
case Watch.Reload =>
|
||||
val rewatch = s"$ContinuousExecutePrefix ${ws.count} ${cs.commands mkString "; "}"
|
||||
stop.map(_ :: "reload" :: rewatch :: Nil mkString "; ")
|
||||
stop.map(_ :: s"$SetTerminal ${channel.name}" :: "reload" :: rewatch :: Nil mkString "; ")
|
||||
case Watch.Prompt => stop.map(_ :: s"$PromptChannel ${channel.name}" :: Nil mkString ";")
|
||||
case Watch.Run(commands) =>
|
||||
stop.map(_ +: commands.map(_.commandLine).filter(_.nonEmpty) mkString "; ")
|
||||
|
|
|
|||
|
|
@ -53,11 +53,12 @@ private[sbt] class TaskProgress private ()
|
|||
if (firstTime.compareAndSet(true, activeExceedingThreshold.isEmpty)) threshold
|
||||
else sleepDuration
|
||||
val limit = duration.fromNow
|
||||
while (Deadline.now < limit) {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ package internal.nio
|
|||
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||
import sbt.BasicCommandStrings.{ RebootCommand, Shutdown, TerminateAction }
|
||||
import sbt.BasicCommandStrings.{ RebootCommand, SetTerminal, Shutdown, TerminateAction }
|
||||
import sbt.Keys.{ baseDirectory, pollInterval, state }
|
||||
import sbt.Scope.Global
|
||||
import sbt.SlashSyntax0._
|
||||
import sbt.internal.CommandStrings.LoadProject
|
||||
import sbt.internal.SysProp
|
||||
import sbt.internal.util.AttributeKey
|
||||
import sbt.internal.util.{ AttributeKey, Terminal }
|
||||
import sbt.io.syntax._
|
||||
import sbt.nio.FileChanges
|
||||
import sbt.nio.FileStamp
|
||||
|
|
@ -24,9 +24,11 @@ import sbt.nio.Keys._
|
|||
import sbt.nio.file.{ FileAttributes, FileTreeView, Glob, ** }
|
||||
import sbt.nio.file.syntax._
|
||||
import sbt.nio.Settings
|
||||
import sbt.util.Logger
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration.{ Deadline => SDeadline, _ }
|
||||
import scala.io.AnsiColor
|
||||
|
||||
/**
|
||||
* This class is used to determine whether sbt needs to automatically reload
|
||||
|
|
@ -92,19 +94,41 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
|
|||
val filter = (c: String) =>
|
||||
c == LoadProject || c == RebootCommand || c == TerminateAction || c == Shutdown ||
|
||||
c.startsWith("sbtReboot")
|
||||
val res = !commands.exists(filter)
|
||||
if (!res) {
|
||||
val resetState = commands.exists(filter)
|
||||
if (resetState) {
|
||||
previousStamps.set(getStamps(force = true))
|
||||
needUpdate.set(false)
|
||||
}
|
||||
res
|
||||
// We don't need to do a check since we just updated the stamps since
|
||||
// we are about to perform a reload or reboot.
|
||||
!resetState
|
||||
}
|
||||
@inline private def forceCheck = fileTreeRepository.isEmpty
|
||||
private[sbt] def needsReload(state: State, cmd: String) = {
|
||||
private[sbt] def needsReload(
|
||||
state: State,
|
||||
exec: Exec
|
||||
): Boolean = {
|
||||
val isSetTerminal = exec.commandLine.startsWith(SetTerminal)
|
||||
val name =
|
||||
if (isSetTerminal)
|
||||
exec.commandLine.split(s"$SetTerminal ").lastOption.filterNot(_.isEmpty)
|
||||
else exec.source.map(_.channelName)
|
||||
val loggerOrTerminal =
|
||||
name.flatMap(StandardMain.exchange.channelForName(_).map(_.terminal)) match {
|
||||
case Some(t) => Right(t)
|
||||
case _ => Left(state.globalLogging.full)
|
||||
}
|
||||
|
||||
needsReload(state, loggerOrTerminal, exec.commandLine)
|
||||
}
|
||||
private def needsReload(
|
||||
state: State,
|
||||
loggerOrTerminal: Either[Logger, Terminal],
|
||||
cmd: String
|
||||
): Boolean = {
|
||||
(needCheck(state, cmd) && (forceCheck || needUpdate.compareAndSet(true, false))) && {
|
||||
val extracted = Project.extract(state)
|
||||
val onChanges = extracted.get(Global / onChangedBuildSource)
|
||||
val logger = state.globalLogging.full
|
||||
val current = getStamps(force = false)
|
||||
val previous = previousStamps.getAndSet(current)
|
||||
Settings.changedFiles(previous, current) match {
|
||||
|
|
@ -120,14 +144,24 @@ private[sbt] class CheckBuildSources extends AutoCloseable {
|
|||
else "")
|
||||
val prefix = rawPrefix.linesIterator.filterNot(_.trim.isEmpty).mkString("\n")
|
||||
if (onChanges == ReloadOnSourceChanges) {
|
||||
logger.info(s"$prefix\nReloading sbt...")
|
||||
val msg = s"$prefix\nReloading sbt..."
|
||||
loggerOrTerminal match {
|
||||
case Right(t) => msg.linesIterator.foreach(l => t.printStream.println(s"[info] $l"))
|
||||
case Left(l) => l.info(msg)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
val tail = "Apply these changes by running `reload`.\nAutomatically reload the " +
|
||||
"build when source changes are detected by setting " +
|
||||
"`Global / onChangedBuildSource := ReloadOnSourceChanges`.\nDisable this " +
|
||||
"warning by setting `Global / onChangedBuildSource := IgnoreSourceChanges`."
|
||||
logger.warn(s"$prefix\n$tail")
|
||||
val msg = s"$prefix\n$tail"
|
||||
loggerOrTerminal match {
|
||||
case Right(t) =>
|
||||
val prefix = s"[${Def.withColor("warn", Some(AnsiColor.YELLOW), t.isColorEnabled)}]"
|
||||
msg.linesIterator.foreach(l => t.printStream.println(s"$prefix $l"))
|
||||
case Left(l) => l.warn(msg)
|
||||
}
|
||||
false
|
||||
}
|
||||
case _ => false
|
||||
|
|
@ -158,8 +192,9 @@ private[sbt] object CheckBuildSources {
|
|||
private[sbt] def needReloadImpl: Def.Initialize[Task[StateTransform]] = Def.task {
|
||||
val st = state.value
|
||||
st.get(CheckBuildSourcesKey) match {
|
||||
case Some(cbs) if (cbs.needsReload(st, "")) => StateTransform("reload" :: (_: State))
|
||||
case _ => StateTransform(identity)
|
||||
case Some(cbs) if (cbs.needsReload(st, Exec("", None))) =>
|
||||
StateTransform("reload" :: (_: State))
|
||||
case _ => StateTransform(identity)
|
||||
}
|
||||
}
|
||||
private[sbt] def buildSourceFileInputs: Def.Initialize[Seq[Glob]] = Def.setting {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ package server
|
|||
import java.io.{ IOException, InputStream, OutputStream }
|
||||
import java.net.{ Socket, SocketTimeoutException }
|
||||
import java.nio.channels.ClosedChannelException
|
||||
import java.util.concurrent.{ ConcurrentHashMap, LinkedBlockingQueue }
|
||||
import java.util.concurrent.{ ConcurrentHashMap, Executors, LinkedBlockingQueue, TimeUnit }
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||
|
||||
import sbt.BasicCommandStrings.{ Shutdown, TerminateAction }
|
||||
|
|
@ -99,7 +99,6 @@ final class NetworkChannel(
|
|||
addFastTrackTask(attach)
|
||||
}
|
||||
private[sbt] def prompt(): Unit = {
|
||||
terminal.setPrompt(Prompt.Running)
|
||||
interactive.set(true)
|
||||
jsonRpcNotify(promptChannel, "")
|
||||
}
|
||||
|
|
@ -575,6 +574,7 @@ final class NetworkChannel(
|
|||
catch { case _: IOException => }
|
||||
running.set(false)
|
||||
out.close()
|
||||
outputStream.close()
|
||||
thread.interrupt()
|
||||
writeThread.interrupt()
|
||||
}
|
||||
|
|
@ -611,7 +611,7 @@ final class NetworkChannel(
|
|||
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = {
|
||||
val m =
|
||||
JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get))
|
||||
log.debug(s"jsonRpcNotify: $m")
|
||||
if (method != Serialization.systemOut) log.debug(s"jsonRpcNotify: $m")
|
||||
val bytes = Serialization.serializeNotificationMessage(m)
|
||||
publishBytes(bytes)
|
||||
}
|
||||
|
|
@ -640,28 +640,60 @@ final class NetworkChannel(
|
|||
case -1 => throw new ClosedChannelException()
|
||||
case b => b
|
||||
}
|
||||
} catch { case _: IOException => -1 }
|
||||
} catch { case e: IOException => -1 }
|
||||
}
|
||||
override def available(): Int = inputBuffer.size
|
||||
}
|
||||
import sjsonnew.BasicJsonProtocol._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
private[this] lazy val outputStream: OutputStream = new OutputStream {
|
||||
private[this] val buffer = new LinkedBlockingQueue[Byte]()
|
||||
override def write(b: Int): Unit = buffer.put(b.toByte)
|
||||
override def flush(): Unit = {
|
||||
jsonRpcNotify(Serialization.systemOut, buffer.asScala)
|
||||
buffer.clear()
|
||||
private[this] lazy val outputStream: OutputStream with AutoCloseable = new OutputStream
|
||||
with AutoCloseable {
|
||||
/*
|
||||
* We buffer calls to flush to the remote client so that it is called at most
|
||||
* once every 20 milliseconds. This is done because many terminals seem to flicker
|
||||
* and display ghost characters if we flush to the remote client too often. The
|
||||
* json protocol is a bit bulky so this will also reduce the total number of
|
||||
* bytes that are written to the named pipe or unix domain socket. The buffer
|
||||
* period of 20 milliseconds was arbitrarily chosen and could be tuned in the future.
|
||||
* The thinking is that writes tend to be bursty so a twenty millisecond window is
|
||||
* probably long enough to catch each burst but short enough to not introduce
|
||||
* noticeable latency.
|
||||
*/
|
||||
private[this] val executor =
|
||||
Executors.newSingleThreadScheduledExecutor(
|
||||
r => new Thread(r, s"$name-output-buffer-timer-thread")
|
||||
)
|
||||
private[this] val buffer = new LinkedBlockingQueue[Byte]
|
||||
private[this] val future = new AtomicReference[java.util.concurrent.Future[_]]
|
||||
override def close(): Unit = Util.ignoreResult(executor.shutdownNow())
|
||||
override def write(b: Int): Unit = buffer.synchronized {
|
||||
buffer.put(b.toByte)
|
||||
}
|
||||
override def write(b: Array[Byte]): Unit = write(b, 0, b.length)
|
||||
override def write(b: Array[Byte], off: Int, len: Int): Unit = {
|
||||
var i = off
|
||||
while (i < len) {
|
||||
buffer.put(b(i))
|
||||
i += 1
|
||||
override def flush(): Unit = {
|
||||
future.get match {
|
||||
case null =>
|
||||
future.set(
|
||||
executor.schedule(
|
||||
(() => {
|
||||
future.set(null)
|
||||
val list = new java.util.ArrayList[Byte]
|
||||
buffer.synchronized(buffer.drainTo(list))
|
||||
jsonRpcNotify(Serialization.systemOut, list.asScala.toSeq)
|
||||
}): Runnable,
|
||||
20,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
)
|
||||
case f =>
|
||||
}
|
||||
}
|
||||
override def write(b: Array[Byte]): Unit = buffer.synchronized {
|
||||
b.foreach(buffer.put)
|
||||
}
|
||||
override def write(b: Array[Byte], off: Int, len: Int): Unit = {
|
||||
write(java.util.Arrays.copyOfRange(b, off, off + len))
|
||||
}
|
||||
}
|
||||
private class NetworkTerminal extends TerminalImpl(inputStream, outputStream, name) {
|
||||
private[this] val pending = new AtomicBoolean(false)
|
||||
|
|
@ -741,25 +773,82 @@ final class NetworkChannel(
|
|||
Some(result(queue.take))
|
||||
}
|
||||
}
|
||||
override def getBooleanCapability(capability: String): Boolean =
|
||||
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||
getCapability(
|
||||
TerminalCapabilitiesQuery(boolean = Some(capability), numeric = None, string = None),
|
||||
TerminalCapabilitiesQuery(
|
||||
boolean = Some(capability),
|
||||
numeric = None,
|
||||
string = None,
|
||||
jline3
|
||||
),
|
||||
_.boolean.getOrElse(false)
|
||||
).getOrElse(false)
|
||||
override def getNumericCapability(capability: String): Int =
|
||||
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
|
||||
getCapability(
|
||||
TerminalCapabilitiesQuery(boolean = None, numeric = Some(capability), string = None),
|
||||
_.numeric.getOrElse(-1)
|
||||
).getOrElse(-1)
|
||||
override def getStringCapability(capability: String): String =
|
||||
TerminalCapabilitiesQuery(
|
||||
boolean = None,
|
||||
numeric = Some(capability),
|
||||
string = None,
|
||||
jline3
|
||||
),
|
||||
(_: TerminalCapabilitiesResponse).numeric.map(Integer.valueOf).getOrElse(-1: Integer)
|
||||
).getOrElse(-1: Integer)
|
||||
override def getStringCapability(capability: String, jline3: Boolean): String =
|
||||
getCapability(
|
||||
TerminalCapabilitiesQuery(boolean = None, numeric = None, string = Some(capability)),
|
||||
TerminalCapabilitiesQuery(
|
||||
boolean = None,
|
||||
numeric = None,
|
||||
string = Some(capability),
|
||||
jline3
|
||||
),
|
||||
_.string.flatMap {
|
||||
case "null" => None
|
||||
case s => Some(s)
|
||||
}.orNull
|
||||
).getOrElse("")
|
||||
|
||||
override private[sbt] def getAttributes: Map[String, String] =
|
||||
if (closed.get) Map.empty
|
||||
else {
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
val queue = VirtualTerminal.sendTerminalAttributesQuery(
|
||||
name,
|
||||
jsonRpcRequest
|
||||
)
|
||||
try {
|
||||
val a = queue.take
|
||||
Map(
|
||||
"iflag" -> a.iflag,
|
||||
"oflag" -> a.oflag,
|
||||
"cflag" -> a.cflag,
|
||||
"lflag" -> a.lflag,
|
||||
"cchars" -> a.cchars
|
||||
)
|
||||
} catch { case _: InterruptedException => Map.empty }
|
||||
}
|
||||
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
|
||||
if (!closed.get) {
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
val attrs = TerminalSetAttributesCommand(
|
||||
iflag = attributes.getOrElse("iflag", ""),
|
||||
oflag = attributes.getOrElse("oflag", ""),
|
||||
cflag = attributes.getOrElse("cflag", ""),
|
||||
lflag = attributes.getOrElse("lflag", ""),
|
||||
cchars = attributes.getOrElse("cchars", ""),
|
||||
)
|
||||
val queue = VirtualTerminal.setTerminalAttributes(name, jsonRpcRequest, attrs)
|
||||
try queue.take
|
||||
catch { case _: InterruptedException => }
|
||||
}
|
||||
override def setSize(width: Int, height: Int): Unit =
|
||||
if (!closed.get) {
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
val size = TerminalSetSizeCommand(width, height)
|
||||
val queue = VirtualTerminal.setTerminalSize(name, jsonRpcRequest, size)
|
||||
try queue.take
|
||||
catch { case _: InterruptedException => }
|
||||
}
|
||||
|
||||
override def toString: String = s"NetworkTerminal($name)"
|
||||
override def close(): Unit = if (closed.compareAndSet(false, true)) {
|
||||
val threads = blockedThreads.synchronized {
|
||||
|
|
|
|||
|
|
@ -25,16 +25,27 @@ import sbt.protocol.Serialization.{
|
|||
import sjsonnew.support.scalajson.unsafe.Converter
|
||||
import sbt.protocol.{
|
||||
Attach,
|
||||
TerminalAttributesQuery,
|
||||
TerminalAttributesResponse,
|
||||
TerminalCapabilitiesQuery,
|
||||
TerminalCapabilitiesResponse,
|
||||
TerminalPropertiesResponse
|
||||
TerminalPropertiesResponse,
|
||||
TerminalSetAttributesCommand,
|
||||
TerminalSetSizeCommand,
|
||||
}
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
|
||||
object VirtualTerminal {
|
||||
private[this] val pendingTerminalProperties =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalPropertiesResponse]]()
|
||||
private[this] val pendingTerminalCapabilities =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalCapabilitiesResponse]]
|
||||
private[this] val pendingTerminalAttributes =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalAttributesResponse]]
|
||||
private[this] val pendingTerminalSetAttributes =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
|
||||
private[this] val pendingTerminalSetSize =
|
||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
|
||||
private[sbt] def sendTerminalPropertiesQuery(
|
||||
channelName: String,
|
||||
jsonRpcRequest: (String, String, String) => Unit
|
||||
|
|
@ -70,6 +81,39 @@ object VirtualTerminal {
|
|||
case _ =>
|
||||
}
|
||||
}
|
||||
private[sbt] def sendTerminalAttributesQuery(
|
||||
channelName: String,
|
||||
jsonRpcRequest: (String, String, TerminalAttributesQuery) => Unit,
|
||||
): ArrayBlockingQueue[TerminalAttributesResponse] = {
|
||||
val id = UUID.randomUUID.toString
|
||||
val queue = new ArrayBlockingQueue[TerminalAttributesResponse](1)
|
||||
pendingTerminalAttributes.put((channelName, id), queue)
|
||||
jsonRpcRequest(id, terminalCapabilities, TerminalAttributesQuery())
|
||||
queue
|
||||
}
|
||||
private[sbt] def setTerminalAttributes(
|
||||
channelName: String,
|
||||
jsonRpcRequest: (String, String, TerminalSetAttributesCommand) => Unit,
|
||||
query: TerminalSetAttributesCommand
|
||||
): ArrayBlockingQueue[Unit] = {
|
||||
val id = UUID.randomUUID.toString
|
||||
val queue = new ArrayBlockingQueue[Unit](1)
|
||||
pendingTerminalSetAttributes.put((channelName, id), queue)
|
||||
jsonRpcRequest(id, terminalCapabilities, query)
|
||||
queue
|
||||
}
|
||||
|
||||
private[sbt] def setTerminalSize(
|
||||
channelName: String,
|
||||
jsonRpcRequest: (String, String, TerminalSetSizeCommand) => Unit,
|
||||
query: TerminalSetSizeCommand
|
||||
): ArrayBlockingQueue[Unit] = {
|
||||
val id = UUID.randomUUID.toString
|
||||
val queue = new ArrayBlockingQueue[Unit](1)
|
||||
pendingTerminalSetSize.put((channelName, id), queue)
|
||||
jsonRpcRequest(id, terminalCapabilities, query)
|
||||
queue
|
||||
}
|
||||
val handler = ServerHandler { cb =>
|
||||
ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb))
|
||||
}
|
||||
|
|
@ -77,7 +121,6 @@ object VirtualTerminal {
|
|||
private val requestHandler: Handler[JsonRpcRequestMessage] =
|
||||
callback => {
|
||||
case r if r.method == attach =>
|
||||
import sbt.protocol.codec.JsonProtocol.AttachFormat
|
||||
val isInteractive = r.params
|
||||
.flatMap(Converter.fromJson[Attach](_).toOption.map(_.interactive))
|
||||
.exists(identity)
|
||||
|
|
@ -89,7 +132,6 @@ object VirtualTerminal {
|
|||
private val responseHandler: Handler[JsonRpcResponseMessage] =
|
||||
callback => {
|
||||
case r if pendingTerminalProperties.get((callback.name, r.id)) != null =>
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
val response =
|
||||
r.result.flatMap(Converter.fromJson[TerminalPropertiesResponse](_).toOption)
|
||||
pendingTerminalProperties.remove((callback.name, r.id)) match {
|
||||
|
|
@ -97,7 +139,6 @@ object VirtualTerminal {
|
|||
case buffer => response.foreach(buffer.put)
|
||||
}
|
||||
case r if pendingTerminalCapabilities.get((callback.name, r.id)) != null =>
|
||||
import sbt.protocol.codec.JsonProtocol._
|
||||
val response =
|
||||
r.result.flatMap(
|
||||
Converter.fromJson[TerminalCapabilitiesResponse](_).toOption
|
||||
|
|
@ -107,6 +148,24 @@ object VirtualTerminal {
|
|||
case buffer =>
|
||||
buffer.put(response.getOrElse(TerminalCapabilitiesResponse(None, None, None)))
|
||||
}
|
||||
case r if pendingTerminalAttributes.get((callback.name, r.id)) != null =>
|
||||
val response =
|
||||
r.result.flatMap(Converter.fromJson[TerminalAttributesResponse](_).toOption)
|
||||
pendingTerminalAttributes.remove((callback.name, r.id)) match {
|
||||
case null =>
|
||||
case buffer =>
|
||||
buffer.put(response.getOrElse(TerminalAttributesResponse("", "", "", "", "")))
|
||||
}
|
||||
case r if pendingTerminalSetAttributes.get((callback.name, r.id)) != null =>
|
||||
pendingTerminalSetAttributes.remove((callback.name, r.id)) match {
|
||||
case null =>
|
||||
case buffer => buffer.put(())
|
||||
}
|
||||
case r if pendingTerminalSetSize.get((callback.name, r.id)) != null =>
|
||||
pendingTerminalSetSize.remove((callback.name, r.id)) match {
|
||||
case null =>
|
||||
case buffer => buffer.put(())
|
||||
}
|
||||
}
|
||||
private val notificationHandler: Handler[JsonRpcNotificationMessage] =
|
||||
callback => {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ object Keys {
|
|||
val watchAntiEntropyRetentionPeriod = settingKey[FiniteDuration](
|
||||
"Wall clock Duration for which a FileEventMonitor will store anti-entropy events. This prevents spurious triggers when a task takes a long time to run. Higher values will consume more memory but make spurious triggers less likely."
|
||||
).withRank(BMinusSetting)
|
||||
val watchAntiEntropyPollPeriod = settingKey[FiniteDuration](
|
||||
"The duration for which sbt will poll for file events during the window in which sbt is buffering file events"
|
||||
)
|
||||
val onChangedBuildSource = settingKey[WatchBuildSourceOption](
|
||||
"Determines what to do if the sbt meta build sources have changed"
|
||||
).withRank(DSetting)
|
||||
|
|
|
|||
|
|
@ -450,6 +450,14 @@ object Watch {
|
|||
*/
|
||||
final val defaultAntiEntropy: FiniteDuration = 500.milliseconds
|
||||
|
||||
/**
|
||||
* The duration for which we will poll for new file events when we are buffering events
|
||||
* after an initial event has been detected to avoid spurious rebuilds.
|
||||
*
|
||||
* If this value is ever updated, please update the comment in Continuous.getFileEvents.
|
||||
*/
|
||||
final val defaultAntiEntropyPollPeriod: FiniteDuration = 5.milliseconds
|
||||
|
||||
/**
|
||||
* The duration in wall clock time for which a FileEventMonitor will retain anti-entropy
|
||||
* events for files. This is an implementation detail of the FileEventMonitor. It should
|
||||
|
|
@ -613,5 +621,6 @@ object Watch {
|
|||
watchForceTriggerOnAnyChange :== false,
|
||||
watchPersistFileStamps := (sbt.Keys.turbo in ThisBuild).value,
|
||||
watchTriggers :== Nil,
|
||||
watchAntiEntropyPollPeriod := Watch.defaultAntiEntropyPollPeriod,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,10 +84,12 @@ object Dependencies {
|
|||
val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash")
|
||||
|
||||
val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-5e51b9d4f9631ebfa29753ce4accc57808e7fd6b"
|
||||
val jansi = "org.fusesource.jansi" % "jansi" % "1.12"
|
||||
val jline3 = "org.jline" % "jline" % "3.15.0"
|
||||
val jline3Jansi = "org.jline" % "jline-terminal-jansi" % "3.15.0"
|
||||
val jansi = "org.fusesource.jansi" % "jansi" % "1.18"
|
||||
val scalatest = "org.scalatest" %% "scalatest" % "3.0.8"
|
||||
val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0"
|
||||
val specs2 = "org.specs2" %% "specs2-junit" % "4.0.1"
|
||||
val specs2 = "org.specs2" %% "specs2-junit" % "4.10.0"
|
||||
val junit = "junit" % "junit" % "4.11"
|
||||
val scalaVerify = "com.eed3si9n.verify" %% "verify" % "0.2.0"
|
||||
val templateResolverApi = "org.scala-sbt" % "template-resolver" % "0.1"
|
||||
|
|
|
|||
29
protocol/src/main/contraband-scala/sbt/protocol/TerminalAttributesQuery.scala
generated
Normal file
29
protocol/src/main/contraband-scala/sbt/protocol/TerminalAttributesQuery.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
|
||||
final class TerminalAttributesQuery private () extends sbt.protocol.CommandMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case _: TerminalAttributesQuery => true
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (17 + "sbt.protocol.TerminalAttributesQuery".##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalAttributesQuery()"
|
||||
}
|
||||
private[this] def copy(): TerminalAttributesQuery = {
|
||||
new TerminalAttributesQuery()
|
||||
}
|
||||
|
||||
}
|
||||
object TerminalAttributesQuery {
|
||||
|
||||
def apply(): TerminalAttributesQuery = new TerminalAttributesQuery()
|
||||
}
|
||||
48
protocol/src/main/contraband-scala/sbt/protocol/TerminalAttributesResponse.scala
generated
Normal file
48
protocol/src/main/contraband-scala/sbt/protocol/TerminalAttributesResponse.scala
generated
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol
|
||||
final class TerminalAttributesResponse private (
|
||||
val iflag: String,
|
||||
val oflag: String,
|
||||
val cflag: String,
|
||||
val lflag: String,
|
||||
val cchars: String) extends sbt.protocol.EventMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: TerminalAttributesResponse => (this.iflag == x.iflag) && (this.oflag == x.oflag) && (this.cflag == x.cflag) && (this.lflag == x.lflag) && (this.cchars == x.cchars)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.TerminalAttributesResponse".##) + iflag.##) + oflag.##) + cflag.##) + lflag.##) + cchars.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalAttributesResponse(" + iflag + ", " + oflag + ", " + cflag + ", " + lflag + ", " + cchars + ")"
|
||||
}
|
||||
private[this] def copy(iflag: String = iflag, oflag: String = oflag, cflag: String = cflag, lflag: String = lflag, cchars: String = cchars): TerminalAttributesResponse = {
|
||||
new TerminalAttributesResponse(iflag, oflag, cflag, lflag, cchars)
|
||||
}
|
||||
def withIflag(iflag: String): TerminalAttributesResponse = {
|
||||
copy(iflag = iflag)
|
||||
}
|
||||
def withOflag(oflag: String): TerminalAttributesResponse = {
|
||||
copy(oflag = oflag)
|
||||
}
|
||||
def withCflag(cflag: String): TerminalAttributesResponse = {
|
||||
copy(cflag = cflag)
|
||||
}
|
||||
def withLflag(lflag: String): TerminalAttributesResponse = {
|
||||
copy(lflag = lflag)
|
||||
}
|
||||
def withCchars(cchars: String): TerminalAttributesResponse = {
|
||||
copy(cchars = cchars)
|
||||
}
|
||||
}
|
||||
object TerminalAttributesResponse {
|
||||
|
||||
def apply(iflag: String, oflag: String, cflag: String, lflag: String, cchars: String): TerminalAttributesResponse = new TerminalAttributesResponse(iflag, oflag, cflag, lflag, cchars)
|
||||
}
|
||||
|
|
@ -7,22 +7,23 @@ package sbt.protocol
|
|||
final class TerminalCapabilitiesQuery private (
|
||||
val boolean: Option[String],
|
||||
val numeric: Option[String],
|
||||
val string: Option[String]) extends sbt.protocol.CommandMessage() with Serializable {
|
||||
val string: Option[String],
|
||||
val jline3: Boolean) extends sbt.protocol.CommandMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: TerminalCapabilitiesQuery => (this.boolean == x.boolean) && (this.numeric == x.numeric) && (this.string == x.string)
|
||||
case x: TerminalCapabilitiesQuery => (this.boolean == x.boolean) && (this.numeric == x.numeric) && (this.string == x.string) && (this.jline3 == x.jline3)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (37 * (17 + "sbt.protocol.TerminalCapabilitiesQuery".##) + boolean.##) + numeric.##) + string.##)
|
||||
37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.TerminalCapabilitiesQuery".##) + boolean.##) + numeric.##) + string.##) + jline3.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalCapabilitiesQuery(" + boolean + ", " + numeric + ", " + string + ")"
|
||||
"TerminalCapabilitiesQuery(" + boolean + ", " + numeric + ", " + string + ", " + jline3 + ")"
|
||||
}
|
||||
private[this] def copy(boolean: Option[String] = boolean, numeric: Option[String] = numeric, string: Option[String] = string): TerminalCapabilitiesQuery = {
|
||||
new TerminalCapabilitiesQuery(boolean, numeric, string)
|
||||
private[this] def copy(boolean: Option[String] = boolean, numeric: Option[String] = numeric, string: Option[String] = string, jline3: Boolean = jline3): TerminalCapabilitiesQuery = {
|
||||
new TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
|
||||
}
|
||||
def withBoolean(boolean: Option[String]): TerminalCapabilitiesQuery = {
|
||||
copy(boolean = boolean)
|
||||
|
|
@ -42,9 +43,12 @@ final class TerminalCapabilitiesQuery private (
|
|||
def withString(string: String): TerminalCapabilitiesQuery = {
|
||||
copy(string = Option(string))
|
||||
}
|
||||
def withJline3(jline3: Boolean): TerminalCapabilitiesQuery = {
|
||||
copy(jline3 = jline3)
|
||||
}
|
||||
}
|
||||
object TerminalCapabilitiesQuery {
|
||||
|
||||
def apply(boolean: Option[String], numeric: Option[String], string: Option[String]): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(boolean, numeric, string)
|
||||
def apply(boolean: String, numeric: String, string: String): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(Option(boolean), Option(numeric), Option(string))
|
||||
def apply(boolean: Option[String], numeric: Option[String], string: Option[String], jline3: Boolean): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
|
||||
def apply(boolean: String, numeric: String, string: String, jline3: Boolean): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(Option(boolean), Option(numeric), Option(string), jline3)
|
||||
}
|
||||
|
|
|
|||
48
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetAttributesCommand.scala
generated
Normal file
48
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetAttributesCommand.scala
generated
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol
|
||||
final class TerminalSetAttributesCommand private (
|
||||
val iflag: String,
|
||||
val oflag: String,
|
||||
val cflag: String,
|
||||
val lflag: String,
|
||||
val cchars: String) extends sbt.protocol.CommandMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: TerminalSetAttributesCommand => (this.iflag == x.iflag) && (this.oflag == x.oflag) && (this.cflag == x.cflag) && (this.lflag == x.lflag) && (this.cchars == x.cchars)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.TerminalSetAttributesCommand".##) + iflag.##) + oflag.##) + cflag.##) + lflag.##) + cchars.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalSetAttributesCommand(" + iflag + ", " + oflag + ", " + cflag + ", " + lflag + ", " + cchars + ")"
|
||||
}
|
||||
private[this] def copy(iflag: String = iflag, oflag: String = oflag, cflag: String = cflag, lflag: String = lflag, cchars: String = cchars): TerminalSetAttributesCommand = {
|
||||
new TerminalSetAttributesCommand(iflag, oflag, cflag, lflag, cchars)
|
||||
}
|
||||
def withIflag(iflag: String): TerminalSetAttributesCommand = {
|
||||
copy(iflag = iflag)
|
||||
}
|
||||
def withOflag(oflag: String): TerminalSetAttributesCommand = {
|
||||
copy(oflag = oflag)
|
||||
}
|
||||
def withCflag(cflag: String): TerminalSetAttributesCommand = {
|
||||
copy(cflag = cflag)
|
||||
}
|
||||
def withLflag(lflag: String): TerminalSetAttributesCommand = {
|
||||
copy(lflag = lflag)
|
||||
}
|
||||
def withCchars(cchars: String): TerminalSetAttributesCommand = {
|
||||
copy(cchars = cchars)
|
||||
}
|
||||
}
|
||||
object TerminalSetAttributesCommand {
|
||||
|
||||
def apply(iflag: String, oflag: String, cflag: String, lflag: String, cchars: String): TerminalSetAttributesCommand = new TerminalSetAttributesCommand(iflag, oflag, cflag, lflag, cchars)
|
||||
}
|
||||
29
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetAttributesResponse.scala
generated
Normal file
29
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetAttributesResponse.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
|
||||
final class TerminalSetAttributesResponse private () extends sbt.protocol.EventMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case _: TerminalSetAttributesResponse => true
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (17 + "sbt.protocol.TerminalSetAttributesResponse".##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalSetAttributesResponse()"
|
||||
}
|
||||
private[this] def copy(): TerminalSetAttributesResponse = {
|
||||
new TerminalSetAttributesResponse()
|
||||
}
|
||||
|
||||
}
|
||||
object TerminalSetAttributesResponse {
|
||||
|
||||
def apply(): TerminalSetAttributesResponse = new TerminalSetAttributesResponse()
|
||||
}
|
||||
36
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetSizeCommand.scala
generated
Normal file
36
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetSizeCommand.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 TerminalSetSizeCommand private (
|
||||
val width: Int,
|
||||
val height: Int) extends sbt.protocol.CommandMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: TerminalSetSizeCommand => (this.width == x.width) && (this.height == x.height)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (17 + "sbt.protocol.TerminalSetSizeCommand".##) + width.##) + height.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalSetSizeCommand(" + width + ", " + height + ")"
|
||||
}
|
||||
private[this] def copy(width: Int = width, height: Int = height): TerminalSetSizeCommand = {
|
||||
new TerminalSetSizeCommand(width, height)
|
||||
}
|
||||
def withWidth(width: Int): TerminalSetSizeCommand = {
|
||||
copy(width = width)
|
||||
}
|
||||
def withHeight(height: Int): TerminalSetSizeCommand = {
|
||||
copy(height = height)
|
||||
}
|
||||
}
|
||||
object TerminalSetSizeCommand {
|
||||
|
||||
def apply(width: Int, height: Int): TerminalSetSizeCommand = new TerminalSetSizeCommand(width, height)
|
||||
}
|
||||
29
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetSizeResponse.scala
generated
Normal file
29
protocol/src/main/contraband-scala/sbt/protocol/TerminalSetSizeResponse.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
|
||||
final class TerminalSetSizeResponse private () extends sbt.protocol.EventMessage() with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case _: TerminalSetSizeResponse => true
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (17 + "sbt.protocol.TerminalSetSizeResponse".##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"TerminalSetSizeResponse()"
|
||||
}
|
||||
private[this] def copy(): TerminalSetSizeResponse = {
|
||||
new TerminalSetSizeResponse()
|
||||
}
|
||||
|
||||
}
|
||||
object TerminalSetSizeResponse {
|
||||
|
||||
def apply(): TerminalSetSizeResponse = new TerminalSetSizeResponse()
|
||||
}
|
||||
|
|
@ -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 =>
|
||||
implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat5[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery]("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.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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat7[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure, sbt.protocol.TerminalPropertiesResponse, sbt.protocol.TerminalCapabilitiesResponse]("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.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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
|||
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
|
||||
with sbt.protocol.codec.CommandMessageFormats
|
||||
with sbt.protocol.codec.CompletionParamsFormats
|
||||
with sbt.protocol.codec.ChannelAcceptedEventFormats
|
||||
|
|
@ -20,6 +23,9 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
|||
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
|
||||
with sbt.protocol.codec.EventMessageFormats
|
||||
with sbt.protocol.codec.SettingQueryResponseFormats
|
||||
with sbt.protocol.codec.CompletionResponseFormats
|
||||
|
|
|
|||
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalAttributesQueryFormats.scala
generated
Normal file
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalAttributesQueryFormats.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 TerminalAttributesQueryFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalAttributesQueryFormat: JsonFormat[sbt.protocol.TerminalAttributesQuery] = new JsonFormat[sbt.protocol.TerminalAttributesQuery] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalAttributesQuery = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalAttributesQuery()
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalAttributesQuery, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
35
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalAttributesResponseFormats.scala
generated
Normal file
35
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalAttributesResponseFormats.scala
generated
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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 TerminalAttributesResponseFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalAttributesResponseFormat: JsonFormat[sbt.protocol.TerminalAttributesResponse] = new JsonFormat[sbt.protocol.TerminalAttributesResponse] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalAttributesResponse = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
val iflag = unbuilder.readField[String]("iflag")
|
||||
val oflag = unbuilder.readField[String]("oflag")
|
||||
val cflag = unbuilder.readField[String]("cflag")
|
||||
val lflag = unbuilder.readField[String]("lflag")
|
||||
val cchars = unbuilder.readField[String]("cchars")
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalAttributesResponse(iflag, oflag, cflag, lflag, cchars)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalAttributesResponse, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("iflag", obj.iflag)
|
||||
builder.addField("oflag", obj.oflag)
|
||||
builder.addField("cflag", obj.cflag)
|
||||
builder.addField("lflag", obj.lflag)
|
||||
builder.addField("cchars", obj.cchars)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,9 @@ implicit lazy val TerminalCapabilitiesQueryFormat: JsonFormat[sbt.protocol.Termi
|
|||
val boolean = unbuilder.readField[Option[String]]("boolean")
|
||||
val numeric = unbuilder.readField[Option[String]]("numeric")
|
||||
val string = unbuilder.readField[Option[String]]("string")
|
||||
val jline3 = unbuilder.readField[Boolean]("jline3")
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalCapabilitiesQuery(boolean, numeric, string)
|
||||
sbt.protocol.TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
|
|
@ -25,6 +26,7 @@ implicit lazy val TerminalCapabilitiesQueryFormat: JsonFormat[sbt.protocol.Termi
|
|||
builder.addField("boolean", obj.boolean)
|
||||
builder.addField("numeric", obj.numeric)
|
||||
builder.addField("string", obj.string)
|
||||
builder.addField("jline3", obj.jline3)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetAttributesCommandFormats.scala
generated
Normal file
35
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetAttributesCommandFormats.scala
generated
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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 TerminalSetAttributesCommandFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalSetAttributesCommandFormat: JsonFormat[sbt.protocol.TerminalSetAttributesCommand] = new JsonFormat[sbt.protocol.TerminalSetAttributesCommand] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetAttributesCommand = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
val iflag = unbuilder.readField[String]("iflag")
|
||||
val oflag = unbuilder.readField[String]("oflag")
|
||||
val cflag = unbuilder.readField[String]("cflag")
|
||||
val lflag = unbuilder.readField[String]("lflag")
|
||||
val cchars = unbuilder.readField[String]("cchars")
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalSetAttributesCommand(iflag, oflag, cflag, lflag, cchars)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalSetAttributesCommand, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("iflag", obj.iflag)
|
||||
builder.addField("oflag", obj.oflag)
|
||||
builder.addField("cflag", obj.cflag)
|
||||
builder.addField("lflag", obj.lflag)
|
||||
builder.addField("cchars", obj.cchars)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetAttributesResponseFormats.scala
generated
Normal file
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetAttributesResponseFormats.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 TerminalSetAttributesResponseFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalSetAttributesResponseFormat: JsonFormat[sbt.protocol.TerminalSetAttributesResponse] = new JsonFormat[sbt.protocol.TerminalSetAttributesResponse] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetAttributesResponse = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalSetAttributesResponse()
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalSetAttributesResponse, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
29
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetSizeCommandFormats.scala
generated
Normal file
29
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetSizeCommandFormats.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 TerminalSetSizeCommandFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalSetSizeCommandFormat: JsonFormat[sbt.protocol.TerminalSetSizeCommand] = new JsonFormat[sbt.protocol.TerminalSetSizeCommand] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetSizeCommand = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
val width = unbuilder.readField[Int]("width")
|
||||
val height = unbuilder.readField[Int]("height")
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalSetSizeCommand(width, height)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalSetSizeCommand, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("width", obj.width)
|
||||
builder.addField("height", obj.height)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetSizeResponseFormats.scala
generated
Normal file
27
protocol/src/main/contraband-scala/sbt/protocol/codec/TerminalSetSizeResponseFormats.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 TerminalSetSizeResponseFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TerminalSetSizeResponseFormat: JsonFormat[sbt.protocol.TerminalSetSizeResponse] = new JsonFormat[sbt.protocol.TerminalSetSizeResponse] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetSizeResponse = {
|
||||
__jsOpt match {
|
||||
case Some(__js) =>
|
||||
unbuilder.beginObject(__js)
|
||||
|
||||
unbuilder.endObject()
|
||||
sbt.protocol.TerminalSetSizeResponse()
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.protocol.TerminalSetSizeResponse, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,22 +85,50 @@ type ExecutionEvent {
|
|||
}
|
||||
|
||||
type TerminalPropertiesResponse implements EventMessage {
|
||||
width: Int!
|
||||
height: Int!
|
||||
isAnsiSupported: Boolean!
|
||||
isColorEnabled: Boolean!
|
||||
isSupershellEnabled: Boolean!
|
||||
isEchoEnabled: Boolean!
|
||||
width: Int!
|
||||
height: Int!
|
||||
isAnsiSupported: Boolean!
|
||||
isColorEnabled: Boolean!
|
||||
isSupershellEnabled: Boolean!
|
||||
isEchoEnabled: Boolean!
|
||||
}
|
||||
|
||||
type TerminalCapabilitiesQuery implements CommandMessage {
|
||||
boolean: String
|
||||
numeric: String
|
||||
string: String
|
||||
boolean: String
|
||||
numeric: String
|
||||
string: String
|
||||
jline3: Boolean!
|
||||
}
|
||||
|
||||
type TerminalCapabilitiesResponse implements EventMessage {
|
||||
boolean: Boolean
|
||||
numeric: Int
|
||||
string: String
|
||||
boolean: Boolean
|
||||
numeric: Int
|
||||
string: String
|
||||
}
|
||||
|
||||
type TerminalSetAttributesCommand implements CommandMessage {
|
||||
iflag: String!,
|
||||
oflag: String!,
|
||||
cflag: String!,
|
||||
lflag: String!,
|
||||
cchars: String!,
|
||||
}
|
||||
|
||||
type TerminalSetAttributesResponse implements EventMessage {}
|
||||
|
||||
type TerminalAttributesQuery implements CommandMessage {}
|
||||
|
||||
type TerminalAttributesResponse implements EventMessage {
|
||||
iflag: String!,
|
||||
oflag: String!,
|
||||
cflag: String!,
|
||||
lflag: String!,
|
||||
cchars: String!,
|
||||
}
|
||||
|
||||
type TerminalSetSizeCommand implements CommandMessage {
|
||||
width: Int!
|
||||
height: Int!
|
||||
}
|
||||
|
||||
type TerminalSetSizeResponse implements EventMessage {}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ object Serialization {
|
|||
private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8"
|
||||
val systemIn = "sbt/systemIn"
|
||||
val systemOut = "sbt/systemOut"
|
||||
val systemOutFlush = "sbt/systemOutFlush"
|
||||
val terminalPropertiesQuery = "sbt/terminalPropertiesQuery"
|
||||
val terminalPropertiesResponse = "sbt/terminalPropertiesResponse"
|
||||
val terminalCapabilities = "sbt/terminalCapabilities"
|
||||
|
|
@ -34,6 +35,9 @@ object Serialization {
|
|||
val attachResponse = "sbt/attachResponse"
|
||||
val cancelRequest = "sbt/cancelRequest"
|
||||
val promptChannel = "sbt/promptChannel"
|
||||
val setTerminalAttributes = "sbt/setTerminalAttributes"
|
||||
val getTerminalAttributes = "sbt/getTerminalAttributes"
|
||||
val setTerminalSize = "sbt/setTerminalSize"
|
||||
val CancelAll = "__CancelAll"
|
||||
|
||||
@deprecated("unused", since = "1.4.0")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package sbt
|
|||
|
||||
import sbt.internal.util.ConsoleAppender.ClearScreenAfterCursor
|
||||
import sbt.internal.util.Util.{ AnyOps, none }
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object SelectMainClass {
|
||||
// Some(SimpleReader.readLine _)
|
||||
|
|
@ -21,14 +22,22 @@ object SelectMainClass {
|
|||
case head :: Nil => Some(head)
|
||||
case multiple =>
|
||||
promptIfMultipleChoices.flatMap { prompt =>
|
||||
val header = "\nMultiple main classes detected. Select one to run:\n"
|
||||
val classes = multiple.zipWithIndex
|
||||
.map { case (className, index) => s" [${index + 1}] $className" }
|
||||
.mkString("\n")
|
||||
println(ClearScreenAfterCursor + header + classes + "\n")
|
||||
|
||||
val line = trim(prompt("Enter number: "))
|
||||
toInt(line, multiple.length) map multiple.apply
|
||||
@tailrec def loop(): Option[String] = {
|
||||
val header = "\nMultiple main classes detected. Select one to run:\n"
|
||||
val classes = multiple.zipWithIndex
|
||||
.map { case (className, index) => s" [${index + 1}] $className" }
|
||||
.mkString("\n")
|
||||
println(ClearScreenAfterCursor + header + classes + "\n")
|
||||
val line = trim(prompt("Enter number: "))
|
||||
// An empty line usually means the user typed <ctrl+c>
|
||||
if (line.nonEmpty) {
|
||||
toInt(line, multiple.length) map multiple.apply match {
|
||||
case None => loop()
|
||||
case r => r
|
||||
}
|
||||
} else None
|
||||
}
|
||||
loop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +53,7 @@ object SelectMainClass {
|
|||
}
|
||||
} catch {
|
||||
case nfe: NumberFormatException =>
|
||||
println("Invalid number: " + nfe.toString)
|
||||
println(s"Invalid number: '$s'")
|
||||
none
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,11 @@ failingTask := {
|
|||
throw new IllegalStateException("failed")
|
||||
}
|
||||
|
||||
onChangedBuildSource := ReloadOnSourceChanges
|
||||
watchOnIteration := { (count, project, commands) =>
|
||||
val extra = baseDirectory.value / "extra.sbt"
|
||||
IO.copyFile(baseDirectory.value / "changes" / "extra.sbt", extra, CopyOptions().withOverwrite(true))
|
||||
Watch.defaultStartWatch(count, project, commands).foreach(_.linesIterator.foreach(l => println(s"[info] $l")))
|
||||
Watch.Ignore
|
||||
}
|
||||
|
||||
Global / onChangedBuildSource := ReloadOnSourceChanges
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
Compile / compile := {
|
||||
Count.increment()
|
||||
// Trigger a new build by updating the last modified time
|
||||
val extra = baseDirectory.value / "extra.sbt"
|
||||
IO.copyFile(baseDirectory.value / "changes" / "extra.sbt", extra, CopyOptions().withOverwrite(true))
|
||||
(Compile / compile).value
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue