Fix switching between raw and canonical input

There were a number of issues with swithcing between raw and canonical
issues that affected both the server and the thin client. These were
reported in #5863 and #5856. In both cases, there were issues with
reading input or having the input be displayed. Debugging those issues
revealed a number of issues with how we were using the jline 3 system
terminal and the hybrid interaction with the jline 2 terminal. This
commit eliminates all of our internal jline 2 usage. The only remaining
jline 2 usage is that we create and override the global terminal for the
scala console for scala versions < 2.13. By moving away from jline 2, I
was also able to fix #5828, which reported that the home, end and delete
keys were not working.

One of the big issues that this commit addresses is that the
NetworkClient was always performing blocking reads on System.in. This
was problematic because it turns out that you can't switch between raw
and canonical modes when there is a read present. To fix this, the
server now sends a message to the client when it wants to read bytes and
only then does the client create a background thread to read a single
byte.

I also figured out how to set the terminal type properly for the thin
client on windows where we had been manually setting the capabilities to
ansi, which only worked for some keys. This fix required switching to
the WindowsInputStream that I introduced in a prior commit. Before we
were using the jline 2 wrapped input stream which was converting some
system events, like home and end, to the wrong escape sequence mappings.

The remainder of the commit is mostly just converting from jline 2 apis
to jline 3 apis.

I verified that tab completions, the scala console, the ammonite console
and a run task that read from System.in all work with both the server
and the thin client on mac, linux and windows after these changes.

Fixes #5828, #5863, #5856
This commit is contained in:
Ethan Atkins 2020-09-20 15:27:08 -07:00
parent 410a8dd4b1
commit bb8b9a1c99
25 changed files with 609 additions and 241 deletions

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

View File

@ -14,6 +14,7 @@ import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import org.jline.utils.InfoCmp.Capability
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
@ -24,12 +25,7 @@ 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[this] val forceWindowsJansiHolder = new AtomicBoolean(false)
private[sbt] def forceWindowsJansi(): Unit = forceWindowsJansiHolder.set(true)
@ -51,18 +47,24 @@ private[sbt] object JLine3 {
term
}
private[util] def system: org.jline.terminal.Terminal = {
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.isNonCygwinWindows)
.jansi(false)
.paused(true)
.build()
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.isNonCygwinWindows)
.jansi(false)
.paused(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)
@ -70,7 +72,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()
@ -84,7 +91,6 @@ private[sbt] object JLine3 {
}
}
}
parseInfoCmp()
override val input: InputStream = new InputStream {
override def read: Int = {
val res = term.inputStream match {
@ -166,45 +172,47 @@ 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)
}
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(
@ -233,14 +241,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,15 +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] 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
@ -188,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 = {}
@ -206,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)
}
}
}
@ -318,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 {
@ -333,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 => }
@ -344,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()
}
@ -357,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)
@ -369,17 +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 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)
@ -438,6 +447,10 @@ object Terminal {
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]
@ -500,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 = {
@ -699,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
@ -813,52 +769,56 @@ 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)
JLine3.enterRawMode(system)
}
override private[sbt] def exitRawMode(): Unit = if (rawMode.compareAndSet(true, false)) {
in.setRawMode(false)
JLine3.exitRawMode(system)
}
override def isColorEnabled: Boolean =
props
.map(_.color)
.getOrElse(isColorEnabledProp.getOrElse(term.isAnsiSupported && formatEnabledInEnv))
.getOrElse(isColorEnabledProp.getOrElse(formatEnabledInEnv))
override def isSupershellEnabled: Boolean =
props
@ -871,8 +831,9 @@ object Terminal {
})
override def close(): Unit = {
try {
system.setAttributes(JLine3.initialAttributes.get)
system.close()
term.restore()
in.close()
} catch { case NonFatal(_) => }
super.close()
}
@ -972,13 +933,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
@ -986,6 +949,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
@ -1000,7 +964,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

@ -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,
@ -149,11 +153,15 @@ class NetworkClient(
private[this] val stdinBytes = new LinkedBlockingQueue[Int]
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,6 +173,7 @@ class NetworkClient(
if (noStdErr) System.exit(0)
else if (noTab) waitForServer(portfile, log = true, startServer = true)
else {
startInputThread()
stdinBytes.take match {
case 9 =>
errorStream.println("\nStarting server...")
@ -250,8 +259,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 +303,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 +364,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 +397,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 +623,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 +685,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,6 +810,7 @@ class NetworkClient(
else if (noTab) updateCompletions()
else {
errorStream.print(s"\nNo cached $label names found. Press '<tab>' to compile: ")
startInputThread()
stdinBytes.take match {
case 9 => updateCompletions()
case _ => Nil
@ -901,17 +925,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 {
@ -1095,6 +1120,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

@ -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
}
@ -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")