Merge pull request #5874 from eatkins/terminal-fixes

Fix a number of terminal io bugs
This commit is contained in:
eugene yokota 2020-09-21 22:19:39 -04:00 committed by GitHub
commit 23fd24c838
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 844 additions and 309 deletions

View File

@ -368,6 +368,7 @@ lazy val utilLogging = (project in file("internal") / "util-logging")
Seq(
jline,
jline3Terminal,
jline3JNA,
jline3Jansi,
log4jApi,
log4jCore,

View File

@ -21,6 +21,7 @@ import org.jline.reader.{
ParsedLine,
UserInterruptException,
}
import org.jline.utils.ClosedException
import sbt.internal.util.complete.Parser
import scala.annotation.tailrec
@ -87,7 +88,7 @@ object LineReader {
case e: EndOfFileException =>
if (terminal == Terminal.console && System.console == null) None
else Some("exit")
case _: IOError => Some("exit")
case _: IOError | _: ClosedException => Some("exit")
case _: UserInterruptException | _: ClosedByInterruptException |
_: UncheckedIOException =>
throw new InterruptedException
@ -221,8 +222,7 @@ private[sbt] object JLine {
"Don't use jline.Terminal directly. Use Terminal.get.withCanonicalIn instead.",
"1.4.0"
)
def usingTerminal[T](f: jline.Terminal => T): T =
Terminal.get.withCanonicalIn(f(Terminal.get.toJLine))
def usingTerminal[T](f: jline.Terminal => T): T = f(Terminal.get.toJLine)
@deprecated("unused", "1.4.0")
def createReader(): ConsoleReader = createReader(None, Terminal.wrappedSystemIn)

View File

@ -12,67 +12,64 @@ import java.nio.charset.Charset
import java.util.{ Arrays, EnumSet }
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import org.jline.utils.InfoCmp.Capability
import org.jline.utils.{ ClosedException, NonBlockingReader, OSUtils }
import org.jline.utils.{ ClosedException, NonBlockingReader }
import org.jline.terminal.{ Attributes, Size, Terminal => JTerminal }
import org.jline.terminal.Attributes.{ InputFlag, LocalFlag }
import org.jline.terminal.Terminal.SignalHandler
import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal }
import org.jline.terminal.impl.jansi.JansiSupportImpl
import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal
import org.jline.utils.OSUtils
import scala.collection.JavaConverters._
import scala.util.Try
import java.util.concurrent.LinkedBlockingQueue
private[sbt] object JLine3 {
private val capabilityMap = Capability
.values()
.map { c =>
c.toString -> c
}
.toMap
private[util] val initialAttributes = new AtomicReference[Attributes]
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()
private[this] val forceWindowsJansiHolder = new AtomicBoolean(false)
private[sbt] def forceWindowsJansi(): Unit = forceWindowsJansiHolder.set(true)
private[this] def windowsJansi(): org.jline.terminal.Terminal = {
val support = new JansiSupportImpl
val winConsole = support.isWindowsConsole();
val termType = sys.props.get("org.jline.terminal.type").orElse(sys.env.get("TERM")).orNull
val term = JansiWinSysTerminal.createTerminal(
"console",
termType,
OSUtils.IS_CONEMU,
Charset.forName("UTF-8"),
-1,
false,
SignalHandler.SIG_DFL,
true
)
term.disableScrolling()
term
}
private val jansi = {
val (major, minor) =
(JansiSupportImpl.getJansiMajorVersion, JansiSupportImpl.getJansiMinorVersion)
(major > 1 || minor >= 18) && Util.isWindows
}
private[util] def system: org.jline.terminal.Terminal = {
val term =
if (forceWindowsJansiHolder.get) windowsJansi()
else {
// Only use jna on windows. Both jna and jansi use illegal reflective
// accesses on posix system.
org.jline.terminal.TerminalBuilder
.builder()
.system(System.console != null)
.jna(Util.isWindows && !jansi)
.jansi(jansi)
.paused(true)
.build()
}
} else {
org.jline.terminal.TerminalBuilder
.builder()
.system(System.console != null)
.paused(true)
.jna(false)
.jansi(true)
.build()
initialAttributes.get match {
case null => initialAttributes.set(term.getAttributes)
case _ =>
}
term
}
private[sbt] def apply(term: Terminal): JTerminal = {
if (System.getProperty("jline.terminal", "") == "none" || !Terminal.formatEnabledInEnv)
@ -80,7 +77,12 @@ private[sbt] object JLine3 {
else wrapTerminal(term)
}
private[this] def wrapTerminal(term: Terminal): JTerminal = {
new AbstractTerminal(term.name, "ansi", Charset.forName("UTF-8"), SignalHandler.SIG_DFL) {
new AbstractTerminal(
term.name,
"nocapabilities",
Charset.forName("UTF-8"),
SignalHandler.SIG_DFL
) {
val closed = new AtomicBoolean(false)
setOnClose { () =>
doClose()
@ -94,7 +96,6 @@ private[sbt] object JLine3 {
}
}
}
parseInfoCmp()
override val input: InputStream = new InputStream {
override def read: Int = {
val res = term.inputStream match {
@ -176,45 +177,52 @@ private[sbt] object JLine3 {
* 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)
term.getStringCapability(cap.toString) match {
case null if cap == Capability.key_dc && Util.isWindows => "\\E[3~"
case null if cap == Capability.key_end && Util.isWindows => "\\E[4~"
case null if cap == Capability.key_ic && Util.isWindows => "\\E[2~"
case c => c
}
}
override def getNumericCapability(cap: Capability): Integer =
term.getNumericCapability(cap.toString)
override def getBooleanCapability(cap: Capability): Boolean =
term.getBooleanCapability(cap.toString)
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 setAttributes(a: Attributes): Unit = {} // don't allow the jline line reader to change attributes
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)
override def enterRawMode(): Attributes = {
// don't actually modify the term, that is handled by LineReader
attributesFromMap(term.getAttributes)
}
}
}
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
)
newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO, LocalFlag.IEXTEN), false)
newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false)
term.setAttributes(newAttr)
prvAttr
}
private[util] def enterRawMode(term: JTerminal): Map[String, String] =
toMap(enterRawModeImpl(term))
private[util] def enterRawMode(term: JTerminal): Unit = {
val prevAttr = initialAttributes.get
val newAttr = new Attributes(prevAttr)
// These flags are copied from the jline3 enterRawMode but the jline implementation
// also puts the input stream in non blocking mode, which we do not want.
newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.IEXTEN, LocalFlag.ECHO), false)
newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false)
term.setAttributes(newAttr)
()
}
private[util] def exitRawMode(term: JTerminal): Unit = {
val initAttr = initialAttributes.get
val newAttr = new Attributes(initAttr)
newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO), true)
term.setAttributes(newAttr)
}
private[util] def toMap(jattributes: Attributes): Map[String, String] = {
val result = new java.util.LinkedHashMap[String, String]
result.put(
@ -243,14 +251,14 @@ private[sbt] object JLine3 {
)
result.asScala.toMap
}
private[this] val iflagMap: Map[String, Attributes.InputFlag] =
Attributes.InputFlag.values.map(f => f.name.toLowerCase -> f).toMap
private[this] val iflagMap: Map[String, InputFlag] =
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 lflagMap: Map[String, LocalFlag] =
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 = {

View File

@ -13,7 +13,6 @@ import java.util.{ Arrays, Locale }
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import java.util.concurrent.{ Executors, LinkedBlockingQueue, TimeUnit }
import jline.DefaultTerminal2
import jline.console.ConsoleReader
import scala.annotation.tailrec
import scala.concurrent.duration._
@ -104,6 +103,13 @@ trait Terminal extends AutoCloseable {
*/
def isSupershellEnabled: Boolean
/**
* Toggles whether or not the terminal should echo characters back to stdout
*
* @return the previous value of the toggle
*/
def setEchoEnabled(toggle: Boolean): Unit
/*
* The methods below this comment are implementation details that are in
* some cases specific to jline2. These methods may need to change or be
@ -126,16 +132,21 @@ trait Terminal extends AutoCloseable {
*/
private[sbt] def getLines: Seq[String]
private[sbt] def getBooleanCapability(capability: String, jline3: Boolean): Boolean
private[sbt] def getNumericCapability(capability: String, jline3: Boolean): Integer
private[sbt] def getStringCapability(capability: String, jline3: Boolean): String
private[sbt] def getBooleanCapability(capability: String): Boolean
private[sbt] def getNumericCapability(capability: String): Integer
private[sbt] def getStringCapability(capability: String): 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 withRawInput[T](f: => T): T = f
private[sbt] def withCanonicalIn[T](f: => T): T = f
private[sbt] final def withRawInput[T](f: => T): T = {
enterRawMode()
try f
catch { case e: InterruptedIOException => throw new InterruptedException } finally exitRawMode()
}
private[sbt] def enterRawMode(): Unit
private[sbt] def exitRawMode(): Unit
private[sbt] def write(bytes: Int*): Unit
private[sbt] def printStream: PrintStream
private[sbt] def withPrintStream[T](f: PrintStream => T): T
@ -189,7 +200,6 @@ object Terminal {
* terminal.
*/
private[sbt] def toJLine: jline.Terminal with jline.Terminal2 = term match {
case t: ConsoleTerminal => t.term
case _ =>
new jline.Terminal with jline.Terminal2 {
override def init(): Unit = {}
@ -207,15 +217,12 @@ object Terminal {
override def disableInterruptCharacter(): Unit = {}
override def enableInterruptCharacter(): Unit = {}
override def getOutputEncoding: String = null
override def getBooleanCapability(capability: String): Boolean = {
term.getBooleanCapability(capability, jline3 = false)
}
override def getNumericCapability(capability: String): Integer = {
term.getNumericCapability(capability, jline3 = false)
}
override def getStringCapability(capability: String): String = {
term.getStringCapability(capability, jline3 = false)
}
override def getBooleanCapability(capability: String): Boolean =
term.getBooleanCapability(capability)
override def getNumericCapability(capability: String): Integer =
term.getNumericCapability(capability)
override def getStringCapability(capability: String): String =
term.getStringCapability(capability)
}
}
}
@ -319,7 +326,7 @@ object Terminal {
// In ci environments, don't touch the io streams unless run with -Dsbt.io.virtual=true
if (System.getProperty("sbt.io.virtual", "") == "true" || (logFormatEnabled.getOrElse(true) && !isCI)) {
hasProgress.set(isServer)
consoleTerminalHolder.set(wrap(jline.TerminalFactory.get))
consoleTerminalHolder.set(newConsoleTerminal())
activeTerminal.set(consoleTerminalHolder.get)
try withOut(withIn(f))
finally {
@ -334,7 +341,7 @@ object Terminal {
* back to blocking mode. We can then close the console. We do
* this on a background thread in case the read blocks indefinitely.
*/
val prev = c.system.enterRawMode()
c.system.enterRawMode()
val runnable: Runnable = () => {
try Util.ignoreResult(c.inputStream.read)
catch { case _: InterruptedException => }
@ -345,7 +352,6 @@ object Terminal {
// The thread should exit almost instantly but give it 200ms to spin up
thread.join(200)
if (thread.isAlive) thread.interrupt()
c.system.setAttributes(prev)
c.close()
case c => c.close()
}
@ -358,6 +364,8 @@ object Terminal {
private[this] object ProxyTerminal extends Terminal {
private def t: Terminal = activeTerminal.get
override private[sbt] def progressState: ProgressState = t.progressState
override private[sbt] def enterRawMode(): Unit = t.enterRawMode()
override private[sbt] def exitRawMode(): Unit = t.exitRawMode()
override def getWidth: Int = t.getWidth
override def getHeight: Int = t.getHeight
override def getLineHeightAndWidth(line: String): (Int, Int) = t.getLineHeightAndWidth(line)
@ -370,18 +378,17 @@ object Terminal {
override def isEchoEnabled: Boolean = t.isEchoEnabled
override def isSuccessEnabled: Boolean = t.isSuccessEnabled
override def isSupershellEnabled: Boolean = t.isSupershellEnabled
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
t.getBooleanCapability(capability, jline3)
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
t.getNumericCapability(capability, jline3)
override def getStringCapability(capability: String, jline3: Boolean): String =
t.getStringCapability(capability, jline3)
override def setEchoEnabled(toggle: Boolean): Unit = t.setEchoEnabled(toggle)
override def getBooleanCapability(capability: String): Boolean =
t.getBooleanCapability(capability)
override def getNumericCapability(capability: String): Integer =
t.getNumericCapability(capability)
override def getStringCapability(capability: String): String =
t.getStringCapability(capability)
override private[sbt] def getAttributes: Map[String, String] = t.getAttributes
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
t.setAttributes(attributes)
override private[sbt] def setSize(width: Int, height: Int): Unit = t.setSize(width, height)
override def withRawInput[T](f: => T): T = t.withRawInput(f)
override def withCanonicalIn[T](f: => T): T = t.withCanonicalIn(f)
override def printStream: PrintStream = t.printStream
override def withPrintStream[T](f: PrintStream => T): T = t.withPrintStream(f)
override private[sbt] def withRawOutput[R](f: => R): R = t.withRawOutput(f)
@ -435,11 +442,15 @@ object Terminal {
private[this] val originalErr = System.err
private[this] val originalIn = System.in
private[sbt] class WriteableInputStream(in: InputStream, name: String)
extends InputStream
extends SimpleInputStream
with AutoCloseable {
final def write(bytes: Int*): Unit = readThread.synchronized {
bytes.foreach(b => buffer.put(b))
}
def setRawMode(toggle: Boolean): Unit = in match {
case win: WindowsInputStream => win.setRawMode(toggle)
case _ =>
}
private[this] val executor =
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader"))
private[this] val buffer = new LinkedBlockingQueue[Integer]
@ -502,8 +513,10 @@ object Terminal {
()
}
}
private[this] lazy val nonBlockingIn: WriteableInputStream =
new WriteableInputStream(jline.TerminalFactory.get.wrapInIfNeeded(originalIn), "console")
private[this] def nonBlockingIn(term: org.jline.terminal.Terminal): WriteableInputStream = {
val in = if (Util.isNonCygwinWindows) new WindowsInputStream(term, originalIn) else originalIn
new WriteableInputStream(in, "console")
}
private[this] val inputStream = new AtomicReference[InputStream](System.in)
private[this] def withOut[T](f: => T): T = {
@ -518,8 +531,8 @@ object Terminal {
}
private[this] def withIn[T](f: => T): T =
try {
inputStream.set(Terminal.wrappedSystemIn)
System.setIn(wrappedSystemIn)
inputStream.set(proxyInputStream)
System.setIn(proxyInputStream)
scala.Console.withIn(proxyInputStream)(f)
} finally System.setIn(originalIn)
@ -567,7 +580,15 @@ object Terminal {
bootOutputStreamHolder.set(bootOutputStream)
}
private[this] object proxyInputStream extends InputStream {
private[sbt] trait SimpleInputStream extends InputStream {
override def read(b: Array[Byte]): Int = read(b, 0, b.length)
override def read(b: Array[Byte], off: Int, len: Int): Int = {
val byte = read()
b(off) = byte.toByte
1
}
}
private[this] object proxyInputStream extends SimpleInputStream {
private[this] val isScripted = System.getProperty("sbt.scripted", "false") == "true"
/*
* This is to handle the case when a remote client starts sbt and the build fails.
@ -645,10 +666,10 @@ object Terminal {
os.write(bytes, offset, len)
override def flush(): Unit = os.flush()
}
private[this] val proxyErrorStream = new PrintStream(proxyErrorOutputStream, true)
private[this] object proxyErrorStream extends PrintStream(proxyErrorOutputStream, true)
private[this] lazy val isWindows =
System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
private[this] object WrappedSystemIn extends InputStream {
private[this] object WrappedSystemIn extends SimpleInputStream {
private[this] val in = proxyInputStream
override def available(): Int = if (attached.get) in.available() else 0
override def read(): Int = synchronized {
@ -693,75 +714,16 @@ object Terminal {
}
private[sbt] def startedByRemoteClient = props.isDefined
/**
* Creates an instance of [[Terminal]] that delegates most of its methods to an underlying
* jline.Terminal2 instance. In the long run, sbt should upgrade to jline3, which has a
* completely different terminal interface so whereever possible, we should avoid
* directly referencing jline.Terminal. Wrapping jline Terminal in sbt terminal helps
* with that goal.
*
* @param terminal the jline terminal to wrap
* @return an sbt Terminal
*/
private[this] def wrap(terminal: jline.Terminal): Terminal = {
val term: jline.Terminal with jline.Terminal2 = new jline.Terminal with jline.Terminal2 {
private[this] val hasConsole = System.console != null
private[this] def alive = hasConsole && attached.get
private[this] val term2: jline.Terminal2 = terminal match {
case t: jline.Terminal2 => t
case _ => new DefaultTerminal2(terminal)
}
override def init(): Unit =
if (alive)
try terminal.init()
catch {
case _: InterruptedException | _: java.io.IOError =>
}
override def restore(): Unit =
try terminal.restore()
catch {
case _: InterruptedException | _: java.io.IOError =>
}
override def reset(): Unit =
try terminal.reset()
catch { case _: InterruptedException => }
override def isSupported: Boolean = terminal.isSupported
override def getWidth: Int = props.map(_.width).getOrElse(terminal.getWidth)
override def getHeight: Int = props.map(_.height).getOrElse(terminal.getHeight)
override val isAnsiSupported: Boolean = terminal.isAnsiSupported && formatEnabledInEnv
override def wrapOutIfNeeded(out: OutputStream): OutputStream = terminal.wrapOutIfNeeded(out)
override def wrapInIfNeeded(in: InputStream): InputStream = terminal.wrapInIfNeeded(in)
override def hasWeirdWrap: Boolean = terminal.hasWeirdWrap
override def isEchoEnabled: Boolean = terminal.isEchoEnabled
override def setEchoEnabled(enabled: Boolean): Unit =
if (alive) terminal.setEchoEnabled(enabled)
override def disableInterruptCharacter(): Unit =
if (alive) terminal.disableInterruptCharacter()
override def enableInterruptCharacter(): Unit =
if (alive) terminal.enableInterruptCharacter()
override def getOutputEncoding: String = terminal.getOutputEncoding
override def getBooleanCapability(capability: String): Boolean =
term2.getBooleanCapability(capability)
override def getNumericCapability(capability: String): Integer =
term2.getNumericCapability(capability)
override def getStringCapability(capability: String): String = {
term2.getStringCapability(capability)
}
}
term.restore()
term.setEchoEnabled(true)
new ConsoleTerminal(
term,
if (System.console == null) nullWriteableInputStream else nonBlockingIn,
originalOut
)
private[this] def newConsoleTerminal(): Terminal = {
val system = JLine3.system
val in = if (System.console == null) nullWriteableInputStream else nonBlockingIn(system)
new ConsoleTerminal(in, originalOut, system)
}
private[sbt] def reset(): Unit = {
jline.TerminalFactory.reset()
console.close()
consoleTerminalHolder.set(wrap(jline.TerminalFactory.get))
consoleTerminalHolder.set(newConsoleTerminal())
}
// translate explicit class names to type in order to support
@ -807,52 +769,58 @@ object Terminal {
@deprecated("For compatibility only", "1.4.0")
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
private class ConsoleTerminal(
val term: jline.Terminal with jline.Terminal2,
private[util] class ConsoleTerminal(
in: WriteableInputStream,
out: OutputStream
out: OutputStream,
private[util] val system: org.jline.terminal.Terminal,
) extends TerminalImpl(in, out, originalErr, "console0") {
private[util] lazy val system = JLine3.system
private[this] val rawMode = new AtomicBoolean(false)
enterRawMode()
override private[sbt] def getSizeImpl: (Int, Int) = {
val size = system.getSize
(size.getColumns, size.getRows)
}
override lazy val isAnsiSupported: Boolean = term.isAnsiSupported && !isCI
override lazy val isAnsiSupported: Boolean = formatEnabledInEnv && !isCI
override private[sbt] def progressState: ProgressState = consoleProgressState.get
override def isEchoEnabled: Boolean = system.echo()
override def isEchoEnabled: Boolean =
try system.echo()
catch { case _: InterruptedIOException => false }
override def isSuccessEnabled: Boolean = true
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
if (jline3) capabilityMap.get(capability).fold(false)(system.getBooleanCapability)
else term.getBooleanCapability(capability)
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
if (jline3) capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability)
else term.getNumericCapability(capability)
override def getStringCapability(capability: String, jline3: Boolean): String =
if (jline3) capabilityMap.get(capability).fold(null: String)(system.getStringCapability)
else term.getStringCapability(capability)
override private[sbt] def restore(): Unit = term.restore()
override def setEchoEnabled(toggle: Boolean): Unit =
try Util.ignoreResult(system.echo(toggle))
catch { case _: InterruptedIOException => }
override def getBooleanCapability(capability: String): Boolean =
capabilityMap.get(capability).fold(false)(system.getBooleanCapability)
override def getNumericCapability(capability: String): Integer =
capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability)
override def getStringCapability(capability: String): String = {
val res = capabilityMap.get(capability).fold(null: String)(system.getStringCapability)
res
}
override private[sbt] def restore(): Unit = exitRawMode()
override private[sbt] def getAttributes: Map[String, String] =
Try(JLine3.toMap(system.getAttributes)).getOrElse(Map.empty)
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit = {
system.setAttributes(JLine3.attributesFromMap(attributes))
}
override private[sbt] def setSize(width: Int, height: Int): Unit =
system.setSize(new org.jline.terminal.Size(width, height))
override def withRawInput[T](f: => T): T = term.synchronized {
try {
term.init()
term.setEchoEnabled(false)
f
} catch { case _: InterruptedIOException => throw new InterruptedException } finally {
term.restore()
term.setEchoEnabled(true)
}
override private[sbt] def enterRawMode(): Unit = if (rawMode.compareAndSet(false, true)) {
in.setRawMode(true)
try JLine3.enterRawMode(system)
catch { case _: java.io.IOError => }
}
override private[sbt] def exitRawMode(): Unit = if (rawMode.compareAndSet(true, false)) {
in.setRawMode(false)
try JLine3.exitRawMode(system)
catch { case _: java.io.IOError => }
}
override def isColorEnabled: Boolean =
props
.map(_.color)
.getOrElse(isColorEnabledProp.getOrElse(term.isAnsiSupported && formatEnabledInEnv))
.getOrElse(isColorEnabledProp.getOrElse(formatEnabledInEnv))
override def isSupershellEnabled: Boolean =
props
@ -865,8 +833,9 @@ object Terminal {
})
override def close(): Unit = {
try {
system.setAttributes(JLine3.initialAttributes.get)
system.close()
term.restore()
in.close()
} catch { case NonFatal(_) => }
super.close()
}
@ -966,13 +935,15 @@ object Terminal {
private[sbt] class DefaultTerminal extends Terminal {
override def close(): Unit = {}
override private[sbt] def progressState: ProgressState = new ProgressState(1)
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = false
override private[sbt] def enterRawMode(): Unit = {}
override private[sbt] def exitRawMode(): Unit = {}
override def getBooleanCapability(capability: String): Boolean = false
override def getHeight: Int = 0
override def getLastLine: Option[String] = None
override def getLines: Seq[String] = Nil
override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0)
override def getNumericCapability(capability: String, jline3: Boolean): Integer = null
override def getStringCapability(capability: String, jline3: Boolean): String = null
override def getNumericCapability(capability: String): Integer = null
override def getStringCapability(capability: String): String = null
override def getWidth: Int = 0
override def inputStream: InputStream = nullInputStream
override def isAnsiSupported: Boolean = false
@ -980,6 +951,7 @@ object Terminal {
override def isEchoEnabled: Boolean = false
override def isSuccessEnabled: Boolean = true
override def isSupershellEnabled: Boolean = false
override def setEchoEnabled(toggle: Boolean): Unit = {}
override def outputStream: OutputStream = _ => {}
override def errorStream: OutputStream = _ => {}
override private[sbt] def getAttributes: Map[String, String] = Map.empty
@ -994,7 +966,7 @@ object Terminal {
}
private[sbt] object NullTerminal extends DefaultTerminal
private[sbt] object SimpleTerminal extends DefaultTerminal {
override lazy val inputStream: InputStream = nonBlockingIn
override lazy val inputStream: InputStream = originalIn
override lazy val outputStream: OutputStream = originalOut
override lazy val errorStream: OutputStream = originalErr
}

View File

@ -0,0 +1,138 @@
/*
* 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.InputStream
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean
import org.fusesource.jansi.internal.WindowsSupport
import org.jline.utils.InfoCmp.Capability
import scala.annotation.tailrec
import Terminal.SimpleInputStream
/*
* We need a special input stream for windows because special key events
* like arrow keys are not reported by System.in. What makes this extra
* tricky is that in canonical mode, ReadConsoleInput does not echo
* characters, even when echo is set. The ReadConsole api does, but that
* api isn't exposed by jansi so we can't use it without rolling our own
* jni library or using the jna. That is more trouble than it's worth. To
* work around the canonical mode issue, we can switch between reading using
* ReadConsoleInput and just reading directly from the original System.in,
* which does echo characters in canonical mode, when we enter and exit
* raw mode.
*/
private[util] class WindowsInputStream(term: org.jline.terminal.Terminal, in: InputStream)
extends SimpleInputStream {
private val SHIFT_FLAG = 0x01;
private val ALT_FLAG = 0x02;
private val CTRL_FLAG = 0x04;
private val RIGHT_ALT_PRESSED = 0x0001;
private val LEFT_ALT_PRESSED = 0x0002;
private val RIGHT_CTRL_PRESSED = 0x0004;
private val LEFT_CTRL_PRESSED = 0x0008;
private val SHIFT_PRESSED = 0x0010;
private def getCapability(cap: Capability): String = term.getStringCapability(cap) match {
case null => null
case c => c.replaceAllLiterally("\\E", "\u001B")
}
/*
* This function is a hybrid of jline 2 WindowsTerminal.readConsoleInput
* and jline3 AbstractTerminal.getEscapeSequence.
*/
private def readConsoleInput(): Array[Byte] = {
WindowsSupport.readConsoleInput(1) match {
case null => Array.empty
case events =>
val sb = new StringBuilder();
events.foreach { event =>
val keyEvent = event.keyEvent
val controlKeyState = keyEvent.controlKeyState
val isCtrl = (controlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) > 0;
val isAlt = (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) > 0;
val isShift = (controlKeyState & SHIFT_PRESSED) > 0;
if (keyEvent.keyDown) {
if (keyEvent.uchar > 0) {
if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
&& isAlt && !isCtrl) {
sb.append('\u001B') // ESC
}
if (isShift && keyEvent.keyCode == 9) {
getCapability(Capability.key_btab) match {
case null => sb.append(keyEvent.uchar)
case cap => sb.append(cap)
}
} else {
sb.append(keyEvent.uchar)
}
} else {
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// just add support for basic editing keys (no control state, no numpad keys)
val escapeSequence = keyEvent.keyCode match {
case 0x21 /* VK_PRIOR PageUp*/ => getCapability(Capability.key_ppage);
case 0x22 /* VK_NEXT PageDown*/ => getCapability(Capability.key_npage);
case 0x24 /* VK_HOME */ => getCapability(Capability.key_home)
case 0x25 /* VK_LEFT */ => getCapability(Capability.key_left)
case 0x26 /* VK_UP */ => getCapability(Capability.key_up)
case 0x27 /* VK_RIGHT */ => getCapability(Capability.key_right)
case 0x28 /* VK_DOWN */ => getCapability(Capability.key_down)
case 0x70 /* VK_F1 */ => getCapability(Capability.key_f1)
case 0x71 /* VK_F2 */ => getCapability(Capability.key_f2)
case 0x72 /* VK_F3 */ => getCapability(Capability.key_f3)
case 0x73 /* VK_F4 */ => getCapability(Capability.key_f4)
case 0x74 /* VK_F5 */ => getCapability(Capability.key_f5)
case 0x75 /* VK_F6 */ => getCapability(Capability.key_f6)
case 0x76 /* VK_F7 */ => getCapability(Capability.key_f7)
case 0x77 /* VK_F8 */ => getCapability(Capability.key_f8)
case 0x78 /* VK_F9 */ => getCapability(Capability.key_f9)
case 0x79 /* VK_F10 */ => getCapability(Capability.key_f10)
case 0x7A /* VK_F11 */ => getCapability(Capability.key_f11)
case 0x7B /* VK_F12 */ => getCapability(Capability.key_f12)
// VK_END, VK_INSERT and VK_DELETE are not in the ansi key bindings so we
// have to manually apply the the sequences here and in JLine3.wrap
case 0x23 /* VK_END */ =>
Option(getCapability(Capability.key_end)).getOrElse("\u001B[4!")
case 0x2D /* VK_INSERT */ =>
Option(getCapability(Capability.key_ic)).getOrElse("\u001B[2~")
case 0x2E /* VK_DELETE */ =>
Option(getCapability(Capability.key_dc)).getOrElse("\u001B[3~")
case _ => null
}
escapeSequence match {
case null =>
case es => (0 until keyEvent.repeatCount.toInt).foreach(_ => sb.append(es))
}
}
} else {
// key up event
// support ALT+NumPad input method
if (keyEvent.keyCode == 0x12 /*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
sb.append(keyEvent.uchar);
}
}
}
sb.toString().getBytes()
}
}
private[this] val raw: InputStream = new SimpleInputStream {
val buffer = new LinkedBlockingQueue[Integer]
@tailrec
override def read(): Int = {
buffer.poll match {
case null =>
readConsoleInput().foreach(b => buffer.put(b & 0xFF))
if (!Thread.interrupted) read() else throw new InterruptedException
case b => b
}
}
}
private[this] val isRaw = new AtomicBoolean(true)
private[sbt] def setRawMode(toggle: Boolean): Unit = isRaw.set(toggle)
override def read(): Int = if (isRaw.get) raw.read() else in.read()
}

View File

@ -85,6 +85,7 @@ public class BootServerSocket implements AutoCloseable {
private final Object lock = new Object();
private final LinkedBlockingQueue<ClientSocket> clientSocketReads = new LinkedBlockingQueue<>();
private final Path socketFile;
private final AtomicBoolean needInput = new AtomicBoolean(false);
private class ClientSocket implements AutoCloseable {
final Socket socket;
@ -116,13 +117,20 @@ public class BootServerSocket implements AutoCloseable {
final InputStream inputStream = socket.getInputStream();
while (alive.get()) {
try {
int b = inputStream.read();
if (b != -1) {
bytes.put(b);
clientSocketReads.put(ClientSocket.this);
} else {
alive.set(false);
synchronized (needInput) {
while (!needInput.get() && alive.get()) needInput.wait();
}
if (alive.get()) {
socket.getOutputStream().write(5);
int b = inputStream.read();
if (b != -1) {
bytes.put(b);
clientSocketReads.put(ClientSocket.this);
} else {
alive.set(false);
}
}
} catch (IOException e) {
alive.set(false);
}
@ -209,10 +217,18 @@ public class BootServerSocket implements AutoCloseable {
@Override
public int read() {
try {
synchronized (needInput) {
needInput.set(true);
needInput.notifyAll();
}
ClientSocket clientSocket = clientSocketReads.take();
return clientSocket.bytes.take();
} catch (final InterruptedException e) {
return -1;
} finally {
synchronized (needInput) {
needInput.set(false);
}
}
}
};

View File

@ -38,8 +38,10 @@ import scala.util.{ Failure, Properties, Success, Try }
import Serialization.{
CancelAll,
attach,
cancelReadSystemIn,
cancelRequest,
promptChannel,
readSystemIn,
systemIn,
systemErr,
systemOut,
@ -50,6 +52,8 @@ import Serialization.{
terminalGetSize,
terminalPropertiesQuery,
terminalPropertiesResponse,
terminalSetEcho,
terminalSetRawMode,
terminalSetSize,
getTerminalAttributes,
setTerminalAttributes,
@ -147,13 +151,17 @@ class NetworkClient(
}
}
private[this] val stdinBytes = new LinkedBlockingQueue[Int]
private[this] val stdinBytes = new LinkedBlockingQueue[Integer]
private[this] val inLock = new Object
private[this] val inputThread = new AtomicReference(new RawInputThread)
private[this] val inputThread = new AtomicReference[RawInputThread]
private[this] val exitClean = new AtomicBoolean(true)
private[this] val sbtProcess = new AtomicReference[Process](null)
private class ConnectionRefusedException(t: Throwable) extends Throwable(t)
private class ServerFailedException extends Exception
private[this] def startInputThread(): Unit = inputThread.get match {
case null => inputThread.set(new RawInputThread)
case _ =>
}
// Open server connection based on the portfile
def init(promptCompleteUsers: Boolean, retry: Boolean): ServerConnection =
@ -165,8 +173,10 @@ class NetworkClient(
if (noStdErr) System.exit(0)
else if (noTab) waitForServer(portfile, log = true, startServer = true)
else {
stdinBytes.take match {
case 9 =>
startInputThread()
stdinBytes.poll(5, TimeUnit.SECONDS) match {
case null => System.exit(0)
case i if i == 9 =>
errorStream.println("\nStarting server...")
waitForServer(portfile, !promptCompleteUsers, startServer = true)
case _ => System.exit(0)
@ -250,8 +260,13 @@ class NetworkClient(
Option(inputThread.get).foreach(_.close())
Option(interactiveThread.get).foreach(_.interrupt)
}
case "readInput" =>
case _ => self.onNotification(msg)
case `readSystemIn` => startInputThread()
case `cancelReadSystemIn` =>
inputThread.get match {
case null =>
case t => t.close()
}
case _ => self.onNotification(msg)
}
}
override def onRequest(msg: JsonRpcRequestMessage): Unit = self.onRequest(msg)
@ -289,9 +304,10 @@ class NetworkClient(
var socket: Option[Socket] =
if (!Properties.isLinux) Try(ClientSocket.localSocket(bootSocketName, useJNI)).toOption
else None
val term = Terminal.console
term.exitRawMode()
val process = socket match {
case None if startServer =>
val term = Terminal.console
if (log) console.appendLog(Level.Info, "server was not detected. starting an instance")
val props =
@ -349,6 +365,7 @@ class NetworkClient(
s.getInputStream.read match {
case -1 | 0 => readThreadAlive.set(false)
case 2 => gotInputBack = true
case 5 => term.enterRawMode(); startInputThread()
case 3 if gotInputBack => readThreadAlive.set(false)
case i if gotInputBack => stdinBytes.offer(i)
case i => printStream.write(i)
@ -381,9 +398,6 @@ class NetworkClient(
while (!gotInputBack && !stdinBytes.isEmpty && socket.isDefined) {
val out = s.getOutputStream
val b = stdinBytes.poll
// echo stdin during boot
printStream.write(b)
printStream.flush()
out.write(b)
out.flush()
}
@ -610,18 +624,13 @@ class NetworkClient(
case (`terminalCapabilities`, Some(json)) =>
Converter.fromJson[TerminalCapabilitiesQuery](json) match {
case Success(terminalCapabilitiesQuery) =>
val jline3 = terminalCapabilitiesQuery.jline3
val response = TerminalCapabilitiesResponse(
terminalCapabilitiesQuery.boolean
.map(Terminal.console.getBooleanCapability(_, jline3)),
.map(Terminal.console.getBooleanCapability(_)),
terminalCapabilitiesQuery.numeric
.map(
c => Option(Terminal.console.getNumericCapability(c, jline3)).fold(-1)(_.toInt)
),
.map(c => Option(Terminal.console.getNumericCapability(c)).fold(-1)(_.toInt)),
terminalCapabilitiesQuery.string
.map(
s => Option(Terminal.console.getStringCapability(s, jline3)).getOrElse("null")
),
.map(s => Option(Terminal.console.getStringCapability(s)).getOrElse("null")),
)
sendCommandResponse(
terminalCapabilitiesResponse,
@ -677,6 +686,21 @@ class NetworkClient(
sendCommandResponse("", TerminalSetSizeResponse(), msg.id)
case Failure(_) =>
}
case (`terminalSetEcho`, Some(json)) =>
Converter.fromJson[TerminalSetEchoCommand](json) match {
case Success(echo) =>
Terminal.console.setEchoEnabled(echo.toggle)
sendCommandResponse("", TerminalSetEchoResponse(), msg.id)
case Failure(_) =>
}
case (`terminalSetRawMode`, Some(json)) =>
Converter.fromJson[TerminalSetRawModeCommand](json) match {
case Success(raw) =>
if (raw.toggle) Terminal.console.enterRawMode()
else Terminal.console.exitRawMode()
sendCommandResponse("", TerminalSetRawModeResponse(), msg.id)
case Failure(_) =>
}
case _ =>
}
}
@ -787,9 +811,11 @@ class NetworkClient(
else if (noTab) updateCompletions()
else {
errorStream.print(s"\nNo cached $label names found. Press '<tab>' to compile: ")
stdinBytes.take match {
case 9 => updateCompletions()
case _ => Nil
startInputThread()
stdinBytes.poll(5, TimeUnit.SECONDS) match {
case null => Nil
case i if i == 9 => updateCompletions()
case _ => Nil
}
}
}
@ -901,17 +927,18 @@ class NetworkClient(
start()
val stopped = new AtomicBoolean(false)
override final def run(): Unit = {
@tailrec def read(): Unit = {
def read(): Unit = {
inputStream.read match {
case -1 =>
case b =>
inLock.synchronized(stdinBytes.offer(b))
if (attached.get()) drain()
if (!stopped.get()) read()
}
}
try Terminal.console.withRawInput(read())
catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) }
try read()
catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) } finally {
inputThread.set(null)
}
}
def drain(): Unit = inLock.synchronized {
@ -1098,6 +1125,7 @@ object NetworkClient {
System.out.flush()
})
Runtime.getRuntime.addShutdownHook(hook)
if (Util.isNonCygwinWindows) sbt.internal.util.JLine3.forceWindowsJansi()
System.exit(Terminal.withStreams(false) {
val term = Terminal.console
try client(base, restOfArgs, term.inputStream, System.err, term, useJNI)

View File

@ -64,12 +64,13 @@ public final class MetaBuildLoader extends URLClassLoader {
* library.
*/
public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException {
final Pattern pattern =
Pattern.compile(
"^(test-interface-[0-9.]+|jline-(terminal-)?[0-9.]+-sbt-.*|jansi-[0-9.]+)\\.jar");
final String jlineJars = "jline-(terminal-)?[0-9.]+-sbt-.*|jline-terminal-(jna|jansi)-[0-9.]+";
final String fullPattern =
"^(test-interface-[0-9.]+|" + jlineJars + "|jansi-[0-9.]+|jna-(platform-)?[0-9.]+)\\.jar";
final Pattern pattern = Pattern.compile(fullPattern);
final File[] cp = appProvider.mainClasspath();
final URL[] interfaceURLs = new URL[1];
final URL[] jlineURLs = new URL[3];
final URL[] jlineURLs = new URL[7];
final File[] extra =
appProvider.id().classpathExtra() == null ? new File[0] : appProvider.id().classpathExtra();
final Set<File> bottomClasspath = new LinkedHashSet<>();

View File

@ -386,6 +386,8 @@ object Defaults extends BuildCommon {
sys.env.contains("CI") || SysProp.ci,
// watch related settings
pollInterval :== Watch.defaultPollInterval,
canonicalInput :== true,
echoInput :== true,
) ++ LintUnused.lintSettings
++ DefaultBackgroundJobService.backgroundJobServiceSettings
++ RemoteCache.globalSettings
@ -1700,6 +1702,20 @@ object Defaults extends BuildCommon {
/** Implements `cleanFiles` task. */
private[sbt] def cleanFilesTask: Initialize[Task[Vector[File]]] = Def.task { Vector.empty[File] }
private[this] def termWrapper(canonical: Boolean, echo: Boolean): (() => Unit) => (() => Unit) =
(f: () => Unit) =>
() => {
val term = Terminal.get
if (!canonical) {
term.enterRawMode()
if (echo) term.setEchoEnabled(echo)
} else if (!echo) term.setEchoEnabled(false)
try f()
finally {
if (!canonical) term.exitRawMode()
if (!echo) term.setEchoEnabled(true)
}
}
def bgRunMainTask(
products: Initialize[Task[Classpath]],
classpath: Initialize[Task[Classpath]],
@ -1713,6 +1729,7 @@ object Defaults extends BuildCommon {
val service = bgJobService.value
val (mainClass, args) = parser.parsed
val hashClasspath = (bgHashClasspath in bgRunMain).value
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) =>
val files =
if (copyClasspath.value)
@ -1722,9 +1739,9 @@ object Defaults extends BuildCommon {
scalaRun.value match {
case r: Run =>
val loader = r.newLoader(cp)
(Some(loader), () => r.runWithLoader(loader, cp, mainClass, args, logger).get)
(Some(loader), wrapper(() => r.runWithLoader(loader, cp, mainClass, args, logger).get))
case sr =>
(None, () => sr.run(mainClass, cp, args, logger).get)
(None, wrapper(() => sr.run(mainClass, cp, args, logger).get))
}
}
}
@ -1743,6 +1760,7 @@ object Defaults extends BuildCommon {
val service = bgJobService.value
val mainClass = mainClassTask.value getOrElse sys.error("No main class detected.")
val hashClasspath = (bgHashClasspath in bgRun).value
val wrapper = termWrapper(canonicalInput.value, echoInput.value)
service.runInBackgroundWithLoader(resolvedScoped.value, state.value) { (logger, workingDir) =>
val files =
if (copyClasspath.value)
@ -1753,9 +1771,9 @@ object Defaults extends BuildCommon {
scalaRun.value match {
case r: Run =>
val loader = r.newLoader(cp)
(Some(loader), () => r.runWithLoader(loader, cp, mainClass, args, logger).get)
(Some(loader), wrapper(() => r.runWithLoader(loader, cp, mainClass, args, logger).get))
case sr =>
(None, () => sr.run(mainClass, cp, args, logger).get)
(None, wrapper(() => sr.run(mainClass, cp, args, logger).get))
}
}
}

View File

@ -68,6 +68,8 @@ object Keys {
val logBuffered = settingKey[Boolean]("True if logging should be buffered until work completes.").withRank(CSetting)
val sLog = settingKey[Logger]("Logger usable by settings during project loading.").withRank(CSetting)
val serverLog = taskKey[Unit]("A dummy task to set server log level using Global / serverLog / logLevel.").withRank(CTask)
val canonicalInput = settingKey[Boolean]("Toggles whether a task should use canonical input (line buffered with echo) or raw input").withRank(DSetting)
val echoInput = settingKey[Boolean]("Toggles whether a task should echo user input").withRank(DSetting)
// Project keys
val autoGeneratedProject = settingKey[Boolean]("If it exists, represents that the project (and name) were automatically created, rather than user specified.").withRank(DSetting)

View File

@ -227,11 +227,11 @@ object StandardMain {
// This is to workaround https://github.com/sbt/io/issues/110
sys.props.put("jna.nosys", "true")
import BasicCommandStrings.isEarlyCommand
import BasicCommandStrings.{ DashDashDetachStdio, DashDashServer, isEarlyCommand }
val userCommands =
configuration.arguments
.map(_.trim)
.filterNot(_ == BasicCommandStrings.DashDashDetachStdio)
.filterNot(c => c == DashDashDetachStdio || c == DashDashServer)
val (earlyCommands, normalCommands) = (preCommands ++ userCommands).partition(isEarlyCommand)
val commands = (earlyCommands ++ normalCommands).toList map { x =>
Exec(x, None)

View File

@ -62,16 +62,14 @@ object ConsoleProject {
val initCommands = importString + extra
val terminal = Terminal.get
terminal.withCanonicalIn {
// TODO - Hook up dsl classpath correctly...
(new Console(compiler))(
unit.classpath,
options,
initCommands,
cleanupCommands,
terminal
)(Some(unit.loader), bindings).get
}
// TODO - Hook up dsl classpath correctly...
(new Console(compiler))(
unit.classpath,
options,
initCommands,
cleanupCommands,
terminal
)(Some(unit.loader), bindings).get
()
}

View File

@ -42,7 +42,7 @@ import scala.collection.mutable
import scala.concurrent.duration._
import scala.util.Try
import scala.util.control.NonFatal
import Serialization.attach
import Serialization.{ attach, cancelReadSystemIn, readSystemIn }
import sjsonnew._
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
@ -643,12 +643,19 @@ final class NetworkChannel(
private[this] lazy val inputStream: InputStream = new InputStream {
override def read(): Int = {
import sjsonnew.BasicJsonProtocol._
try {
jsonRpcNotify(readSystemIn, "")
inputBuffer.take & 0xFF match {
case -1 => throw new ClosedChannelException()
case b => b
}
} catch { case e: IOException => -1 }
} catch {
case e: IOException =>
try jsonRpcNotify(cancelReadSystemIn, "")
catch { case _: IOException => }
-1
}
}
override def available(): Int = inputBuffer.size
}
@ -800,7 +807,7 @@ final class NetworkChannel(
override def isAnsiSupported: Boolean = getProperty(_.isAnsiSupported, false).getOrElse(false)
override def isEchoEnabled: Boolean = waitForPending(_.isEchoEnabled)
override def isSuccessEnabled: Boolean =
prompt != Prompt.Batch ||
interactive.get ||
StandardMain.exchange.withState(ContinuousCommands.isInWatch(_, NetworkChannel.this))
override lazy val isColorEnabled: Boolean = waitForPending(_.isColorEnabled)
override lazy val isSupershellEnabled: Boolean = waitForPending(_.isSupershellEnabled)
@ -816,34 +823,19 @@ final class NetworkChannel(
Some(result(queue.take))
}
}
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean =
override def getBooleanCapability(capability: String): Boolean =
getCapability(
TerminalCapabilitiesQuery(
boolean = Some(capability),
numeric = None,
string = None,
jline3
),
TerminalCapabilitiesQuery(boolean = Some(capability), numeric = None, string = None),
_.boolean.getOrElse(false)
).getOrElse(false)
override def getNumericCapability(capability: String, jline3: Boolean): Integer =
override def getNumericCapability(capability: String): Integer =
getCapability(
TerminalCapabilitiesQuery(
boolean = None,
numeric = Some(capability),
string = None,
jline3
),
TerminalCapabilitiesQuery(boolean = None, numeric = Some(capability), string = None),
(_: TerminalCapabilitiesResponse).numeric.map(Integer.valueOf).getOrElse(-1: Integer)
).getOrElse(-1: Integer)
override def getStringCapability(capability: String, jline3: Boolean): String =
override def getStringCapability(capability: String): String =
getCapability(
TerminalCapabilitiesQuery(
boolean = None,
numeric = None,
string = Some(capability),
jline3
),
TerminalCapabilitiesQuery(boolean = None, numeric = None, string = Some(capability)),
_.string.flatMap {
case "null" => None
case s => Some(s)
@ -899,6 +891,25 @@ final class NetworkChannel(
try queue.take
catch { case _: InterruptedException => }
}
private[this] def setRawMode(toggle: Boolean): Unit = {
if (!closed.get || false) {
import sbt.protocol.codec.JsonProtocol._
val raw = TerminalSetRawModeCommand(toggle)
val queue = VirtualTerminal.setTerminalRawMode(name, jsonRpcRequest, raw)
try queue.take
catch { case _: InterruptedException => }
}
}
override private[sbt] def enterRawMode(): Unit = setRawMode(true)
override private[sbt] def exitRawMode(): Unit = setRawMode(false)
override def setEchoEnabled(toggle: Boolean): Unit =
if (!closed.get) {
import sbt.protocol.codec.JsonProtocol._
val echo = TerminalSetEchoCommand(toggle)
val queue = VirtualTerminal.setTerminalEcho(name, jsonRpcRequest, echo)
try queue.take
catch { case _: InterruptedException => () }
}
override def flush(): Unit = doFlush()
override def toString: String = s"NetworkTerminal($name)"

View File

@ -22,7 +22,9 @@ import sbt.protocol.Serialization.{
terminalCapabilities,
terminalGetSize,
terminalPropertiesQuery,
terminalSetEcho,
terminalSetSize,
terminalSetRawMode,
}
import sjsonnew.support.scalajson.unsafe.Converter
import sbt.protocol.{
@ -35,7 +37,9 @@ import sbt.protocol.{
TerminalGetSizeQuery,
TerminalGetSizeResponse,
TerminalSetAttributesCommand,
TerminalSetEchoCommand,
TerminalSetSizeCommand,
TerminalSetRawModeCommand,
}
import sbt.protocol.codec.JsonProtocol._
import sbt.protocol.TerminalGetSizeResponse
@ -53,6 +57,10 @@ object VirtualTerminal {
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
private[this] val pendingTerminalGetSize =
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalGetSizeResponse]]
private[this] val pendingTerminalSetEcho =
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
private[this] val pendingTerminalSetRawMode =
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
private[sbt] def sendTerminalPropertiesQuery(
channelName: String,
jsonRpcRequest: (String, String, String) => Unit
@ -134,6 +142,30 @@ object VirtualTerminal {
queue
}
private[sbt] def setTerminalEcho(
channelName: String,
jsonRpcRequest: (String, String, TerminalSetEchoCommand) => Unit,
query: TerminalSetEchoCommand
): ArrayBlockingQueue[Unit] = {
val id = UUID.randomUUID.toString
val queue = new ArrayBlockingQueue[Unit](1)
pendingTerminalSetEcho.put((channelName, id), queue)
jsonRpcRequest(id, terminalSetEcho, query)
queue
}
private[sbt] def setTerminalRawMode(
channelName: String,
jsonRpcRequest: (String, String, TerminalSetRawModeCommand) => Unit,
query: TerminalSetRawModeCommand
): ArrayBlockingQueue[Unit] = {
val id = UUID.randomUUID.toString
val queue = new ArrayBlockingQueue[Unit](1)
pendingTerminalSetEcho.put((channelName, id), queue)
jsonRpcRequest(id, terminalSetRawMode, query)
queue
}
val handler = ServerHandler { cb =>
ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb))
}
@ -193,6 +225,16 @@ object VirtualTerminal {
case null =>
case buffer => buffer.put(response.getOrElse(TerminalGetSizeResponse(1, 1)))
}
case r if pendingTerminalSetEcho.get((callback.name, r.id)) != null =>
pendingTerminalSetEcho.remove((callback.name, r.id)) match {
case null =>
case buffer => buffer.put(())
}
case r if pendingTerminalSetRawMode.get((callback.name, r.id)) != null =>
pendingTerminalSetRawMode.remove((callback.name, r.id)) match {
case null =>
case buffer => buffer.put(())
}
}
private val notificationHandler: Handler[JsonRpcNotificationMessage] =
callback => {

View File

@ -87,6 +87,7 @@ object Dependencies {
val jline3Version = "3.16.0" // Once the base jline version is upgraded, we can use the official jline-terminal
val jline3Terminal = "org.scala-sbt.jline3" % "jline-terminal" % s"$jline3Version-sbt-211a082ed6326908dc84ca017ce4430728f18a8a"
val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version
val jline3JNA = "org.jline" % "jline-terminal-jna" % jline3Version
val jline3Reader = "org.jline" % "jline-reader" % jline3Version
val jansi = "org.fusesource.jansi" % "jansi" % "1.18"
val scalatest = "org.scalatest" %% "scalatest" % "3.0.8"

View File

@ -7,23 +7,22 @@ package sbt.protocol
final class TerminalCapabilitiesQuery private (
val boolean: Option[String],
val numeric: Option[String],
val string: Option[String],
val jline3: Boolean) extends sbt.protocol.CommandMessage() with Serializable {
val string: Option[String]) extends sbt.protocol.CommandMessage() with Serializable {
override def equals(o: Any): Boolean = o match {
case x: TerminalCapabilitiesQuery => (this.boolean == x.boolean) && (this.numeric == x.numeric) && (this.string == x.string) && (this.jline3 == x.jline3)
case x: TerminalCapabilitiesQuery => (this.boolean == x.boolean) && (this.numeric == x.numeric) && (this.string == x.string)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (17 + "sbt.protocol.TerminalCapabilitiesQuery".##) + boolean.##) + numeric.##) + string.##) + jline3.##)
37 * (37 * (37 * (37 * (17 + "sbt.protocol.TerminalCapabilitiesQuery".##) + boolean.##) + numeric.##) + string.##)
}
override def toString: String = {
"TerminalCapabilitiesQuery(" + boolean + ", " + numeric + ", " + string + ", " + jline3 + ")"
"TerminalCapabilitiesQuery(" + boolean + ", " + numeric + ", " + string + ")"
}
private[this] def copy(boolean: Option[String] = boolean, numeric: Option[String] = numeric, string: Option[String] = string, jline3: Boolean = jline3): TerminalCapabilitiesQuery = {
new TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
private[this] def copy(boolean: Option[String] = boolean, numeric: Option[String] = numeric, string: Option[String] = string): TerminalCapabilitiesQuery = {
new TerminalCapabilitiesQuery(boolean, numeric, string)
}
def withBoolean(boolean: Option[String]): TerminalCapabilitiesQuery = {
copy(boolean = boolean)
@ -43,12 +42,9 @@ final class TerminalCapabilitiesQuery private (
def withString(string: String): TerminalCapabilitiesQuery = {
copy(string = Option(string))
}
def withJline3(jline3: Boolean): TerminalCapabilitiesQuery = {
copy(jline3 = jline3)
}
}
object TerminalCapabilitiesQuery {
def apply(boolean: Option[String], numeric: Option[String], string: Option[String], jline3: Boolean): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
def apply(boolean: String, numeric: String, string: String, jline3: Boolean): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(Option(boolean), Option(numeric), Option(string), jline3)
def apply(boolean: Option[String], numeric: Option[String], string: Option[String]): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(boolean, numeric, string)
def apply(boolean: String, numeric: String, string: String): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(Option(boolean), Option(numeric), Option(string))
}

View File

@ -0,0 +1,32 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.protocol
final class TerminalSetEchoCommand private (
val toggle: Boolean) extends sbt.protocol.CommandMessage() with Serializable {
override def equals(o: Any): Boolean = o match {
case x: TerminalSetEchoCommand => (this.toggle == x.toggle)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (17 + "sbt.protocol.TerminalSetEchoCommand".##) + toggle.##)
}
override def toString: String = {
"TerminalSetEchoCommand(" + toggle + ")"
}
private[this] def copy(toggle: Boolean = toggle): TerminalSetEchoCommand = {
new TerminalSetEchoCommand(toggle)
}
def withToggle(toggle: Boolean): TerminalSetEchoCommand = {
copy(toggle = toggle)
}
}
object TerminalSetEchoCommand {
def apply(toggle: Boolean): TerminalSetEchoCommand = new TerminalSetEchoCommand(toggle)
}

View 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 TerminalSetEchoResponse private () extends sbt.protocol.EventMessage() with Serializable {
override def equals(o: Any): Boolean = o match {
case _: TerminalSetEchoResponse => true
case _ => false
}
override def hashCode: Int = {
37 * (17 + "sbt.protocol.TerminalSetEchoResponse".##)
}
override def toString: String = {
"TerminalSetEchoResponse()"
}
private[this] def copy(): TerminalSetEchoResponse = {
new TerminalSetEchoResponse()
}
}
object TerminalSetEchoResponse {
def apply(): TerminalSetEchoResponse = new TerminalSetEchoResponse()
}

View File

@ -0,0 +1,32 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.protocol
final class TerminalSetRawModeCommand private (
val toggle: Boolean) extends sbt.protocol.CommandMessage() with Serializable {
override def equals(o: Any): Boolean = o match {
case x: TerminalSetRawModeCommand => (this.toggle == x.toggle)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (17 + "sbt.protocol.TerminalSetRawModeCommand".##) + toggle.##)
}
override def toString: String = {
"TerminalSetRawModeCommand(" + toggle + ")"
}
private[this] def copy(toggle: Boolean = toggle): TerminalSetRawModeCommand = {
new TerminalSetRawModeCommand(toggle)
}
def withToggle(toggle: Boolean): TerminalSetRawModeCommand = {
copy(toggle = toggle)
}
}
object TerminalSetRawModeCommand {
def apply(toggle: Boolean): TerminalSetRawModeCommand = new TerminalSetRawModeCommand(toggle)
}

View 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 TerminalSetRawModeReponse private () extends sbt.protocol.EventMessage() with Serializable {
override def equals(o: Any): Boolean = o match {
case _: TerminalSetRawModeReponse => true
case _ => false
}
override def hashCode: Int = {
37 * (17 + "sbt.protocol.TerminalSetRawModeReponse".##)
}
override def toString: String = {
"TerminalSetRawModeReponse()"
}
private[this] def copy(): TerminalSetRawModeReponse = {
new TerminalSetRawModeReponse()
}
}
object TerminalSetRawModeReponse {
def apply(): TerminalSetRawModeReponse = new TerminalSetRawModeReponse()
}

View 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 TerminalSetRawModeResponse private () extends sbt.protocol.EventMessage() with Serializable {
override def equals(o: Any): Boolean = o match {
case _: TerminalSetRawModeResponse => true
case _ => false
}
override def hashCode: Int = {
37 * (17 + "sbt.protocol.TerminalSetRawModeResponse".##)
}
override def toString: String = {
"TerminalSetRawModeResponse()"
}
private[this] def copy(): TerminalSetRawModeResponse = {
new TerminalSetRawModeResponse()
}
}
object TerminalSetRawModeResponse {
def apply(): TerminalSetRawModeResponse = new TerminalSetRawModeResponse()
}

View File

@ -6,6 +6,6 @@
package sbt.protocol.codec
import _root_.sjsonnew.JsonFormat
trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats =>
implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat9[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery, sbt.protocol.TerminalSetAttributesCommand, sbt.protocol.TerminalAttributesQuery, sbt.protocol.TerminalGetSizeQuery, sbt.protocol.TerminalSetSizeCommand]("type")
trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats with sbt.protocol.codec.TerminalSetEchoCommandFormats with sbt.protocol.codec.TerminalSetRawModeCommandFormats =>
implicit lazy val CommandMessageFormat: JsonFormat[sbt.protocol.CommandMessage] = flatUnionFormat11[sbt.protocol.CommandMessage, sbt.protocol.InitCommand, sbt.protocol.ExecCommand, sbt.protocol.SettingQuery, sbt.protocol.Attach, sbt.protocol.TerminalCapabilitiesQuery, sbt.protocol.TerminalSetAttributesCommand, sbt.protocol.TerminalAttributesQuery, sbt.protocol.TerminalGetSizeQuery, sbt.protocol.TerminalSetSizeCommand, sbt.protocol.TerminalSetEchoCommand, sbt.protocol.TerminalSetRawModeCommand]("type")
}

View File

@ -6,6 +6,6 @@
package sbt.protocol.codec
import _root_.sjsonnew.JsonFormat
trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.internal.util.codec.JValueFormats with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats with sbt.protocol.codec.TerminalPropertiesResponseFormats with sbt.protocol.codec.TerminalCapabilitiesResponseFormats with sbt.protocol.codec.TerminalSetAttributesResponseFormats with sbt.protocol.codec.TerminalAttributesResponseFormats with sbt.protocol.codec.TerminalGetSizeResponseFormats with sbt.protocol.codec.TerminalSetSizeResponseFormats =>
implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat11[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure, sbt.protocol.TerminalPropertiesResponse, sbt.protocol.TerminalCapabilitiesResponse, sbt.protocol.TerminalSetAttributesResponse, sbt.protocol.TerminalAttributesResponse, sbt.protocol.TerminalGetSizeResponse, sbt.protocol.TerminalSetSizeResponse]("type")
trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.internal.util.codec.JValueFormats with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats with sbt.protocol.codec.TerminalPropertiesResponseFormats with sbt.protocol.codec.TerminalCapabilitiesResponseFormats with sbt.protocol.codec.TerminalSetAttributesResponseFormats with sbt.protocol.codec.TerminalAttributesResponseFormats with sbt.protocol.codec.TerminalGetSizeResponseFormats with sbt.protocol.codec.TerminalSetSizeResponseFormats with sbt.protocol.codec.TerminalSetEchoResponseFormats with sbt.protocol.codec.TerminalSetRawModeResponseFormats =>
implicit lazy val EventMessageFormat: JsonFormat[sbt.protocol.EventMessage] = flatUnionFormat13[sbt.protocol.EventMessage, sbt.protocol.ChannelAcceptedEvent, sbt.protocol.LogEvent, sbt.protocol.ExecStatusEvent, sbt.protocol.SettingQuerySuccess, sbt.protocol.SettingQueryFailure, sbt.protocol.TerminalPropertiesResponse, sbt.protocol.TerminalCapabilitiesResponse, sbt.protocol.TerminalSetAttributesResponse, sbt.protocol.TerminalAttributesResponse, sbt.protocol.TerminalGetSizeResponse, sbt.protocol.TerminalSetSizeResponse, sbt.protocol.TerminalSetEchoResponse, sbt.protocol.TerminalSetRawModeResponse]("type")
}

View File

@ -14,6 +14,8 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.protocol.codec.TerminalAttributesQueryFormats
with sbt.protocol.codec.TerminalGetSizeQueryFormats
with sbt.protocol.codec.TerminalSetSizeCommandFormats
with sbt.protocol.codec.TerminalSetEchoCommandFormats
with sbt.protocol.codec.TerminalSetRawModeCommandFormats
with sbt.protocol.codec.CommandMessageFormats
with sbt.protocol.codec.CompletionParamsFormats
with sbt.protocol.codec.ChannelAcceptedEventFormats
@ -28,6 +30,8 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.protocol.codec.TerminalAttributesResponseFormats
with sbt.protocol.codec.TerminalGetSizeResponseFormats
with sbt.protocol.codec.TerminalSetSizeResponseFormats
with sbt.protocol.codec.TerminalSetEchoResponseFormats
with sbt.protocol.codec.TerminalSetRawModeResponseFormats
with sbt.protocol.codec.EventMessageFormats
with sbt.protocol.codec.SettingQueryResponseFormats
with sbt.protocol.codec.CompletionResponseFormats

View File

@ -14,9 +14,8 @@ implicit lazy val TerminalCapabilitiesQueryFormat: JsonFormat[sbt.protocol.Termi
val boolean = unbuilder.readField[Option[String]]("boolean")
val numeric = unbuilder.readField[Option[String]]("numeric")
val string = unbuilder.readField[Option[String]]("string")
val jline3 = unbuilder.readField[Boolean]("jline3")
unbuilder.endObject()
sbt.protocol.TerminalCapabilitiesQuery(boolean, numeric, string, jline3)
sbt.protocol.TerminalCapabilitiesQuery(boolean, numeric, string)
case None =>
deserializationError("Expected JsObject but found None")
}
@ -26,7 +25,6 @@ implicit lazy val TerminalCapabilitiesQueryFormat: JsonFormat[sbt.protocol.Termi
builder.addField("boolean", obj.boolean)
builder.addField("numeric", obj.numeric)
builder.addField("string", obj.string)
builder.addField("jline3", obj.jline3)
builder.endObject()
}
}

View 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 TerminalSetEchoCommandFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val TerminalSetEchoCommandFormat: JsonFormat[sbt.protocol.TerminalSetEchoCommand] = new JsonFormat[sbt.protocol.TerminalSetEchoCommand] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetEchoCommand = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val toggle = unbuilder.readField[Boolean]("toggle")
unbuilder.endObject()
sbt.protocol.TerminalSetEchoCommand(toggle)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.protocol.TerminalSetEchoCommand, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("toggle", obj.toggle)
builder.endObject()
}
}
}

View 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 TerminalSetEchoResponseFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val TerminalSetEchoResponseFormat: JsonFormat[sbt.protocol.TerminalSetEchoResponse] = new JsonFormat[sbt.protocol.TerminalSetEchoResponse] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetEchoResponse = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
unbuilder.endObject()
sbt.protocol.TerminalSetEchoResponse()
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.protocol.TerminalSetEchoResponse, builder: Builder[J]): Unit = {
builder.beginObject()
builder.endObject()
}
}
}

View 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 TerminalSetRawModeCommandFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val TerminalSetRawModeCommandFormat: JsonFormat[sbt.protocol.TerminalSetRawModeCommand] = new JsonFormat[sbt.protocol.TerminalSetRawModeCommand] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetRawModeCommand = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val toggle = unbuilder.readField[Boolean]("toggle")
unbuilder.endObject()
sbt.protocol.TerminalSetRawModeCommand(toggle)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.protocol.TerminalSetRawModeCommand, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("toggle", obj.toggle)
builder.endObject()
}
}
}

View 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 TerminalSetRawModeReponseFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val TerminalSetRawModeReponseFormat: JsonFormat[sbt.protocol.TerminalSetRawModeReponse] = new JsonFormat[sbt.protocol.TerminalSetRawModeReponse] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetRawModeReponse = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
unbuilder.endObject()
sbt.protocol.TerminalSetRawModeReponse()
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.protocol.TerminalSetRawModeReponse, builder: Builder[J]): Unit = {
builder.beginObject()
builder.endObject()
}
}
}

View 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 TerminalSetRawModeResponseFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val TerminalSetRawModeResponseFormat: JsonFormat[sbt.protocol.TerminalSetRawModeResponse] = new JsonFormat[sbt.protocol.TerminalSetRawModeResponse] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.TerminalSetRawModeResponse = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
unbuilder.endObject()
sbt.protocol.TerminalSetRawModeResponse()
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.protocol.TerminalSetRawModeResponse, builder: Builder[J]): Unit = {
builder.beginObject()
builder.endObject()
}
}
}

View File

@ -97,7 +97,6 @@ type TerminalCapabilitiesQuery implements CommandMessage {
boolean: String
numeric: String
string: String
jline3: Boolean!
}
type TerminalCapabilitiesResponse implements EventMessage {
@ -138,3 +137,15 @@ type TerminalSetSizeCommand implements CommandMessage {
}
type TerminalSetSizeResponse implements EventMessage {}
type TerminalSetEchoCommand implements CommandMessage {
toggle: Boolean!
}
type TerminalSetEchoResponse implements EventMessage {}
type TerminalSetRawModeCommand implements CommandMessage {
toggle: Boolean!
}
type TerminalSetRawModeResponse implements EventMessage {}

View File

@ -24,6 +24,8 @@ import sbt.internal.protocol.{
object Serialization {
private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8"
val readSystemIn = "sbt/readSystemIn"
val cancelReadSystemIn = "sbt/cancelReadSystemIn"
val systemIn = "sbt/systemIn"
val systemOut = "sbt/systemOut"
val systemErr = "sbt/systemErr"
@ -41,6 +43,8 @@ object Serialization {
val getTerminalAttributes = "sbt/getTerminalAttributes"
val terminalGetSize = "sbt/terminalGetSize"
val terminalSetSize = "sbt/terminalSetSize"
val terminalSetEcho = "sbt/terminalSetEcho"
val terminalSetRawMode = "sbt/terminalSetRawMode"
val CancelAll = "__CancelAll"
@deprecated("unused", since = "1.4.0")