mirror of https://github.com/sbt/sbt.git
Upgrade LineReader to JLine3
This commit upgrades sbt to using jline3. The advantage to jline3 is that it has a significantly better tab completion engine that is more similar to what you get from zsh or fish. The diff is bigger than I'd hoped because there are a number of behaviors that are different in jline3 vs jline2 in how the library consumes input streams and implements various features. I also was unable to remove jline2 because we need it for older versions of the scala console to work correctly with the thin client. As a result, the changes are largely additive. A good amount of this commit was in adding more protocol so that the remote client can forward its jline3 terminal information to the server. There were a number of minor changes that I made that either fixed outstanding ui bugs from #5620 or regressions due to differences between jline3 and jline2. The number one thing that caused problems is that the jline3 LineReader insists on using a NonBlockingInputStream. The implementation ofo NonBlockingInputStream seems buggy. Moreover, sbt internally uses a non blocking input stream for system in so jline is adding non blocking to an already non blocking stream, which is frustrating. A long term solution might be to consider insourcing LineReader.java from jline3 and just adapting it to use an sbt terminal rather than fighting with the jline3 api. This would also have the advantage of not conflicting with other versions of jline3. Even if we don't, we may want to shade jline3 if that is possible.
This commit is contained in:
parent
ed4d40d3e2
commit
2ecf5967ee
16
build.sbt
16
build.sbt
|
|
@ -289,6 +289,7 @@ val completeProj = (project in file("internal") / "util-complete")
|
||||||
testedBaseSettings,
|
testedBaseSettings,
|
||||||
name := "Completion",
|
name := "Completion",
|
||||||
libraryDependencies += jline,
|
libraryDependencies += jline,
|
||||||
|
libraryDependencies += jline3,
|
||||||
mimaSettings,
|
mimaSettings,
|
||||||
// Parser is used publicly, so we can't break bincompat.
|
// Parser is used publicly, so we can't break bincompat.
|
||||||
mimaBinaryIssueFilters := Seq(
|
mimaBinaryIssueFilters := Seq(
|
||||||
|
|
@ -343,12 +344,20 @@ lazy val utilPosition = (project in file("internal") / "util-position")
|
||||||
|
|
||||||
lazy val utilLogging = (project in file("internal") / "util-logging")
|
lazy val utilLogging = (project in file("internal") / "util-logging")
|
||||||
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
|
||||||
.dependsOn(utilInterface)
|
.dependsOn(utilInterface, collectionProj)
|
||||||
.settings(
|
.settings(
|
||||||
utilCommonSettings,
|
utilCommonSettings,
|
||||||
name := "Util Logging",
|
name := "Util Logging",
|
||||||
libraryDependencies ++=
|
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 ++= Seq(scalacheck % "test", scalatest % "test"),
|
||||||
libraryDependencies ++= (scalaVersion.value match {
|
libraryDependencies ++= (scalaVersion.value match {
|
||||||
case v if v.startsWith("2.12.") => List(compilerPlugin(silencerPlugin))
|
case v if v.startsWith("2.12.") => List(compilerPlugin(silencerPlugin))
|
||||||
|
|
@ -1047,8 +1056,7 @@ lazy val sbtClientProj = (project in file("client"))
|
||||||
crossPaths := false,
|
crossPaths := false,
|
||||||
exportJars := true,
|
exportJars := true,
|
||||||
libraryDependencies += jansi,
|
libraryDependencies += jansi,
|
||||||
libraryDependencies += "net.java.dev.jna" % "jna" % "5.5.0",
|
libraryDependencies += jline3Jansi,
|
||||||
libraryDependencies += "net.java.dev.jna" % "jna-platform" % "5.5.0",
|
|
||||||
libraryDependencies += scalatest % "test",
|
libraryDependencies += scalatest % "test",
|
||||||
/*
|
/*
|
||||||
* On windows, the raw classpath is too large to be a command argument to an
|
* On windows, the raw classpath is too large to be a command argument to an
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,20 @@
|
||||||
{
|
{
|
||||||
"resources":[
|
"resources":[
|
||||||
{"pattern":"jline/console/completer/CandidateListCompletionHandler.properties"},
|
{"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":"darwin/x86_64/libsbtipcsocket.dylib"},
|
||||||
{"pattern":"linux/x86_64/libsbtipcsocket.so"},
|
{"pattern":"linux/x86_64/libsbtipcsocket.so"},
|
||||||
{"pattern":"win32/x86_64/sbtipcsocket.dll"}
|
{"pattern":"win32/x86_64/sbtipcsocket.dll"}
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,28 @@
|
||||||
package sbt.internal.util
|
package sbt.internal.util
|
||||||
|
|
||||||
import java.io._
|
import java.io._
|
||||||
|
import java.util.{ List => JList }
|
||||||
|
|
||||||
import jline.console.ConsoleReader
|
import jline.console.ConsoleReader
|
||||||
import jline.console.history.{ FileHistory, MemoryHistory }
|
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 sbt.internal.util.complete.Parser
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent.duration._
|
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]
|
def readLine(prompt: String, mask: Option[Char] = None): Option[String]
|
||||||
|
override def close(): Unit = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
object LineReader {
|
object LineReader {
|
||||||
|
|
@ -25,7 +37,67 @@ object LineReader {
|
||||||
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
|
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
|
||||||
val MaxHistorySize = 500
|
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(
|
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.withRawSystemIn {
|
||||||
|
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],
|
historyPath: Option[File],
|
||||||
terminal: Terminal,
|
terminal: Terminal,
|
||||||
prompt: Prompt = Prompt.Running,
|
prompt: Prompt = Prompt.Running,
|
||||||
|
|
@ -42,7 +114,6 @@ object LineReader {
|
||||||
cr.setHistoryEnabled(true)
|
cr.setHistoryEnabled(true)
|
||||||
cr
|
cr
|
||||||
}
|
}
|
||||||
|
|
||||||
def simple(terminal: Terminal): LineReader = new SimpleReader(None, HandleCONT, terminal)
|
def simple(terminal: Terminal): LineReader = new SimpleReader(None, HandleCONT, terminal)
|
||||||
def simple(
|
def simple(
|
||||||
historyPath: Option[File],
|
historyPath: Option[File],
|
||||||
|
|
@ -230,7 +301,7 @@ final class FullReader(
|
||||||
Terminal.console
|
Terminal.console
|
||||||
)
|
)
|
||||||
protected[this] val reader: ConsoleReader = {
|
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)
|
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
|
||||||
cr
|
cr
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +315,7 @@ class SimpleReader private[sbt] (
|
||||||
def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
|
def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
|
||||||
this(historyPath, handleCONT, Terminal.console)
|
this(historyPath, handleCONT, Terminal.console)
|
||||||
protected[this] val reader: ConsoleReader =
|
protected[this] val reader: ConsoleReader =
|
||||||
LineReader.createReader(historyPath, terminal)
|
LineReader.createJLine2Reader(historyPath, terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false) {
|
object SimpleReader extends SimpleReader(None, LineReader.HandleCONT, false) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package sbt.internal.util
|
||||||
|
|
||||||
import java.io.{ PrintStream, PrintWriter }
|
import java.io.{ PrintStream, PrintWriter }
|
||||||
import java.lang.StringBuilder
|
import java.lang.StringBuilder
|
||||||
|
import java.nio.channels.ClosedChannelException
|
||||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger }
|
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger }
|
||||||
|
|
||||||
import org.apache.logging.log4j.core.appender.AbstractAppender
|
import org.apache.logging.log4j.core.appender.AbstractAppender
|
||||||
|
|
@ -394,7 +395,8 @@ class ConsoleAppender private[ConsoleAppender] (
|
||||||
override def append(event: XLogEvent): Unit = {
|
override def append(event: XLogEvent): Unit = {
|
||||||
val level = ConsoleAppender.toLevel(event.getLevel)
|
val level = ConsoleAppender.toLevel(event.getLevel)
|
||||||
val message = event.getMessage
|
val message = event.getMessage
|
||||||
appendMessage(level, message)
|
try appendMessage(level, message)
|
||||||
|
catch { case _: ClosedChannelException => }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -181,9 +181,10 @@ object EscHelpers {
|
||||||
else res(index) = 32
|
else res(index) = 32
|
||||||
case 'm' =>
|
case 'm' =>
|
||||||
case ';' => state = csi
|
case ';' => state = csi
|
||||||
case _ =>
|
case b => state = csi
|
||||||
}
|
}
|
||||||
digit.clear()
|
digit.clear()
|
||||||
|
case b if state == esc => state = 0
|
||||||
case b =>
|
case b =>
|
||||||
res(index) = b
|
res(index) = b
|
||||||
index += 1
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ import sbt.internal.util.ConsoleAppender.{
|
||||||
ClearScreenAfterCursor,
|
ClearScreenAfterCursor,
|
||||||
CursorLeft1000,
|
CursorLeft1000,
|
||||||
DeleteLine,
|
DeleteLine,
|
||||||
cursorLeft,
|
|
||||||
cursorUp,
|
cursorUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,6 +32,10 @@ private[sbt] final class ProgressState(
|
||||||
blankZone,
|
blankZone,
|
||||||
new AtomicReference(new ArrayBuffer[Byte]),
|
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 = {
|
def reset(): Unit = {
|
||||||
progressLines.set(Nil)
|
progressLines.set(Nil)
|
||||||
padding.set(0)
|
padding.set(0)
|
||||||
|
|
@ -44,8 +47,9 @@ private[sbt] final class ProgressState(
|
||||||
currentLineBytes.set(new ArrayBuffer[Byte])
|
currentLineBytes.set(new ArrayBuffer[Byte])
|
||||||
}
|
}
|
||||||
|
|
||||||
private[util] def addBytes(terminal: Terminal, bytes: ArrayBuffer[Byte]): Unit = {
|
private[this] val lineSeparatorBytes: Array[Byte] = System.lineSeparator.getBytes("UTF-8")
|
||||||
val previous = currentLineBytes.get
|
private[util] def addBytes(terminal: Terminal, bytes: Seq[Byte]): Unit = {
|
||||||
|
val previous: ArrayBuffer[Byte] = currentLineBytes.get
|
||||||
val padding = this.padding.get
|
val padding = this.padding.get
|
||||||
val prevLineCount = if (padding > 0) terminal.lineCount(new String(previous.toArray)) else 0
|
val prevLineCount = if (padding > 0) terminal.lineCount(new String(previous.toArray)) else 0
|
||||||
previous ++= bytes
|
previous ++= bytes
|
||||||
|
|
@ -54,6 +58,16 @@ private[sbt] final class ProgressState(
|
||||||
val diff = newLineCount - prevLineCount
|
val diff = newLineCount - prevLineCount
|
||||||
this.padding.set(math.max(padding - diff, 0))
|
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)) {
|
||||||
|
lines
|
||||||
|
.split(System.lineSeparator)
|
||||||
|
.lastOption
|
||||||
|
.foreach(currentLineBytes.get ++= _.getBytes("UTF-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private[util] def printPrompt(terminal: Terminal, printStream: PrintStream): Unit =
|
private[util] def printPrompt(terminal: Terminal, printStream: PrintStream): Unit =
|
||||||
|
|
@ -62,31 +76,50 @@ private[sbt] final class ProgressState(
|
||||||
val pmpt = prefix.getBytes ++ terminal.prompt.render().getBytes
|
val pmpt = prefix.getBytes ++ terminal.prompt.render().getBytes
|
||||||
pmpt.foreach(b => printStream.write(b & 0xFF))
|
pmpt.foreach(b => printStream.write(b & 0xFF))
|
||||||
}
|
}
|
||||||
private[util] def reprint(terminal: Terminal, printStream: PrintStream): Unit = {
|
private[util] def write(
|
||||||
printPrompt(terminal, printStream)
|
terminal: Terminal,
|
||||||
if (progressLines.get.nonEmpty) {
|
bytes: Array[Byte],
|
||||||
val lines = printProgress(terminal, terminal.getLastLine.getOrElse(""))
|
printStream: PrintStream,
|
||||||
printStream.print(ClearScreenAfterCursor + lines)
|
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(
|
private[util] def printProgress(terminal: Terminal, lastLine: String): String = {
|
||||||
terminal: Terminal,
|
|
||||||
lastLine: String
|
|
||||||
): String = {
|
|
||||||
val previousLines = progressLines.get
|
val previousLines = progressLines.get
|
||||||
if (previousLines.nonEmpty) {
|
if (previousLines.nonEmpty) {
|
||||||
val currentLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
val currentLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
||||||
val (height, width) = terminal.getLineHeightAndWidth(lastLine)
|
val (height, width) = terminal.getLineHeightAndWidth(lastLine)
|
||||||
val left = cursorLeft(1000) // resets the position to the left
|
|
||||||
val offset = width > 0
|
val offset = width > 0
|
||||||
val pad = math.max(padding.get - height, 0)
|
val pad = math.max(padding.get - height, 0)
|
||||||
val start = (if (offset) "\n" else "")
|
val start = (if (offset) s"\n$CursorLeft1000" else "")
|
||||||
val totalSize = currentLength + blankZone + pad
|
val totalSize = currentLength + blankZone + pad
|
||||||
val blank = left + s"\n$DeleteLine" * (totalSize - currentLength)
|
val blank = CursorLeft1000 + s"\n$DeleteLine" * (totalSize - currentLength)
|
||||||
val lines = previousLines.mkString(DeleteLine, s"\n$DeleteLine", s"\n$DeleteLine")
|
val lines = previousLines.mkString(DeleteLine, s"\n$DeleteLine", s"\n$DeleteLine")
|
||||||
val resetCursorUp = cursorUp(totalSize + (if (offset) 1 else 0))
|
val resetCursorUp = cursorUp(totalSize + (if (offset) 1 else 0))
|
||||||
val resetCursor = resetCursorUp + left + lastLine
|
val resetCursor = resetCursorUp + CursorLeft1000 + lastLine
|
||||||
start + blank + lines + resetCursor
|
start + blank + lines + resetCursor
|
||||||
} else {
|
} else {
|
||||||
ClearScreenAfterCursor
|
ClearScreenAfterCursor
|
||||||
|
|
@ -108,6 +141,7 @@ private[sbt] object ProgressState {
|
||||||
terminal: Terminal
|
terminal: Terminal
|
||||||
): Unit = {
|
): Unit = {
|
||||||
val state = terminal.progressState
|
val state = terminal.progressState
|
||||||
|
val isAskUser = terminal.prompt.isInstanceOf[Prompt.AskUser]
|
||||||
val isRunning = terminal.prompt == Prompt.Running
|
val isRunning = terminal.prompt == Prompt.Running
|
||||||
val isBatch = terminal.prompt == Prompt.Batch
|
val isBatch = terminal.prompt == Prompt.Batch
|
||||||
val isWatch = terminal.prompt == Prompt.Watch
|
val isWatch = terminal.prompt == Prompt.Watch
|
||||||
|
|
@ -115,31 +149,27 @@ private[sbt] object ProgressState {
|
||||||
if (terminal.isSupershellEnabled) {
|
if (terminal.isSupershellEnabled) {
|
||||||
if (!pe.skipIfActive.getOrElse(false) || (!isRunning && !isBatch)) {
|
if (!pe.skipIfActive.getOrElse(false) || (!isRunning && !isBatch)) {
|
||||||
terminal.withPrintStream { ps =>
|
terminal.withPrintStream { ps =>
|
||||||
val info =
|
val commandFromThisTerminal = pe.channelName.fold(true)(_ == terminal.name)
|
||||||
if ((isRunning || isBatch || noPrompt) && pe.channelName
|
val info = if ((isRunning || isBatch || noPrompt) && commandFromThisTerminal) {
|
||||||
.fold(true)(_ == terminal.name)) {
|
pe.items.map { item =>
|
||||||
pe.items.map { item =>
|
val elapsed = item.elapsedMicros / 1000000L
|
||||||
val elapsed = item.elapsedMicros / 1000000L
|
s" | => ${item.name} ${elapsed}s"
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} 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 currentLength = info.foldLeft(0)(_ + terminal.lineCount(_))
|
||||||
val previousLines = state.progressLines.getAndSet(info)
|
val previousLines = state.progressLines.getAndSet(info)
|
||||||
val prevLength = previousLines.foldLeft(0)(_ + terminal.lineCount(_))
|
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 prevSize = prevLength + state.padding.get
|
||||||
|
|
||||||
val newPadding = math.max(0, prevSize - currentLength)
|
val lastLine =
|
||||||
state.padding.set(newPadding)
|
if (isAskUser) terminal.prompt.render() else terminal.getLastLine.getOrElse("")
|
||||||
|
state.padding.set(math.max(0, prevSize - currentLength))
|
||||||
state.printPrompt(terminal, ps)
|
state.printPrompt(terminal, ps)
|
||||||
ps.print(state.printProgress(terminal, lastLine))
|
ps.print(state.printProgress(terminal, lastLine))
|
||||||
ps.flush()
|
ps.flush()
|
||||||
|
|
|
||||||
|
|
@ -7,39 +7,29 @@
|
||||||
|
|
||||||
package sbt.internal.util
|
package sbt.internal.util
|
||||||
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
private[sbt] sealed trait Prompt {
|
private[sbt] sealed trait Prompt {
|
||||||
def mkPrompt: () => String
|
def mkPrompt: () => String
|
||||||
def render(): String
|
def render(): String
|
||||||
def wrappedOutputStream(terminal: Terminal): OutputStream
|
def reset(): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] object Prompt {
|
private[sbt] object Prompt {
|
||||||
private[sbt] case class AskUser(override val mkPrompt: () => String) extends Prompt {
|
private[sbt] case class AskUser(override val mkPrompt: () => String) extends Prompt {
|
||||||
private[this] val bytes = new LinkedBlockingQueue[Int]
|
private[this] val bytes = new LinkedBlockingQueue[Byte]
|
||||||
override def wrappedOutputStream(terminal: Terminal): OutputStream = new OutputStream {
|
def write(b: Array[Byte]): Unit = b.foreach(bytes.put)
|
||||||
override def write(b: Int): Unit = {
|
override def render(): String = {
|
||||||
if (b == 10) bytes.clear()
|
val res = new String(bytes.asScala.toArray, "UTF-8")
|
||||||
else bytes.put(b)
|
if (res.endsWith(System.lineSeparator)) "" else res
|
||||||
terminal.withPrintStream { p =>
|
|
||||||
p.write(b)
|
|
||||||
p.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override def flush(): Unit = terminal.withPrintStream(_.flush())
|
|
||||||
}
|
}
|
||||||
|
override def reset(): Unit = bytes.clear()
|
||||||
override def render(): String =
|
|
||||||
EscHelpers.stripMoves(new String(bytes.asScala.toArray.map(_.toByte)))
|
|
||||||
}
|
}
|
||||||
private[sbt] trait NoPrompt extends Prompt {
|
private[sbt] trait NoPrompt extends Prompt {
|
||||||
override val mkPrompt: () => String = () => ""
|
override val mkPrompt: () => String = () => ""
|
||||||
override def render(): 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 Running extends NoPrompt
|
||||||
private[sbt] case object Batch extends NoPrompt
|
private[sbt] case object Batch extends NoPrompt
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,17 @@
|
||||||
|
|
||||||
package sbt.internal.util
|
package sbt.internal.util
|
||||||
|
|
||||||
import java.io.{ InputStream, OutputStream, PrintStream }
|
import java.io.{ InputStream, InterruptedIOException, OutputStream, PrintStream }
|
||||||
import java.nio.channels.ClosedChannelException
|
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.atomic.{ AtomicBoolean, AtomicReference }
|
||||||
import java.util.concurrent.{ ConcurrentHashMap, Executors, LinkedBlockingQueue, TimeUnit }
|
import java.util.concurrent.{ ArrayBlockingQueue, Executors, LinkedBlockingQueue, TimeUnit }
|
||||||
|
|
||||||
import jline.DefaultTerminal2
|
import jline.DefaultTerminal2
|
||||||
import jline.console.ConsoleReader
|
import jline.console.ConsoleReader
|
||||||
import sbt.internal.util.ConsoleAppender.{ ClearScreenAfterCursor, CursorLeft1000 }
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.collection.mutable.ArrayBuffer
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
import scala.util.control.NonFatal
|
||||||
|
|
||||||
trait Terminal extends AutoCloseable {
|
trait Terminal extends AutoCloseable {
|
||||||
|
|
||||||
|
|
@ -111,9 +109,12 @@ trait Terminal extends AutoCloseable {
|
||||||
*/
|
*/
|
||||||
private[sbt] def getLastLine: Option[String]
|
private[sbt] def getLastLine: Option[String]
|
||||||
|
|
||||||
private[sbt] def getBooleanCapability(capability: String): Boolean
|
private[sbt] def getBooleanCapability(capability: String, jline3: Boolean): Boolean
|
||||||
private[sbt] def getNumericCapability(capability: String): Int
|
private[sbt] def getNumericCapability(capability: String, jline3: Boolean): Integer
|
||||||
private[sbt] def getStringCapability(capability: String): String
|
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 name: String
|
||||||
private[sbt] def withRawSystemIn[T](f: => T): T = f
|
private[sbt] def withRawSystemIn[T](f: => T): T = f
|
||||||
|
|
@ -142,7 +143,8 @@ trait Terminal extends AutoCloseable {
|
||||||
val len = l.length
|
val len = l.length
|
||||||
if (width > 0 && len > 0) (len - 1 + width) / width else 0
|
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 +190,13 @@ object Terminal {
|
||||||
override def enableInterruptCharacter(): Unit = {}
|
override def enableInterruptCharacter(): Unit = {}
|
||||||
override def getOutputEncoding: String = null
|
override def getOutputEncoding: String = null
|
||||||
override def getBooleanCapability(capability: String): Boolean = {
|
override def getBooleanCapability(capability: String): Boolean = {
|
||||||
term.getBooleanCapability(capability)
|
term.getBooleanCapability(capability, jline3 = false)
|
||||||
}
|
}
|
||||||
override def getNumericCapability(capability: String): Integer = {
|
override def getNumericCapability(capability: String): Integer = {
|
||||||
term.getNumericCapability(capability)
|
term.getNumericCapability(capability, jline3 = false)
|
||||||
}
|
}
|
||||||
override def getStringCapability(capability: String): String = {
|
override def getStringCapability(capability: String): String = {
|
||||||
term.getStringCapability(capability)
|
term.getStringCapability(capability, jline3 = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,18 +255,45 @@ object Terminal {
|
||||||
*/
|
*/
|
||||||
private[sbt] def restore(): Unit = console.toJLine.restore()
|
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
|
* @param f the thunk to run
|
||||||
* @tparam T the result type of the thunk
|
* @tparam T the result type of the thunk
|
||||||
* @return the result 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") {
|
if (System.getProperty("sbt.io.virtual", "true") == "true") {
|
||||||
|
hasProgress.set(isServer)
|
||||||
try withOut(withIn(f))
|
try withOut(withIn(f))
|
||||||
finally {
|
finally {
|
||||||
jline.TerminalFactory.reset()
|
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
|
} else f
|
||||||
|
|
||||||
|
|
@ -281,10 +310,16 @@ object Terminal {
|
||||||
override def isEchoEnabled: Boolean = t.isEchoEnabled
|
override def isEchoEnabled: Boolean = t.isEchoEnabled
|
||||||
override def isSuccessEnabled: Boolean = t.isSuccessEnabled
|
override def isSuccessEnabled: Boolean = t.isSuccessEnabled
|
||||||
override def isSupershellEnabled: Boolean = t.isSupershellEnabled
|
override def isSupershellEnabled: Boolean = t.isSupershellEnabled
|
||||||
override def getBooleanCapability(capability: String): Boolean =
|
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||||
t.getBooleanCapability(capability)
|
t.getBooleanCapability(capability, jline3)
|
||||||
override def getNumericCapability(capability: String): Int = t.getNumericCapability(capability)
|
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
|
||||||
override def getStringCapability(capability: String): String = t.getStringCapability(capability)
|
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 withRawSystemIn[T](f: => T): T = t.withRawSystemIn(f)
|
override def withRawSystemIn[T](f: => T): T = t.withRawSystemIn(f)
|
||||||
override def withCanonicalIn[T](f: => T): T = t.withCanonicalIn(f)
|
override def withCanonicalIn[T](f: => T): T = t.withCanonicalIn(f)
|
||||||
override def printStream: PrintStream = t.printStream
|
override def printStream: PrintStream = t.printStream
|
||||||
|
|
@ -322,18 +357,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[this] val originalIn = System.in
|
||||||
private[sbt] class WriteableInputStream(in: InputStream, name: String)
|
private[sbt] class WriteableInputStream(in: InputStream, name: String)
|
||||||
extends InputStream
|
extends InputStream
|
||||||
with AutoCloseable {
|
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 =
|
private[this] val executor =
|
||||||
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader"))
|
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader"))
|
||||||
private[this] val buffer = new LinkedBlockingQueue[Integer]
|
private[this] val buffer = new LinkedBlockingQueue[Integer]
|
||||||
private[this] val closed = new AtomicBoolean(false)
|
private[this] val closed = new AtomicBoolean(false)
|
||||||
private[this] val resultQueue = new LinkedBlockingQueue[LinkedBlockingQueue[Int]]
|
private[this] val readQueue = new LinkedBlockingQueue[Unit]
|
||||||
private[this] val waiting = ConcurrentHashMap.newKeySet[LinkedBlockingQueue[Int]]
|
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.
|
* Starts a loop that waits for consumers of the InputStream to call read.
|
||||||
* When read is called, we enqueue a `LinkedBlockingQueue[Int]` to which
|
* When read is called, we enqueue a `LinkedBlockingQueue[Int]` to which
|
||||||
|
|
@ -354,11 +406,14 @@ object Terminal {
|
||||||
*/
|
*/
|
||||||
private[this] val runnable: Runnable = () => {
|
private[this] val runnable: Runnable = () => {
|
||||||
@tailrec def impl(): Unit = {
|
@tailrec def impl(): Unit = {
|
||||||
val result = resultQueue.take
|
val _ = readQueue.take
|
||||||
val b = in.read
|
val b = in.read
|
||||||
// The downstream consumer may have been interrupted. Buffer the result
|
// The downstream consumer may have been interrupted. Buffer the result
|
||||||
// when that hapens.
|
// 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()
|
if (b != -1 && !Thread.interrupted()) impl()
|
||||||
else closed.set(true)
|
else closed.set(true)
|
||||||
}
|
}
|
||||||
|
|
@ -370,21 +425,28 @@ object Terminal {
|
||||||
if (closed.get) -1
|
if (closed.get) -1
|
||||||
else
|
else
|
||||||
synchronized {
|
synchronized {
|
||||||
buffer.poll match {
|
readThread.set(Thread.currentThread)
|
||||||
|
try buffer.poll match {
|
||||||
case null =>
|
case null =>
|
||||||
val result = new LinkedBlockingQueue[Int]
|
val result = new LinkedBlockingQueue[Integer]
|
||||||
waiting.add(result)
|
waiting.synchronized(waiting.put(result))
|
||||||
resultQueue.offer(result)
|
readQueue.put(())
|
||||||
try result.take
|
try result.take.toInt
|
||||||
catch {
|
catch {
|
||||||
case e: InterruptedException =>
|
case e: InterruptedException =>
|
||||||
waiting.remove(result)
|
waiting.remove(result)
|
||||||
throw e
|
-1
|
||||||
}
|
}
|
||||||
case b if b == -1 => throw new ClosedChannelException
|
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 = {
|
override def available(): Int = {
|
||||||
buffer.size
|
buffer.size
|
||||||
|
|
@ -524,7 +586,7 @@ object Terminal {
|
||||||
}
|
}
|
||||||
override def flush(): Unit = os.flush()
|
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)"
|
override def toString: String = s"proxyPrintStream($proxyOutputStream)"
|
||||||
}
|
}
|
||||||
private[this] lazy val isWindows =
|
private[this] lazy val isWindows =
|
||||||
|
|
@ -592,9 +654,21 @@ object Terminal {
|
||||||
case t: jline.Terminal2 => t
|
case t: jline.Terminal2 => t
|
||||||
case _ => new DefaultTerminal2(terminal)
|
case _ => new DefaultTerminal2(terminal)
|
||||||
}
|
}
|
||||||
override def init(): Unit = if (alive) terminal.init()
|
override def init(): Unit =
|
||||||
override def restore(): Unit = if (alive) terminal.restore()
|
if (alive)
|
||||||
override def reset(): Unit = if (alive) terminal.reset()
|
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 isSupported: Boolean = terminal.isSupported
|
||||||
override def getWidth: Int = props.map(_.width).getOrElse(terminal.getWidth)
|
override def getWidth: Int = props.map(_.width).getOrElse(terminal.getWidth)
|
||||||
override def getHeight: Int = props.map(_.height).getOrElse(terminal.getHeight)
|
override def getHeight: Int = props.map(_.height).getOrElse(terminal.getHeight)
|
||||||
|
|
@ -650,7 +724,7 @@ object Terminal {
|
||||||
fixTerminalProperty()
|
fixTerminalProperty()
|
||||||
|
|
||||||
private[sbt] def createReader(term: Terminal, prompt: Prompt): ConsoleReader = {
|
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 =
|
override def readLine(prompt: String, mask: Character): String =
|
||||||
term.withRawSystemIn(super.readLine(prompt, mask))
|
term.withRawSystemIn(super.readLine(prompt, mask))
|
||||||
override def readLine(prompt: String): String = term.withRawSystemIn(super.readLine(prompt))
|
override def readLine(prompt: String): String = term.withRawSystemIn(super.readLine(prompt))
|
||||||
|
|
@ -662,6 +736,9 @@ object Terminal {
|
||||||
case term => term
|
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")
|
@deprecated("For compatibility only", "1.4.0")
|
||||||
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
|
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
|
||||||
private class ConsoleTerminal(
|
private class ConsoleTerminal(
|
||||||
|
|
@ -669,28 +746,36 @@ object Terminal {
|
||||||
in: InputStream,
|
in: InputStream,
|
||||||
out: OutputStream
|
out: OutputStream
|
||||||
) extends TerminalImpl(in, out, "console0") {
|
) 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")
|
private[this] def isCI = sys.env.contains("BUILD_NUMBER") || sys.env.contains("CI")
|
||||||
override def getWidth: Int = term.getWidth
|
override def getWidth: Int = system.getSize.getColumns
|
||||||
override def getHeight: Int = term.getHeight
|
override def getHeight: Int = system.getSize.getRows
|
||||||
override def isAnsiSupported: Boolean = term.isAnsiSupported && !isCI
|
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 isSuccessEnabled: Boolean = true
|
||||||
override def getBooleanCapability(capability: String): Boolean =
|
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||||
term.getBooleanCapability(capability)
|
if (jline3) capabilityMap.get(capability).fold(false)(system.getBooleanCapability)
|
||||||
override def getNumericCapability(capability: String): Int =
|
else term.getBooleanCapability(capability)
|
||||||
term.getNumericCapability(capability)
|
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
|
||||||
override def getStringCapability(capability: String): String =
|
if (jline3) capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability)
|
||||||
term.getStringCapability(capability)
|
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 private[sbt] def restore(): Unit = term.restore()
|
||||||
|
|
||||||
|
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 withRawSystemIn[T](f: => T): T = term.synchronized {
|
override def withRawSystemIn[T](f: => T): T = term.synchronized {
|
||||||
try {
|
val prev = JLine3.enterRawMode(system)
|
||||||
term.init()
|
try f
|
||||||
term.setEchoEnabled(false)
|
catch { case _: InterruptedIOException => throw new InterruptedException } finally {
|
||||||
f
|
setAttributes(prev)
|
||||||
} finally {
|
|
||||||
term.restore()
|
|
||||||
term.setEchoEnabled(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override def isColorEnabled: Boolean =
|
override def isColorEnabled: Boolean =
|
||||||
|
|
@ -705,29 +790,33 @@ object Terminal {
|
||||||
case "true" => true
|
case "true" => true
|
||||||
case _ => false
|
case _ => false
|
||||||
})
|
})
|
||||||
|
override def close(): Unit = {
|
||||||
|
try system.close()
|
||||||
|
catch { case NonFatal(_) => }
|
||||||
|
super.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private[sbt] abstract class TerminalImpl private[sbt] (
|
private[sbt] abstract class TerminalImpl private[sbt] (
|
||||||
val in: InputStream,
|
val in: InputStream,
|
||||||
val out: OutputStream,
|
val out: OutputStream,
|
||||||
override private[sbt] val name: String
|
override private[sbt] val name: String
|
||||||
) extends Terminal {
|
) 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 writeLock = new AnyRef
|
private[this] val writeLock = new AnyRef
|
||||||
private[this] val writeableInputStream = in match {
|
private[this] val writeableInputStream = in match {
|
||||||
case w: WriteableInputStream => w
|
case w: WriteableInputStream => w
|
||||||
case _ => new WriteableInputStream(in, name)
|
case _ => new WriteableInputStream(in, name)
|
||||||
}
|
}
|
||||||
def throwIfClosed[R](f: => R): R = if (isStopped.get) throw new ClosedChannelException else f
|
def throwIfClosed[R](f: => R): R = if (isStopped.get) throw new ClosedChannelException else f
|
||||||
|
override def getLastLine: Option[String] = progressState.currentLine
|
||||||
|
|
||||||
private val combinedOutputStream = new OutputStream {
|
private val combinedOutputStream = new OutputStream {
|
||||||
override def write(b: Int): Unit = {
|
override def write(b: Int): Unit = {
|
||||||
Option(bootOutputStreamHolder.get).foreach(_.write(b))
|
Option(bootOutputStreamHolder.get).foreach(_.write(b))
|
||||||
out.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 = {
|
override def write(b: Array[Byte], offset: Int, len: Int): Unit = {
|
||||||
Option(bootOutputStreamHolder.get).foreach(_.write(b, offset, len))
|
Option(bootOutputStreamHolder.get).foreach(_.write(b, offset, len))
|
||||||
out.write(b, offset, len)
|
out.write(b, offset, len)
|
||||||
|
|
@ -740,54 +829,19 @@ object Terminal {
|
||||||
|
|
||||||
override val outputStream = new OutputStream {
|
override val outputStream = new OutputStream {
|
||||||
override def write(b: Int): Unit = throwIfClosed {
|
override def write(b: Int): Unit = throwIfClosed {
|
||||||
writeLock.synchronized {
|
write(Array((b & 0xFF).toByte))
|
||||||
if (b == Int.MinValue) currentLine.set(new ArrayBuffer[Byte])
|
|
||||||
else doWrite(Vector((b & 0xFF).toByte))
|
|
||||||
if (b == 10) combinedOutputStream.flush()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
override def write(b: Array[Byte]): Unit = throwIfClosed(write(b, 0, b.length))
|
override def write(b: Array[Byte]): Unit = throwIfClosed {
|
||||||
override def write(b: Array[Byte], off: Int, len: Int): Unit = {
|
writeLock.synchronized(doWrite(b))
|
||||||
throwIfClosed {
|
}
|
||||||
writeLock.synchronized {
|
override def write(b: Array[Byte], offset: Int, length: Int): Unit = throwIfClosed {
|
||||||
val lo = math.max(0, off)
|
write(Arrays.copyOfRange(b, offset, offset + length))
|
||||||
val hi = math.min(math.max(off + len, 0), b.length)
|
|
||||||
doWrite(b.slice(off, off + len).toSeq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
override def flush(): Unit = combinedOutputStream.flush()
|
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 _ =>
|
|
||||||
}
|
|
||||||
if (prompt != Prompt.Loading) 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)
|
||||||
|
override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream)
|
||||||
override def inputStream: InputStream = writeableInputStream
|
override def inputStream: InputStream = writeableInputStream
|
||||||
|
|
||||||
private[sbt] def write(bytes: Int*): Unit = writeableInputStream.write(bytes: _*)
|
private[sbt] def write(bytes: Int*): Unit = writeableInputStream.write(bytes: _*)
|
||||||
|
|
@ -801,17 +855,7 @@ object Terminal {
|
||||||
case _ => (0, 0)
|
case _ => (0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def getLastLine: Option[String] = currentLine.get match {
|
private[this] val rawPrintStream: PrintStream = new LinePrintStream(combinedOutputStream)
|
||||||
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 = {}
|
|
||||||
}
|
|
||||||
override def withPrintStream[T](f: PrintStream => T): T =
|
override def withPrintStream[T](f: PrintStream => T): T =
|
||||||
writeLock.synchronized(f(rawPrintStream))
|
writeLock.synchronized(f(rawPrintStream))
|
||||||
|
|
||||||
|
|
@ -821,12 +865,12 @@ object Terminal {
|
||||||
}
|
}
|
||||||
private[sbt] val NullTerminal = new Terminal {
|
private[sbt] val NullTerminal = new Terminal {
|
||||||
override def close(): Unit = {}
|
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 getHeight: Int = 0
|
||||||
override def getLastLine: Option[String] = None
|
override def getLastLine: Option[String] = None
|
||||||
override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0)
|
override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0)
|
||||||
override def getNumericCapability(capability: String): Int = -1
|
override def getNumericCapability(capability: String, jline3: Boolean): Integer = null
|
||||||
override def getStringCapability(capability: String): String = null
|
override def getStringCapability(capability: String, jline3: Boolean): String = null
|
||||||
override def getWidth: Int = 0
|
override def getWidth: Int = 0
|
||||||
override def inputStream: java.io.InputStream = () => {
|
override def inputStream: java.io.InputStream = () => {
|
||||||
try this.synchronized(this.wait)
|
try this.synchronized(this.wait)
|
||||||
|
|
@ -839,6 +883,9 @@ object Terminal {
|
||||||
override def isSuccessEnabled: Boolean = false
|
override def isSuccessEnabled: Boolean = false
|
||||||
override def isSupershellEnabled: Boolean = false
|
override def isSupershellEnabled: Boolean = false
|
||||||
override def outputStream: java.io.OutputStream = _ => {}
|
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] def name: String = "NullTerminal"
|
||||||
override private[sbt] val printStream: java.io.PrintStream =
|
override private[sbt] val printStream: java.io.PrintStream =
|
||||||
new PrintStream(outputStream, false)
|
new PrintStream(outputStream, false)
|
||||||
|
|
|
||||||
|
|
@ -63,4 +63,11 @@ class CleanStringSpec extends FlatSpec {
|
||||||
val colorArrow = new String(Array[Byte](27, 91, 51, 54, 109, 62))
|
val colorArrow = new String(Array[Byte](27, 91, 51, 54, 109, 62))
|
||||||
assert(EscHelpers.stripMoves(original) == "foo" + colorArrow + " " + scala.Console.RESET)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ package client
|
||||||
import java.io.{ File, IOException, InputStream, PrintStream }
|
import java.io.{ File, IOException, InputStream, PrintStream }
|
||||||
import java.lang.ProcessBuilder.Redirect
|
import java.lang.ProcessBuilder.Redirect
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.nio.channels.ClosedChannelException
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
|
||||||
|
|
@ -43,10 +42,14 @@ import Serialization.{
|
||||||
promptChannel,
|
promptChannel,
|
||||||
systemIn,
|
systemIn,
|
||||||
systemOut,
|
systemOut,
|
||||||
|
systemOutFlush,
|
||||||
terminalCapabilities,
|
terminalCapabilities,
|
||||||
terminalCapabilitiesResponse,
|
terminalCapabilitiesResponse,
|
||||||
terminalPropertiesQuery,
|
terminalPropertiesQuery,
|
||||||
terminalPropertiesResponse
|
terminalPropertiesResponse,
|
||||||
|
getTerminalAttributes,
|
||||||
|
setTerminalAttributes,
|
||||||
|
setTerminalSize,
|
||||||
}
|
}
|
||||||
import NetworkClient.Arguments
|
import NetworkClient.Arguments
|
||||||
|
|
||||||
|
|
@ -199,7 +202,6 @@ class NetworkClient(
|
||||||
case _ => (false, None)
|
case _ => (false, None)
|
||||||
}
|
}
|
||||||
if (rebootCommands.nonEmpty) {
|
if (rebootCommands.nonEmpty) {
|
||||||
if (Terminal.console.getLastLine.isDefined) Terminal.console.printStream.println()
|
|
||||||
rebooting.set(true)
|
rebooting.set(true)
|
||||||
attached.set(false)
|
attached.set(false)
|
||||||
connectionHolder.getAndSet(null) match {
|
connectionHolder.getAndSet(null) match {
|
||||||
|
|
@ -212,7 +214,7 @@ class NetworkClient(
|
||||||
rebooting.set(false)
|
rebooting.set(false)
|
||||||
rebootCommands match {
|
rebootCommands match {
|
||||||
case Some((execId, cmd)) if execId.nonEmpty =>
|
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(
|
console.appendLog(
|
||||||
Level.Error,
|
Level.Error,
|
||||||
s"received request to re-run unknown command '$cmd' after reboot"
|
s"received request to re-run unknown command '$cmd' after reboot"
|
||||||
|
|
@ -230,8 +232,6 @@ class NetworkClient(
|
||||||
} else {
|
} else {
|
||||||
if (!rebooting.get() && running.compareAndSet(true, false) && log) {
|
if (!rebooting.get() && running.compareAndSet(true, false) && log) {
|
||||||
if (!arguments.commandArguments.contains(Shutdown)) {
|
if (!arguments.commandArguments.contains(Shutdown)) {
|
||||||
if (Terminal.console.getLastLine.isDefined)
|
|
||||||
Terminal.console.printStream.println()
|
|
||||||
console.appendLog(Level.Error, "sbt server disconnected")
|
console.appendLog(Level.Error, "sbt server disconnected")
|
||||||
exitClean.set(false)
|
exitClean.set(false)
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +306,6 @@ class NetworkClient(
|
||||||
Some(process)
|
Some(process)
|
||||||
case _ =>
|
case _ =>
|
||||||
if (log) {
|
if (log) {
|
||||||
if (Terminal.console.getLastLine.isDefined) Terminal.console.printStream.println()
|
|
||||||
console.appendLog(Level.Info, "sbt server is booting up")
|
console.appendLog(Level.Info, "sbt server is booting up")
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
@ -522,17 +521,15 @@ class NetworkClient(
|
||||||
}
|
}
|
||||||
} else Vector()
|
} else Vector()
|
||||||
case (`systemOut`, Some(json)) =>
|
case (`systemOut`, Some(json)) =>
|
||||||
Converter.fromJson[Seq[Byte]](json) match {
|
Converter.fromJson[Array[Byte]](json) match {
|
||||||
case Success(params) =>
|
case Success(bytes) if bytes.nonEmpty && attached.get =>
|
||||||
if (params.nonEmpty) {
|
synchronized(printStream.write(bytes))
|
||||||
if (attached.get) {
|
case _ =>
|
||||||
printStream.write(params.toArray)
|
|
||||||
printStream.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Failure(_) =>
|
|
||||||
}
|
}
|
||||||
Vector.empty
|
Vector.empty
|
||||||
|
case (`systemOutFlush`, _) =>
|
||||||
|
synchronized(printStream.flush())
|
||||||
|
Vector.empty
|
||||||
case (`promptChannel`, _) =>
|
case (`promptChannel`, _) =>
|
||||||
batchMode.set(false)
|
batchMode.set(false)
|
||||||
Vector.empty
|
Vector.empty
|
||||||
|
|
@ -589,16 +586,23 @@ class NetworkClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
def onRequest(msg: JsonRpcRequestMessage): Unit = {
|
def onRequest(msg: JsonRpcRequestMessage): Unit = {
|
||||||
|
import sbt.protocol.codec.JsonProtocol._
|
||||||
(msg.method, msg.params) match {
|
(msg.method, msg.params) match {
|
||||||
case (`terminalCapabilities`, Some(json)) =>
|
case (`terminalCapabilities`, Some(json)) =>
|
||||||
import sbt.protocol.codec.JsonProtocol._
|
|
||||||
Converter.fromJson[TerminalCapabilitiesQuery](json) match {
|
Converter.fromJson[TerminalCapabilitiesQuery](json) match {
|
||||||
case Success(terminalCapabilitiesQuery) =>
|
case Success(terminalCapabilitiesQuery) =>
|
||||||
|
val jline3 = terminalCapabilitiesQuery.jline3
|
||||||
val response = TerminalCapabilitiesResponse(
|
val response = TerminalCapabilitiesResponse(
|
||||||
terminalCapabilitiesQuery.boolean.map(Terminal.console.getBooleanCapability),
|
terminalCapabilitiesQuery.boolean
|
||||||
terminalCapabilitiesQuery.numeric.map(Terminal.console.getNumericCapability),
|
.map(Terminal.console.getBooleanCapability(_, jline3)),
|
||||||
|
terminalCapabilitiesQuery.numeric
|
||||||
|
.map(
|
||||||
|
c => Option(Terminal.console.getNumericCapability(c, jline3)).fold(-1)(_.toInt)
|
||||||
|
),
|
||||||
terminalCapabilitiesQuery.string
|
terminalCapabilitiesQuery.string
|
||||||
.map(s => Option(Terminal.console.getStringCapability(s)).getOrElse("null")),
|
.map(
|
||||||
|
s => Option(Terminal.console.getStringCapability(s, jline3)).getOrElse("null")
|
||||||
|
),
|
||||||
)
|
)
|
||||||
sendCommandResponse(
|
sendCommandResponse(
|
||||||
terminalCapabilitiesResponse,
|
terminalCapabilitiesResponse,
|
||||||
|
|
@ -617,6 +621,37 @@ class NetworkClient(
|
||||||
isEchoEnabled = Terminal.console.isEchoEnabled
|
isEchoEnabled = Terminal.console.isEchoEnabled
|
||||||
)
|
)
|
||||||
sendCommandResponse(terminalPropertiesResponse, response, msg.id)
|
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 _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -851,7 +886,7 @@ class NetworkClient(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try Terminal.console.withRawSystemIn(read())
|
try Terminal.console.withRawSystemIn(read())
|
||||||
catch { case _: InterruptedException | _: ClosedChannelException => stopped.set(true) }
|
catch { case NonFatal(_) => stopped.set(true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
def drain(): Unit = inLock.synchronized {
|
def drain(): Unit = inLock.synchronized {
|
||||||
|
|
@ -897,20 +932,18 @@ object NetworkClient {
|
||||||
override def success(msg: String): Unit = appender.success(msg)
|
override def success(msg: String): Unit = appender.success(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private def simpleConsoleInterface(printStream: PrintStream): ConsoleInterface =
|
private def simpleConsoleInterface(doPrintln: String => Unit): ConsoleInterface =
|
||||||
new ConsoleInterface {
|
new ConsoleInterface {
|
||||||
import scala.Console.{ GREEN, RED, RESET, YELLOW }
|
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 {
|
val prefix = level match {
|
||||||
case Level.Error => s"[$RED$level$RESET]"
|
case Level.Error => s"[$RED$level$RESET]"
|
||||||
case Level.Warn => s"[$YELLOW$level$RESET]"
|
case Level.Warn => s"[$YELLOW$level$RESET]"
|
||||||
case _ => s"[$RESET$level$RESET]"
|
case _ => s"[$RESET$level$RESET]"
|
||||||
}
|
}
|
||||||
message.split("\n").foreach { line =>
|
message.linesIterator.foreach(line => doPrintln(s"$prefix $line"))
|
||||||
if (!line.trim.isEmpty) printStream.println(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(
|
private[client] class Arguments(
|
||||||
val baseDirectory: File,
|
val baseDirectory: File,
|
||||||
|
|
@ -961,8 +994,29 @@ object NetworkClient {
|
||||||
baseDirectory: File,
|
baseDirectory: File,
|
||||||
args: Array[String],
|
args: Array[String],
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
errorStream: PrintStream,
|
|
||||||
printStream: 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
|
useJNI: Boolean
|
||||||
): Int = {
|
): Int = {
|
||||||
val client =
|
val client =
|
||||||
|
|
@ -970,8 +1024,8 @@ object NetworkClient {
|
||||||
NetworkClient.parseArgs(args).withBaseDirectory(baseDirectory),
|
NetworkClient.parseArgs(args).withBaseDirectory(baseDirectory),
|
||||||
inputStream,
|
inputStream,
|
||||||
errorStream,
|
errorStream,
|
||||||
printStream,
|
|
||||||
useJNI,
|
useJNI,
|
||||||
|
terminal
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
if (client.connect(log = true, promptCompleteUsers = false)) client.run()
|
if (client.connect(log = true, promptCompleteUsers = false)) client.run()
|
||||||
|
|
@ -982,17 +1036,27 @@ object NetworkClient {
|
||||||
arguments: Arguments,
|
arguments: Arguments,
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
errorStream: PrintStream,
|
errorStream: PrintStream,
|
||||||
printStream: PrintStream,
|
|
||||||
useJNI: Boolean,
|
useJNI: Boolean,
|
||||||
): NetworkClient =
|
terminal: Terminal
|
||||||
new NetworkClient(
|
): NetworkClient = {
|
||||||
arguments,
|
val doPrint: String => Unit = line => {
|
||||||
NetworkClient.simpleConsoleInterface(printStream),
|
if (terminal.getLastLine.isDefined) terminal.printStream.println()
|
||||||
inputStream,
|
terminal.printStream.println(line)
|
||||||
errorStream,
|
}
|
||||||
printStream,
|
val interface = NetworkClient.simpleConsoleInterface(doPrint)
|
||||||
useJNI,
|
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 = {
|
def main(args: Array[String]): Unit = {
|
||||||
val (jnaArg, restOfArgs) = args.partition(_ == "--jna")
|
val (jnaArg, restOfArgs) = args.partition(_ == "--jna")
|
||||||
val useJNI = jnaArg.isEmpty
|
val useJNI = jnaArg.isEmpty
|
||||||
|
|
@ -1005,8 +1069,9 @@ object NetworkClient {
|
||||||
System.out.flush()
|
System.out.flush()
|
||||||
})
|
})
|
||||||
Runtime.getRuntime.addShutdownHook(hook)
|
Runtime.getRuntime.addShutdownHook(hook)
|
||||||
System.exit(Terminal.withStreams {
|
System.exit(Terminal.withStreams(false) {
|
||||||
try client(base, restOfArgs, System.in, System.err, System.out, useJNI)
|
val term = Terminal.console
|
||||||
|
try client(base, restOfArgs, term.inputStream, System.err, term, useJNI)
|
||||||
finally {
|
finally {
|
||||||
Runtime.getRuntime.removeShutdownHook(hook)
|
Runtime.getRuntime.removeShutdownHook(hook)
|
||||||
hook.run()
|
hook.run()
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ import java.io.File
|
||||||
import java.nio.channels.ClosedChannelException
|
import java.nio.channels.ClosedChannelException
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
import jline.console.history.PersistentHistory
|
//import jline.console.history.PersistentHistory
|
||||||
import sbt.BasicCommandStrings.{ Cancel, TerminateAction, Shutdown }
|
import sbt.BasicCommandStrings.{ Cancel, TerminateAction, Shutdown }
|
||||||
import sbt.BasicKeys.{ historyPath, terminalShellPrompt }
|
import sbt.BasicKeys.{ historyPath, terminalShellPrompt }
|
||||||
import sbt.State
|
import sbt.State
|
||||||
import sbt.internal.CommandChannel
|
import sbt.internal.CommandChannel
|
||||||
import sbt.internal.util.ConsoleAppender.{ ClearPromptLine, ClearScreenAfterCursor, DeleteLine }
|
import sbt.internal.util.ConsoleAppender.{ ClearPromptLine, ClearScreenAfterCursor, DeleteLine }
|
||||||
import sbt.internal.util._
|
import sbt.internal.util._
|
||||||
import sbt.internal.util.complete.{ JLineCompletion, Parser }
|
import sbt.internal.util.complete.{ Parser }
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
|
@ -47,44 +47,31 @@ private[sbt] object UITask {
|
||||||
def terminalReader(parser: Parser[_])(
|
def terminalReader(parser: Parser[_])(
|
||||||
terminal: Terminal,
|
terminal: Terminal,
|
||||||
state: State
|
state: State
|
||||||
): Reader = {
|
): Reader = { () =>
|
||||||
val lineReader = LineReader.createReader(history(state), terminal, terminal.prompt)
|
try {
|
||||||
JLineCompletion.installCustomCompletor(lineReader, parser)
|
|
||||||
() => {
|
|
||||||
val clear = terminal.ansi(ClearPromptLine, "")
|
val clear = terminal.ansi(ClearPromptLine, "")
|
||||||
try {
|
@tailrec def impl(): Either[String, String] = {
|
||||||
@tailrec def impl(): Either[String, String] = {
|
val reader = LineReader.createReader(history(state), parser, terminal, terminal.prompt)
|
||||||
lineReader.readLine(clear + terminal.prompt.mkPrompt()) match {
|
(try reader.readLine(clear + terminal.prompt.mkPrompt())
|
||||||
case null if terminal == Terminal.console && System.console == null =>
|
finally reader.close) match {
|
||||||
// No stdin is attached to the process so just ignore the result and
|
case None if terminal == Terminal.console && System.console == null =>
|
||||||
// block until the thread is interrupted.
|
// No stdin is attached to the process so just ignore the result and
|
||||||
this.synchronized(this.wait())
|
// block until the thread is interrupted.
|
||||||
Right("") // should be unreachable
|
this.synchronized(this.wait())
|
||||||
// JLine returns null on ctrl+d when there is no other input. This interprets
|
Right("") // should be unreachable
|
||||||
// ctrl+d with no imput as an exit
|
// JLine returns null on ctrl+d when there is no other input. This interprets
|
||||||
case null => Left(TerminateAction)
|
// ctrl+d with no imput as an exit
|
||||||
case s: String =>
|
case None => Left(TerminateAction)
|
||||||
lineReader.getHistory match {
|
case Some(s: String) =>
|
||||||
case p: PersistentHistory =>
|
s.trim() match {
|
||||||
p.add(s)
|
case "" => impl()
|
||||||
p.flush()
|
case cmd @ (`Shutdown` | `TerminateAction` | `Cancel`) => Left(cmd)
|
||||||
case _ =>
|
case cmd => Right(cmd)
|
||||||
}
|
}
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl()
|
}
|
||||||
} catch {
|
impl()
|
||||||
case _: InterruptedException => Right("")
|
} catch { case e: InterruptedException => Right("") }
|
||||||
} finally lineReader.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private[this] def history(s: State): Option[File] =
|
private[this] def history(s: State): Option[File] =
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ public final class MetaBuildLoader extends URLClassLoader {
|
||||||
*/
|
*/
|
||||||
public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException {
|
public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException {
|
||||||
final Pattern pattern =
|
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 File[] cp = appProvider.mainClasspath();
|
||||||
final URL[] interfaceURLs = new URL[3];
|
final URL[] interfaceURLs = new URL[3];
|
||||||
final File[] extra =
|
final File[] extra =
|
||||||
|
|
|
||||||
|
|
@ -1504,13 +1504,13 @@ object Defaults extends BuildCommon {
|
||||||
def print(st: String) = { scala.Console.out.print(st); scala.Console.out.flush() }
|
def print(st: String) = { scala.Console.out.print(st); scala.Console.out.flush() }
|
||||||
print(s)
|
print(s)
|
||||||
Terminal.get.withRawSystemIn {
|
Terminal.get.withRawSystemIn {
|
||||||
Terminal.get.inputStream.read match {
|
try Terminal.get.inputStream.read match {
|
||||||
case -1 => None
|
case -1 | -2 => None
|
||||||
case b =>
|
case b =>
|
||||||
val res = b.toChar.toString
|
val res = b.toChar.toString
|
||||||
println(res)
|
println(res)
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
} catch { case e: InterruptedException => None }
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
classes
|
classes
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ private[sbt] object xMain {
|
||||||
BspClient.run(dealiasBaseDirectory(configuration))
|
BspClient.run(dealiasBaseDirectory(configuration))
|
||||||
} else {
|
} else {
|
||||||
bootServerSocket.foreach(l => Terminal.setBootStreams(l.inputStream, l.outputStream))
|
bootServerSocket.foreach(l => Terminal.setBootStreams(l.inputStream, l.outputStream))
|
||||||
Terminal.withStreams {
|
Terminal.withStreams(true) {
|
||||||
if (clientModByEnv || userCommands.exists(isClient)) {
|
if (clientModByEnv || userCommands.exists(isClient)) {
|
||||||
val args = userCommands.toList.filterNot(isClient)
|
val args = userCommands.toList.filterNot(isClient)
|
||||||
NetworkClient.run(dealiasBaseDirectory(configuration), args)
|
NetworkClient.run(dealiasBaseDirectory(configuration), args)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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 d: FiniteDuration => Some(d.fromNow)
|
||||||
case _ => None
|
case _ => None
|
||||||
}, idleDeadline)
|
}, idleDeadline)
|
||||||
|
exec.source.foreach { s =>
|
||||||
|
channelForName(s.channelName).foreach {
|
||||||
|
case c if c.terminal.prompt != Prompt.Batch => c.terminal.setPrompt(Prompt.Running)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exec
|
||||||
}
|
}
|
||||||
|
|
||||||
private def addConsoleChannel(): Unit =
|
private def addConsoleChannel(): Unit =
|
||||||
|
|
@ -412,6 +419,10 @@ private[sbt] final class CommandExchange {
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
|
channels.foreach {
|
||||||
|
case nc: NetworkChannel => nc.shutdown(true, Some(("", "")))
|
||||||
|
case c => c.shutdown(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private[sbt] def shutdown(name: String): Unit = {
|
private[sbt] def shutdown(name: String): Unit = {
|
||||||
|
|
@ -448,7 +459,9 @@ private[sbt] final class CommandExchange {
|
||||||
case mt: FastTrackTask =>
|
case mt: FastTrackTask =>
|
||||||
mt.task match {
|
mt.task match {
|
||||||
case `attach` => mt.channel.prompt(ConsolePromptEvent(lastState.get))
|
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) =>
|
case t if t.startsWith(ContinuousCommands.stopWatch) =>
|
||||||
ContinuousCommands.stopWatchImpl(mt.channel.name)
|
ContinuousCommands.stopWatchImpl(mt.channel.name)
|
||||||
mt.channel match {
|
mt.channel match {
|
||||||
|
|
@ -458,6 +471,10 @@ private[sbt] final class CommandExchange {
|
||||||
commandQueue.add(Exec(t, None, None))
|
commandQueue.add(Exec(t, None, None))
|
||||||
case `TerminateAction` => exit(mt)
|
case `TerminateAction` => exit(mt)
|
||||||
case `Shutdown` =>
|
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 {
|
channels.find(_.name == mt.channel.name) match {
|
||||||
case Some(c: NetworkChannel) => c.shutdown(false)
|
case Some(c: NetworkChannel) => c.shutdown(false)
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
|
||||||
|
|
@ -1217,7 +1217,6 @@ private[sbt] object ContinuousCommands {
|
||||||
) extends Thread(s"sbt-${channel.name}-watch-ui-thread")
|
) extends Thread(s"sbt-${channel.name}-watch-ui-thread")
|
||||||
with UITask {
|
with UITask {
|
||||||
override private[sbt] def reader: UITask.Reader = () => {
|
override private[sbt] def reader: UITask.Reader = () => {
|
||||||
channel.terminal.printStream.write(Int.MinValue)
|
|
||||||
def stop = Right(s"${ContinuousCommands.stopWatch} ${channel.name}")
|
def stop = Right(s"${ContinuousCommands.stopWatch} ${channel.name}")
|
||||||
val exitAction: Watch.Action = {
|
val exitAction: Watch.Action = {
|
||||||
Watch.apply(
|
Watch.apply(
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,12 @@ private[sbt] class TaskProgress private ()
|
||||||
if (firstTime.compareAndSet(true, activeExceedingThreshold.isEmpty)) threshold
|
if (firstTime.compareAndSet(true, activeExceedingThreshold.isEmpty)) threshold
|
||||||
else sleepDuration
|
else sleepDuration
|
||||||
val limit = duration.fromNow
|
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)
|
var task = tasks.poll((limit - Deadline.now).toMillis, TimeUnit.MILLISECONDS)
|
||||||
while (task != null) {
|
while (task != null) {
|
||||||
if (containsSkipTasks(Vector(task)) || lastTaskCount.get == 0) doReport()
|
if (containsSkipTasks(Vector(task)) || lastTaskCount.get == 0) doReport()
|
||||||
task = tasks.poll
|
task = tasks.poll
|
||||||
|
tasks.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ final class NetworkChannel(
|
||||||
addFastTrackTask(attach)
|
addFastTrackTask(attach)
|
||||||
}
|
}
|
||||||
private[sbt] def prompt(): Unit = {
|
private[sbt] def prompt(): Unit = {
|
||||||
terminal.setPrompt(Prompt.Running)
|
|
||||||
interactive.set(true)
|
interactive.set(true)
|
||||||
jsonRpcNotify(promptChannel, "")
|
jsonRpcNotify(promptChannel, "")
|
||||||
}
|
}
|
||||||
|
|
@ -641,7 +640,7 @@ final class NetworkChannel(
|
||||||
case -1 => throw new ClosedChannelException()
|
case -1 => throw new ClosedChannelException()
|
||||||
case b => b
|
case b => b
|
||||||
}
|
}
|
||||||
} catch { case _: IOException => -1 }
|
} catch { case e: IOException => -1 }
|
||||||
}
|
}
|
||||||
override def available(): Int = inputBuffer.size
|
override def available(): Int = inputBuffer.size
|
||||||
}
|
}
|
||||||
|
|
@ -774,25 +773,82 @@ final class NetworkChannel(
|
||||||
Some(result(queue.take))
|
Some(result(queue.take))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override def getBooleanCapability(capability: String): Boolean =
|
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
|
||||||
getCapability(
|
getCapability(
|
||||||
TerminalCapabilitiesQuery(boolean = Some(capability), numeric = None, string = None),
|
TerminalCapabilitiesQuery(
|
||||||
|
boolean = Some(capability),
|
||||||
|
numeric = None,
|
||||||
|
string = None,
|
||||||
|
jline3
|
||||||
|
),
|
||||||
_.boolean.getOrElse(false)
|
_.boolean.getOrElse(false)
|
||||||
).getOrElse(false)
|
).getOrElse(false)
|
||||||
override def getNumericCapability(capability: String): Int =
|
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
|
||||||
getCapability(
|
getCapability(
|
||||||
TerminalCapabilitiesQuery(boolean = None, numeric = Some(capability), string = None),
|
TerminalCapabilitiesQuery(
|
||||||
_.numeric.getOrElse(-1)
|
boolean = None,
|
||||||
).getOrElse(-1)
|
numeric = Some(capability),
|
||||||
override def getStringCapability(capability: String): String =
|
string = None,
|
||||||
|
jline3
|
||||||
|
),
|
||||||
|
(_: TerminalCapabilitiesResponse).numeric.map(Integer.valueOf).getOrElse(-1: Integer)
|
||||||
|
).getOrElse(-1: Integer)
|
||||||
|
override def getStringCapability(capability: String, jline3: Boolean): String =
|
||||||
getCapability(
|
getCapability(
|
||||||
TerminalCapabilitiesQuery(boolean = None, numeric = None, string = Some(capability)),
|
TerminalCapabilitiesQuery(
|
||||||
|
boolean = None,
|
||||||
|
numeric = None,
|
||||||
|
string = Some(capability),
|
||||||
|
jline3
|
||||||
|
),
|
||||||
_.string.flatMap {
|
_.string.flatMap {
|
||||||
case "null" => None
|
case "null" => None
|
||||||
case s => Some(s)
|
case s => Some(s)
|
||||||
}.orNull
|
}.orNull
|
||||||
).getOrElse("")
|
).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 toString: String = s"NetworkTerminal($name)"
|
||||||
override def close(): Unit = if (closed.compareAndSet(false, true)) {
|
override def close(): Unit = if (closed.compareAndSet(false, true)) {
|
||||||
val threads = blockedThreads.synchronized {
|
val threads = blockedThreads.synchronized {
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,27 @@ import sbt.protocol.Serialization.{
|
||||||
import sjsonnew.support.scalajson.unsafe.Converter
|
import sjsonnew.support.scalajson.unsafe.Converter
|
||||||
import sbt.protocol.{
|
import sbt.protocol.{
|
||||||
Attach,
|
Attach,
|
||||||
|
TerminalAttributesQuery,
|
||||||
|
TerminalAttributesResponse,
|
||||||
TerminalCapabilitiesQuery,
|
TerminalCapabilitiesQuery,
|
||||||
TerminalCapabilitiesResponse,
|
TerminalCapabilitiesResponse,
|
||||||
TerminalPropertiesResponse
|
TerminalPropertiesResponse,
|
||||||
|
TerminalSetAttributesCommand,
|
||||||
|
TerminalSetSizeCommand,
|
||||||
}
|
}
|
||||||
|
import sbt.protocol.codec.JsonProtocol._
|
||||||
|
|
||||||
object VirtualTerminal {
|
object VirtualTerminal {
|
||||||
private[this] val pendingTerminalProperties =
|
private[this] val pendingTerminalProperties =
|
||||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalPropertiesResponse]]()
|
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalPropertiesResponse]]()
|
||||||
private[this] val pendingTerminalCapabilities =
|
private[this] val pendingTerminalCapabilities =
|
||||||
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalCapabilitiesResponse]]
|
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(
|
private[sbt] def sendTerminalPropertiesQuery(
|
||||||
channelName: String,
|
channelName: String,
|
||||||
jsonRpcRequest: (String, String, String) => Unit
|
jsonRpcRequest: (String, String, String) => Unit
|
||||||
|
|
@ -70,6 +81,39 @@ object VirtualTerminal {
|
||||||
case _ =>
|
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 =>
|
val handler = ServerHandler { cb =>
|
||||||
ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb))
|
ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb))
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +121,6 @@ object VirtualTerminal {
|
||||||
private val requestHandler: Handler[JsonRpcRequestMessage] =
|
private val requestHandler: Handler[JsonRpcRequestMessage] =
|
||||||
callback => {
|
callback => {
|
||||||
case r if r.method == attach =>
|
case r if r.method == attach =>
|
||||||
import sbt.protocol.codec.JsonProtocol.AttachFormat
|
|
||||||
val isInteractive = r.params
|
val isInteractive = r.params
|
||||||
.flatMap(Converter.fromJson[Attach](_).toOption.map(_.interactive))
|
.flatMap(Converter.fromJson[Attach](_).toOption.map(_.interactive))
|
||||||
.exists(identity)
|
.exists(identity)
|
||||||
|
|
@ -89,7 +132,6 @@ object VirtualTerminal {
|
||||||
private val responseHandler: Handler[JsonRpcResponseMessage] =
|
private val responseHandler: Handler[JsonRpcResponseMessage] =
|
||||||
callback => {
|
callback => {
|
||||||
case r if pendingTerminalProperties.get((callback.name, r.id)) != null =>
|
case r if pendingTerminalProperties.get((callback.name, r.id)) != null =>
|
||||||
import sbt.protocol.codec.JsonProtocol._
|
|
||||||
val response =
|
val response =
|
||||||
r.result.flatMap(Converter.fromJson[TerminalPropertiesResponse](_).toOption)
|
r.result.flatMap(Converter.fromJson[TerminalPropertiesResponse](_).toOption)
|
||||||
pendingTerminalProperties.remove((callback.name, r.id)) match {
|
pendingTerminalProperties.remove((callback.name, r.id)) match {
|
||||||
|
|
@ -97,7 +139,6 @@ object VirtualTerminal {
|
||||||
case buffer => response.foreach(buffer.put)
|
case buffer => response.foreach(buffer.put)
|
||||||
}
|
}
|
||||||
case r if pendingTerminalCapabilities.get((callback.name, r.id)) != null =>
|
case r if pendingTerminalCapabilities.get((callback.name, r.id)) != null =>
|
||||||
import sbt.protocol.codec.JsonProtocol._
|
|
||||||
val response =
|
val response =
|
||||||
r.result.flatMap(
|
r.result.flatMap(
|
||||||
Converter.fromJson[TerminalCapabilitiesResponse](_).toOption
|
Converter.fromJson[TerminalCapabilitiesResponse](_).toOption
|
||||||
|
|
@ -107,6 +148,24 @@ object VirtualTerminal {
|
||||||
case buffer =>
|
case buffer =>
|
||||||
buffer.put(response.getOrElse(TerminalCapabilitiesResponse(None, None, None)))
|
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] =
|
private val notificationHandler: Handler[JsonRpcNotificationMessage] =
|
||||||
callback => {
|
callback => {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ object Dependencies {
|
||||||
val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash")
|
val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash")
|
||||||
|
|
||||||
val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-5e51b9d4f9631ebfa29753ce4accc57808e7fd6b"
|
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 scalatest = "org.scalatest" %% "scalatest" % "3.0.8"
|
||||||
val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0"
|
val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0"
|
||||||
val specs2 = "org.specs2" %% "specs2-junit" % "4.0.1"
|
val specs2 = "org.specs2" %% "specs2-junit" % "4.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 (
|
final class TerminalCapabilitiesQuery private (
|
||||||
val boolean: Option[String],
|
val boolean: Option[String],
|
||||||
val numeric: 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 {
|
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
|
case _ => false
|
||||||
}
|
}
|
||||||
override def hashCode: Int = {
|
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 = {
|
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 = {
|
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)
|
new TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
|
||||||
}
|
}
|
||||||
def withBoolean(boolean: Option[String]): TerminalCapabilitiesQuery = {
|
def withBoolean(boolean: Option[String]): TerminalCapabilitiesQuery = {
|
||||||
copy(boolean = boolean)
|
copy(boolean = boolean)
|
||||||
|
|
@ -42,9 +43,12 @@ final class TerminalCapabilitiesQuery private (
|
||||||
def withString(string: String): TerminalCapabilitiesQuery = {
|
def withString(string: String): TerminalCapabilitiesQuery = {
|
||||||
copy(string = Option(string))
|
copy(string = Option(string))
|
||||||
}
|
}
|
||||||
|
def withJline3(jline3: Boolean): TerminalCapabilitiesQuery = {
|
||||||
|
copy(jline3 = jline3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
object TerminalCapabilitiesQuery {
|
object TerminalCapabilitiesQuery {
|
||||||
|
|
||||||
def apply(boolean: Option[String], numeric: Option[String], string: Option[String]): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(boolean, numeric, 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): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(Option(boolean), Option(numeric), Option(string))
|
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
|
package sbt.protocol.codec
|
||||||
|
|
||||||
import _root_.sjsonnew.JsonFormat
|
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 =>
|
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] = flatUnionFormat5[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery]("type")
|
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
|
package sbt.protocol.codec
|
||||||
|
|
||||||
import _root_.sjsonnew.JsonFormat
|
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 =>
|
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] = 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")
|
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.SettingQueryFormats
|
||||||
with sbt.protocol.codec.AttachFormats
|
with sbt.protocol.codec.AttachFormats
|
||||||
with sbt.protocol.codec.TerminalCapabilitiesQueryFormats
|
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.CommandMessageFormats
|
||||||
with sbt.protocol.codec.CompletionParamsFormats
|
with sbt.protocol.codec.CompletionParamsFormats
|
||||||
with sbt.protocol.codec.ChannelAcceptedEventFormats
|
with sbt.protocol.codec.ChannelAcceptedEventFormats
|
||||||
|
|
@ -20,6 +23,9 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
||||||
with sbt.protocol.codec.SettingQueryFailureFormats
|
with sbt.protocol.codec.SettingQueryFailureFormats
|
||||||
with sbt.protocol.codec.TerminalPropertiesResponseFormats
|
with sbt.protocol.codec.TerminalPropertiesResponseFormats
|
||||||
with sbt.protocol.codec.TerminalCapabilitiesResponseFormats
|
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.EventMessageFormats
|
||||||
with sbt.protocol.codec.SettingQueryResponseFormats
|
with sbt.protocol.codec.SettingQueryResponseFormats
|
||||||
with sbt.protocol.codec.CompletionResponseFormats
|
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 boolean = unbuilder.readField[Option[String]]("boolean")
|
||||||
val numeric = unbuilder.readField[Option[String]]("numeric")
|
val numeric = unbuilder.readField[Option[String]]("numeric")
|
||||||
val string = unbuilder.readField[Option[String]]("string")
|
val string = unbuilder.readField[Option[String]]("string")
|
||||||
|
val jline3 = unbuilder.readField[Boolean]("jline3")
|
||||||
unbuilder.endObject()
|
unbuilder.endObject()
|
||||||
sbt.protocol.TerminalCapabilitiesQuery(boolean, numeric, string)
|
sbt.protocol.TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
|
||||||
case None =>
|
case None =>
|
||||||
deserializationError("Expected JsObject but found 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("boolean", obj.boolean)
|
||||||
builder.addField("numeric", obj.numeric)
|
builder.addField("numeric", obj.numeric)
|
||||||
builder.addField("string", obj.string)
|
builder.addField("string", obj.string)
|
||||||
|
builder.addField("jline3", obj.jline3)
|
||||||
builder.endObject()
|
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 {
|
type TerminalPropertiesResponse implements EventMessage {
|
||||||
width: Int!
|
width: Int!
|
||||||
height: Int!
|
height: Int!
|
||||||
isAnsiSupported: Boolean!
|
isAnsiSupported: Boolean!
|
||||||
isColorEnabled: Boolean!
|
isColorEnabled: Boolean!
|
||||||
isSupershellEnabled: Boolean!
|
isSupershellEnabled: Boolean!
|
||||||
isEchoEnabled: Boolean!
|
isEchoEnabled: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type TerminalCapabilitiesQuery implements CommandMessage {
|
type TerminalCapabilitiesQuery implements CommandMessage {
|
||||||
boolean: String
|
boolean: String
|
||||||
numeric: String
|
numeric: String
|
||||||
string: String
|
string: String
|
||||||
|
jline3: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type TerminalCapabilitiesResponse implements EventMessage {
|
type TerminalCapabilitiesResponse implements EventMessage {
|
||||||
boolean: Boolean
|
boolean: Boolean
|
||||||
numeric: Int
|
numeric: Int
|
||||||
string: String
|
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"
|
private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8"
|
||||||
val systemIn = "sbt/systemIn"
|
val systemIn = "sbt/systemIn"
|
||||||
val systemOut = "sbt/systemOut"
|
val systemOut = "sbt/systemOut"
|
||||||
|
val systemOutFlush = "sbt/systemOutFlush"
|
||||||
val terminalPropertiesQuery = "sbt/terminalPropertiesQuery"
|
val terminalPropertiesQuery = "sbt/terminalPropertiesQuery"
|
||||||
val terminalPropertiesResponse = "sbt/terminalPropertiesResponse"
|
val terminalPropertiesResponse = "sbt/terminalPropertiesResponse"
|
||||||
val terminalCapabilities = "sbt/terminalCapabilities"
|
val terminalCapabilities = "sbt/terminalCapabilities"
|
||||||
|
|
@ -34,6 +35,9 @@ object Serialization {
|
||||||
val attachResponse = "sbt/attachResponse"
|
val attachResponse = "sbt/attachResponse"
|
||||||
val cancelRequest = "sbt/cancelRequest"
|
val cancelRequest = "sbt/cancelRequest"
|
||||||
val promptChannel = "sbt/promptChannel"
|
val promptChannel = "sbt/promptChannel"
|
||||||
|
val setTerminalAttributes = "sbt/setTerminalAttributes"
|
||||||
|
val getTerminalAttributes = "sbt/getTerminalAttributes"
|
||||||
|
val setTerminalSize = "sbt/setTerminalSize"
|
||||||
val CancelAll = "__CancelAll"
|
val CancelAll = "__CancelAll"
|
||||||
|
|
||||||
@deprecated("unused", since = "1.4.0")
|
@deprecated("unused", since = "1.4.0")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue