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, ParsedLine,
UserInterruptException, UserInterruptException,
} }
import org.jline.utils.ClosedException
import sbt.internal.util.complete.Parser import sbt.internal.util.complete.Parser
import scala.annotation.tailrec import scala.annotation.tailrec
@ -87,7 +88,7 @@ object LineReader {
case e: EndOfFileException => case e: EndOfFileException =>
if (terminal == Terminal.console && System.console == null) None if (terminal == Terminal.console && System.console == null) None
else Some("exit") else Some("exit")
case _: IOError => Some("exit") case _: IOError | _: ClosedException => Some("exit")
case _: UserInterruptException | _: ClosedByInterruptException | case _: UserInterruptException | _: ClosedByInterruptException |
_: UncheckedIOException => _: UncheckedIOException =>
throw new InterruptedException 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.InfoCmp.Capability
import org.jline.utils.{ ClosedException, NonBlockingReader } import org.jline.utils.{ ClosedException, NonBlockingReader }
import org.jline.terminal.{ Attributes, Size, Terminal => JTerminal } 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.Terminal.SignalHandler
import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal } import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal }
import org.jline.terminal.impl.jansi.JansiSupportImpl import org.jline.terminal.impl.jansi.JansiSupportImpl
@ -24,12 +25,7 @@ import scala.util.Try
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
private[sbt] object JLine3 { private[sbt] object JLine3 {
private val capabilityMap = Capability private[util] val initialAttributes = new AtomicReference[Attributes]
.values()
.map { c =>
c.toString -> c
}
.toMap
private[this] val forceWindowsJansiHolder = new AtomicBoolean(false) private[this] val forceWindowsJansiHolder = new AtomicBoolean(false)
private[sbt] def forceWindowsJansi(): Unit = forceWindowsJansiHolder.set(true) private[sbt] def forceWindowsJansi(): Unit = forceWindowsJansiHolder.set(true)
@ -51,18 +47,24 @@ private[sbt] object JLine3 {
term term
} }
private[util] def system: org.jline.terminal.Terminal = { private[util] def system: org.jline.terminal.Terminal = {
if (forceWindowsJansiHolder.get) windowsJansi() val term =
else { if (forceWindowsJansiHolder.get) windowsJansi()
// Only use jna on windows. Both jna and jansi use illegal reflective else {
// accesses on posix system. // Only use jna on windows. Both jna and jansi use illegal reflective
org.jline.terminal.TerminalBuilder // accesses on posix system.
.builder() org.jline.terminal.TerminalBuilder
.system(System.console != null) .builder()
.jna(Util.isNonCygwinWindows) .system(System.console != null)
.jansi(false) .jna(Util.isNonCygwinWindows)
.paused(true) .jansi(false)
.build() .paused(true)
.build()
}
initialAttributes.get match {
case null => initialAttributes.set(term.getAttributes)
case _ =>
} }
term
} }
private[sbt] def apply(term: Terminal): JTerminal = { private[sbt] def apply(term: Terminal): JTerminal = {
if (System.getProperty("jline.terminal", "") == "none" || !Terminal.formatEnabledInEnv) if (System.getProperty("jline.terminal", "") == "none" || !Terminal.formatEnabledInEnv)
@ -70,7 +72,12 @@ private[sbt] object JLine3 {
else wrapTerminal(term) else wrapTerminal(term)
} }
private[this] def wrapTerminal(term: Terminal): JTerminal = { 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) val closed = new AtomicBoolean(false)
setOnClose { () => setOnClose { () =>
doClose() doClose()
@ -84,7 +91,6 @@ private[sbt] object JLine3 {
} }
} }
} }
parseInfoCmp()
override val input: InputStream = new InputStream { override val input: InputStream = new InputStream {
override def read: Int = { override def read: Int = {
val res = term.inputStream match { val res = term.inputStream match {
@ -166,45 +172,47 @@ private[sbt] object JLine3 {
* are the same. * are the same.
*/ */
override def getStringCapability(cap: Capability): String = { override def getStringCapability(cap: Capability): String = {
term.getStringCapability(cap.toString, jline3 = true) term.getStringCapability(cap.toString)
}
override def getNumericCapability(cap: Capability): Integer = {
term.getNumericCapability(cap.toString, jline3 = true)
}
override def getBooleanCapability(cap: Capability): Boolean = {
term.getBooleanCapability(cap.toString, jline3 = true)
} }
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 getAttributes(): Attributes = attributesFromMap(term.getAttributes)
def getSize(): Size = new Size(term.getWidth, term.getHeight) 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) def setSize(size: Size): Unit = term.setSize(size.getColumns, size.getRows)
/** override def enterRawMode(): Attributes = {
* Override enterRawMode because the default implementation modifies System.in // don't actually modify the term, that is handled by LineReader
* to be non-blocking which means it immediately returns -1 if there is no attributesFromMap(term.getAttributes)
* data available, which is not desirable for us. }
*/
override def enterRawMode(): Attributes = enterRawModeImpl(this)
} }
} }
private def enterRawModeImpl(term: JTerminal): Attributes = { private def enterRawModeImpl(term: JTerminal): Attributes = {
val prvAttr = term.getAttributes() val prvAttr = term.getAttributes()
val newAttr = new Attributes(prvAttr) val newAttr = new Attributes(prvAttr)
newAttr.setLocalFlags( newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO, LocalFlag.IEXTEN), false)
EnumSet newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false)
.of(Attributes.LocalFlag.ICANON, Attributes.LocalFlag.ECHO, Attributes.LocalFlag.IEXTEN),
false
)
newAttr.setInputFlags(
EnumSet
.of(Attributes.InputFlag.IXON, Attributes.InputFlag.ICRNL, Attributes.InputFlag.INLCR),
false
)
term.setAttributes(newAttr) term.setAttributes(newAttr)
prvAttr prvAttr
} }
private[util] def enterRawMode(term: JTerminal): Map[String, String] = private[util] def enterRawMode(term: JTerminal): Unit = {
toMap(enterRawModeImpl(term)) 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] = { private[util] def toMap(jattributes: Attributes): Map[String, String] = {
val result = new java.util.LinkedHashMap[String, String] val result = new java.util.LinkedHashMap[String, String]
result.put( result.put(
@ -233,14 +241,14 @@ private[sbt] object JLine3 {
) )
result.asScala.toMap result.asScala.toMap
} }
private[this] val iflagMap: Map[String, Attributes.InputFlag] = private[this] val iflagMap: Map[String, InputFlag] =
Attributes.InputFlag.values.map(f => f.name.toLowerCase -> f).toMap InputFlag.values.map(f => f.name.toLowerCase -> f).toMap
private[this] val oflagMap: Map[String, Attributes.OutputFlag] = private[this] val oflagMap: Map[String, Attributes.OutputFlag] =
Attributes.OutputFlag.values.map(f => f.name.toLowerCase -> f).toMap Attributes.OutputFlag.values.map(f => f.name.toLowerCase -> f).toMap
private[this] val cflagMap: Map[String, Attributes.ControlFlag] = private[this] val cflagMap: Map[String, Attributes.ControlFlag] =
Attributes.ControlFlag.values.map(f => f.name.toLowerCase -> f).toMap Attributes.ControlFlag.values.map(f => f.name.toLowerCase -> f).toMap
private[this] val lflagMap: Map[String, Attributes.LocalFlag] = private[this] val lflagMap: Map[String, LocalFlag] =
Attributes.LocalFlag.values.map(f => f.name.toLowerCase -> f).toMap LocalFlag.values.map(f => f.name.toLowerCase -> f).toMap
private[this] val charMap: Map[String, Attributes.ControlChar] = private[this] val charMap: Map[String, Attributes.ControlChar] =
Attributes.ControlChar.values().map(f => f.name.toLowerCase -> f).toMap Attributes.ControlChar.values().map(f => f.name.toLowerCase -> f).toMap
private[util] def attributesFromMap(map: Map[String, String]): Attributes = { 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.atomic.{ AtomicBoolean, AtomicReference }
import java.util.concurrent.{ Executors, LinkedBlockingQueue, TimeUnit } import java.util.concurrent.{ Executors, LinkedBlockingQueue, TimeUnit }
import jline.DefaultTerminal2
import jline.console.ConsoleReader import jline.console.ConsoleReader
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -104,6 +103,13 @@ trait Terminal extends AutoCloseable {
*/ */
def isSupershellEnabled: Boolean 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 * The methods below this comment are implementation details that are in
* some cases specific to jline2. These methods may need to change or be * 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 getLines: Seq[String]
private[sbt] def getBooleanCapability(capability: String, jline3: Boolean): Boolean private[sbt] def getBooleanCapability(capability: String): Boolean
private[sbt] def getNumericCapability(capability: String, jline3: Boolean): Integer private[sbt] def getNumericCapability(capability: String): Integer
private[sbt] def getStringCapability(capability: String, jline3: Boolean): String private[sbt] def getStringCapability(capability: String): String
private[sbt] def getAttributes: Map[String, String] private[sbt] def getAttributes: Map[String, String]
private[sbt] def setAttributes(attributes: Map[String, String]): Unit private[sbt] def setAttributes(attributes: Map[String, String]): Unit
private[sbt] def setSize(width: Int, height: Int): Unit private[sbt] def setSize(width: Int, height: Int): Unit
private[sbt] def name: String 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 write(bytes: Int*): Unit
private[sbt] def printStream: PrintStream private[sbt] def printStream: PrintStream
private[sbt] def withPrintStream[T](f: PrintStream => T): T private[sbt] def withPrintStream[T](f: PrintStream => T): T
@ -188,7 +200,6 @@ object Terminal {
* terminal. * terminal.
*/ */
private[sbt] def toJLine: jline.Terminal with jline.Terminal2 = term match { private[sbt] def toJLine: jline.Terminal with jline.Terminal2 = term match {
case t: ConsoleTerminal => t.term
case _ => case _ =>
new jline.Terminal with jline.Terminal2 { new jline.Terminal with jline.Terminal2 {
override def init(): Unit = {} override def init(): Unit = {}
@ -206,15 +217,12 @@ object Terminal {
override def disableInterruptCharacter(): Unit = {} override def disableInterruptCharacter(): Unit = {}
override def enableInterruptCharacter(): Unit = {} override def enableInterruptCharacter(): Unit = {}
override def getOutputEncoding: String = null override def getOutputEncoding: String = null
override def getBooleanCapability(capability: String): Boolean = { override def getBooleanCapability(capability: String): Boolean =
term.getBooleanCapability(capability, jline3 = false) term.getBooleanCapability(capability)
} override def getNumericCapability(capability: String): Integer =
override def getNumericCapability(capability: String): Integer = { term.getNumericCapability(capability)
term.getNumericCapability(capability, jline3 = false) override def getStringCapability(capability: String): String =
} term.getStringCapability(capability)
override def getStringCapability(capability: String): String = {
term.getStringCapability(capability, jline3 = false)
}
} }
} }
} }
@ -318,7 +326,7 @@ object Terminal {
// In ci environments, don't touch the io streams unless run with -Dsbt.io.virtual=true // 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)) { if (System.getProperty("sbt.io.virtual", "") == "true" || (logFormatEnabled.getOrElse(true) && !isCI)) {
hasProgress.set(isServer) hasProgress.set(isServer)
consoleTerminalHolder.set(wrap(jline.TerminalFactory.get)) consoleTerminalHolder.set(newConsoleTerminal())
activeTerminal.set(consoleTerminalHolder.get) activeTerminal.set(consoleTerminalHolder.get)
try withOut(withIn(f)) try withOut(withIn(f))
finally { finally {
@ -333,7 +341,7 @@ object Terminal {
* back to blocking mode. We can then close the console. We do * back to blocking mode. We can then close the console. We do
* this on a background thread in case the read blocks indefinitely. * this on a background thread in case the read blocks indefinitely.
*/ */
val prev = c.system.enterRawMode() c.system.enterRawMode()
val runnable: Runnable = () => { val runnable: Runnable = () => {
try Util.ignoreResult(c.inputStream.read) try Util.ignoreResult(c.inputStream.read)
catch { case _: InterruptedException => } catch { case _: InterruptedException => }
@ -344,7 +352,6 @@ object Terminal {
// The thread should exit almost instantly but give it 200ms to spin up // The thread should exit almost instantly but give it 200ms to spin up
thread.join(200) thread.join(200)
if (thread.isAlive) thread.interrupt() if (thread.isAlive) thread.interrupt()
c.system.setAttributes(prev)
c.close() c.close()
case c => c.close() case c => c.close()
} }
@ -357,6 +364,8 @@ object Terminal {
private[this] object ProxyTerminal extends Terminal { private[this] object ProxyTerminal extends Terminal {
private def t: Terminal = activeTerminal.get private def t: Terminal = activeTerminal.get
override private[sbt] def progressState: ProgressState = t.progressState 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 getWidth: Int = t.getWidth
override def getHeight: Int = t.getHeight override def getHeight: Int = t.getHeight
override def getLineHeightAndWidth(line: String): (Int, Int) = t.getLineHeightAndWidth(line) override def getLineHeightAndWidth(line: String): (Int, Int) = t.getLineHeightAndWidth(line)
@ -369,17 +378,17 @@ object Terminal {
override def isEchoEnabled: Boolean = t.isEchoEnabled override def isEchoEnabled: Boolean = t.isEchoEnabled
override def isSuccessEnabled: Boolean = t.isSuccessEnabled override def isSuccessEnabled: Boolean = t.isSuccessEnabled
override def isSupershellEnabled: Boolean = t.isSupershellEnabled override def isSupershellEnabled: Boolean = t.isSupershellEnabled
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = override def setEchoEnabled(toggle: Boolean): Unit = t.setEchoEnabled(toggle)
t.getBooleanCapability(capability, jline3) override def getBooleanCapability(capability: String): Boolean =
override def getNumericCapability(capability: String, jline3: Boolean): Integer = t.getBooleanCapability(capability)
t.getNumericCapability(capability, jline3) override def getNumericCapability(capability: String): Integer =
override def getStringCapability(capability: String, jline3: Boolean): String = t.getNumericCapability(capability)
t.getStringCapability(capability, jline3) override def getStringCapability(capability: String): String =
t.getStringCapability(capability)
override private[sbt] def getAttributes: Map[String, String] = t.getAttributes override private[sbt] def getAttributes: Map[String, String] = t.getAttributes
override private[sbt] def setAttributes(attributes: Map[String, String]): Unit = override private[sbt] def setAttributes(attributes: Map[String, String]): Unit =
t.setAttributes(attributes) t.setAttributes(attributes)
override private[sbt] def setSize(width: Int, height: Int): Unit = t.setSize(width, height) 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 printStream: PrintStream = t.printStream
override def withPrintStream[T](f: PrintStream => T): T = t.withPrintStream(f) override def withPrintStream[T](f: PrintStream => T): T = t.withPrintStream(f)
override private[sbt] def withRawOutput[R](f: => R): R = t.withRawOutput(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 { final def write(bytes: Int*): Unit = readThread.synchronized {
bytes.foreach(b => buffer.put(b)) bytes.foreach(b => buffer.put(b))
} }
def setRawMode(toggle: Boolean): Unit = in match {
case win: WindowsInputStream => win.setRawMode(toggle)
case _ =>
}
private[this] val executor = private[this] val executor =
Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader")) Executors.newSingleThreadExecutor(r => new Thread(r, s"sbt-$name-input-reader"))
private[this] val buffer = new LinkedBlockingQueue[Integer] private[this] val buffer = new LinkedBlockingQueue[Integer]
@ -500,8 +513,10 @@ object Terminal {
() ()
} }
} }
private[this] lazy val nonBlockingIn: WriteableInputStream = private[this] def nonBlockingIn(term: org.jline.terminal.Terminal): WriteableInputStream = {
new WriteableInputStream(jline.TerminalFactory.get.wrapInIfNeeded(originalIn), "console") 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] val inputStream = new AtomicReference[InputStream](System.in)
private[this] def withOut[T](f: => T): T = { private[this] def withOut[T](f: => T): T = {
@ -699,75 +714,16 @@ object Terminal {
} }
private[sbt] def startedByRemoteClient = props.isDefined private[sbt] def startedByRemoteClient = props.isDefined
/** private[this] def newConsoleTerminal(): Terminal = {
* Creates an instance of [[Terminal]] that delegates most of its methods to an underlying val system = JLine3.system
* jline.Terminal2 instance. In the long run, sbt should upgrade to jline3, which has a val in = if (System.console == null) nullWriteableInputStream else nonBlockingIn(system)
* completely different terminal interface so whereever possible, we should avoid new ConsoleTerminal(in, originalOut, system)
* 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[sbt] def reset(): Unit = { private[sbt] def reset(): Unit = {
jline.TerminalFactory.reset() jline.TerminalFactory.reset()
console.close() console.close()
consoleTerminalHolder.set(wrap(jline.TerminalFactory.get)) consoleTerminalHolder.set(newConsoleTerminal())
} }
// translate explicit class names to type in order to support // translate explicit class names to type in order to support
@ -813,52 +769,56 @@ object Terminal {
@deprecated("For compatibility only", "1.4.0") @deprecated("For compatibility only", "1.4.0")
private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine private[sbt] def deprecatedTeminal: jline.Terminal = console.toJLine
private class ConsoleTerminal( private[util] class ConsoleTerminal(
val term: jline.Terminal with jline.Terminal2,
in: WriteableInputStream, in: WriteableInputStream,
out: OutputStream out: OutputStream,
private[util] val system: org.jline.terminal.Terminal,
) extends TerminalImpl(in, out, originalErr, "console0") { ) 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) = { override private[sbt] def getSizeImpl: (Int, Int) = {
val size = system.getSize val size = system.getSize
(size.getColumns, size.getRows) (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 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 isSuccessEnabled: Boolean = true
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = override def setEchoEnabled(toggle: Boolean): Unit =
if (jline3) capabilityMap.get(capability).fold(false)(system.getBooleanCapability) try Util.ignoreResult(system.echo(toggle))
else term.getBooleanCapability(capability) catch { case _: InterruptedIOException => }
override def getNumericCapability(capability: String, jline3: Boolean): Integer = override def getBooleanCapability(capability: String): Boolean =
if (jline3) capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability) capabilityMap.get(capability).fold(false)(system.getBooleanCapability)
else term.getNumericCapability(capability) override def getNumericCapability(capability: String): Integer =
override def getStringCapability(capability: String, jline3: Boolean): String = capabilityMap.get(capability).fold(null: Integer)(system.getNumericCapability)
if (jline3) capabilityMap.get(capability).fold(null: String)(system.getStringCapability) override def getStringCapability(capability: String): String = {
else term.getStringCapability(capability) val res = capabilityMap.get(capability).fold(null: String)(system.getStringCapability)
override private[sbt] def restore(): Unit = term.restore() res
}
override private[sbt] def restore(): Unit = exitRawMode()
override private[sbt] def getAttributes: Map[String, String] = override private[sbt] def getAttributes: Map[String, String] =
Try(JLine3.toMap(system.getAttributes)).getOrElse(Map.empty) 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)) system.setAttributes(JLine3.attributesFromMap(attributes))
}
override private[sbt] def setSize(width: Int, height: Int): Unit = override private[sbt] def setSize(width: Int, height: Int): Unit =
system.setSize(new org.jline.terminal.Size(width, height)) system.setSize(new org.jline.terminal.Size(width, height))
override def withRawInput[T](f: => T): T = term.synchronized { override private[sbt] def enterRawMode(): Unit = if (rawMode.compareAndSet(false, true)) {
try { in.setRawMode(true)
term.init() JLine3.enterRawMode(system)
term.setEchoEnabled(false) }
f override private[sbt] def exitRawMode(): Unit = if (rawMode.compareAndSet(true, false)) {
} catch { case _: InterruptedIOException => throw new InterruptedException } finally { in.setRawMode(false)
term.restore() JLine3.exitRawMode(system)
term.setEchoEnabled(true)
}
} }
override def isColorEnabled: Boolean = override def isColorEnabled: Boolean =
props props
.map(_.color) .map(_.color)
.getOrElse(isColorEnabledProp.getOrElse(term.isAnsiSupported && formatEnabledInEnv)) .getOrElse(isColorEnabledProp.getOrElse(formatEnabledInEnv))
override def isSupershellEnabled: Boolean = override def isSupershellEnabled: Boolean =
props props
@ -871,8 +831,9 @@ object Terminal {
}) })
override def close(): Unit = { override def close(): Unit = {
try { try {
system.setAttributes(JLine3.initialAttributes.get)
system.close() system.close()
term.restore() in.close()
} catch { case NonFatal(_) => } } catch { case NonFatal(_) => }
super.close() super.close()
} }
@ -972,13 +933,15 @@ object Terminal {
private[sbt] class DefaultTerminal extends Terminal { private[sbt] class DefaultTerminal extends Terminal {
override def close(): Unit = {} override def close(): Unit = {}
override private[sbt] def progressState: ProgressState = new ProgressState(1) 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 getHeight: Int = 0
override def getLastLine: Option[String] = None override def getLastLine: Option[String] = None
override def getLines: Seq[String] = Nil override def getLines: Seq[String] = Nil
override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0) override def getLineHeightAndWidth(line: String): (Int, Int) = (0, 0)
override def getNumericCapability(capability: String, jline3: Boolean): Integer = null override def getNumericCapability(capability: String): Integer = null
override def getStringCapability(capability: String, jline3: Boolean): String = null override def getStringCapability(capability: String): String = null
override def getWidth: Int = 0 override def getWidth: Int = 0
override def inputStream: InputStream = nullInputStream override def inputStream: InputStream = nullInputStream
override def isAnsiSupported: Boolean = false override def isAnsiSupported: Boolean = false
@ -986,6 +949,7 @@ object Terminal {
override def isEchoEnabled: Boolean = false override def isEchoEnabled: Boolean = false
override def isSuccessEnabled: Boolean = true override def isSuccessEnabled: Boolean = true
override def isSupershellEnabled: Boolean = false override def isSupershellEnabled: Boolean = false
override def setEchoEnabled(toggle: Boolean): Unit = {}
override def outputStream: OutputStream = _ => {} override def outputStream: OutputStream = _ => {}
override def errorStream: OutputStream = _ => {} override def errorStream: OutputStream = _ => {}
override private[sbt] def getAttributes: Map[String, String] = Map.empty 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 NullTerminal extends DefaultTerminal
private[sbt] object SimpleTerminal 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 outputStream: OutputStream = originalOut
override lazy val errorStream: OutputStream = originalErr 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 Object lock = new Object();
private final LinkedBlockingQueue<ClientSocket> clientSocketReads = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue<ClientSocket> clientSocketReads = new LinkedBlockingQueue<>();
private final Path socketFile; private final Path socketFile;
private final AtomicBoolean needInput = new AtomicBoolean(false);
private class ClientSocket implements AutoCloseable { private class ClientSocket implements AutoCloseable {
final Socket socket; final Socket socket;
@ -116,13 +117,20 @@ public class BootServerSocket implements AutoCloseable {
final InputStream inputStream = socket.getInputStream(); final InputStream inputStream = socket.getInputStream();
while (alive.get()) { while (alive.get()) {
try { try {
int b = inputStream.read(); synchronized (needInput) {
if (b != -1) { while (!needInput.get() && alive.get()) needInput.wait();
bytes.put(b);
clientSocketReads.put(ClientSocket.this);
} else {
alive.set(false);
} }
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) { } catch (IOException e) {
alive.set(false); alive.set(false);
} }
@ -209,10 +217,18 @@ public class BootServerSocket implements AutoCloseable {
@Override @Override
public int read() { public int read() {
try { try {
synchronized (needInput) {
needInput.set(true);
needInput.notifyAll();
}
ClientSocket clientSocket = clientSocketReads.take(); ClientSocket clientSocket = clientSocketReads.take();
return clientSocket.bytes.take(); return clientSocket.bytes.take();
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
return -1; return -1;
} finally {
synchronized (needInput) {
needInput.set(false);
}
} }
} }
}; };

View File

@ -38,8 +38,10 @@ import scala.util.{ Failure, Properties, Success, Try }
import Serialization.{ import Serialization.{
CancelAll, CancelAll,
attach, attach,
cancelReadSystemIn,
cancelRequest, cancelRequest,
promptChannel, promptChannel,
readSystemIn,
systemIn, systemIn,
systemErr, systemErr,
systemOut, systemOut,
@ -50,6 +52,8 @@ import Serialization.{
terminalGetSize, terminalGetSize,
terminalPropertiesQuery, terminalPropertiesQuery,
terminalPropertiesResponse, terminalPropertiesResponse,
terminalSetEcho,
terminalSetRawMode,
terminalSetSize, terminalSetSize,
getTerminalAttributes, getTerminalAttributes,
setTerminalAttributes, setTerminalAttributes,
@ -149,11 +153,15 @@ class NetworkClient(
private[this] val stdinBytes = new LinkedBlockingQueue[Int] private[this] val stdinBytes = new LinkedBlockingQueue[Int]
private[this] val inLock = new Object 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 exitClean = new AtomicBoolean(true)
private[this] val sbtProcess = new AtomicReference[Process](null) private[this] val sbtProcess = new AtomicReference[Process](null)
private class ConnectionRefusedException(t: Throwable) extends Throwable(t) private class ConnectionRefusedException(t: Throwable) extends Throwable(t)
private class ServerFailedException extends Exception 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 // Open server connection based on the portfile
def init(promptCompleteUsers: Boolean, retry: Boolean): ServerConnection = def init(promptCompleteUsers: Boolean, retry: Boolean): ServerConnection =
@ -165,6 +173,7 @@ class NetworkClient(
if (noStdErr) System.exit(0) if (noStdErr) System.exit(0)
else if (noTab) waitForServer(portfile, log = true, startServer = true) else if (noTab) waitForServer(portfile, log = true, startServer = true)
else { else {
startInputThread()
stdinBytes.take match { stdinBytes.take match {
case 9 => case 9 =>
errorStream.println("\nStarting server...") errorStream.println("\nStarting server...")
@ -250,8 +259,13 @@ class NetworkClient(
Option(inputThread.get).foreach(_.close()) Option(inputThread.get).foreach(_.close())
Option(interactiveThread.get).foreach(_.interrupt) Option(interactiveThread.get).foreach(_.interrupt)
} }
case "readInput" => case `readSystemIn` => startInputThread()
case _ => self.onNotification(msg) case `cancelReadSystemIn` =>
inputThread.get match {
case null =>
case t => t.close()
}
case _ => self.onNotification(msg)
} }
} }
override def onRequest(msg: JsonRpcRequestMessage): Unit = self.onRequest(msg) override def onRequest(msg: JsonRpcRequestMessage): Unit = self.onRequest(msg)
@ -289,9 +303,10 @@ class NetworkClient(
var socket: Option[Socket] = var socket: Option[Socket] =
if (!Properties.isLinux) Try(ClientSocket.localSocket(bootSocketName, useJNI)).toOption if (!Properties.isLinux) Try(ClientSocket.localSocket(bootSocketName, useJNI)).toOption
else None else None
val term = Terminal.console
term.exitRawMode()
val process = socket match { val process = socket match {
case None if startServer => case None if startServer =>
val term = Terminal.console
if (log) console.appendLog(Level.Info, "server was not detected. starting an instance") if (log) console.appendLog(Level.Info, "server was not detected. starting an instance")
val props = val props =
@ -349,6 +364,7 @@ class NetworkClient(
s.getInputStream.read match { s.getInputStream.read match {
case -1 | 0 => readThreadAlive.set(false) case -1 | 0 => readThreadAlive.set(false)
case 2 => gotInputBack = true case 2 => gotInputBack = true
case 5 => term.enterRawMode(); startInputThread()
case 3 if gotInputBack => readThreadAlive.set(false) case 3 if gotInputBack => readThreadAlive.set(false)
case i if gotInputBack => stdinBytes.offer(i) case i if gotInputBack => stdinBytes.offer(i)
case i => printStream.write(i) case i => printStream.write(i)
@ -381,9 +397,6 @@ class NetworkClient(
while (!gotInputBack && !stdinBytes.isEmpty && socket.isDefined) { while (!gotInputBack && !stdinBytes.isEmpty && socket.isDefined) {
val out = s.getOutputStream val out = s.getOutputStream
val b = stdinBytes.poll val b = stdinBytes.poll
// echo stdin during boot
printStream.write(b)
printStream.flush()
out.write(b) out.write(b)
out.flush() out.flush()
} }
@ -610,18 +623,13 @@ class NetworkClient(
case (`terminalCapabilities`, Some(json)) => case (`terminalCapabilities`, Some(json)) =>
Converter.fromJson[TerminalCapabilitiesQuery](json) match { Converter.fromJson[TerminalCapabilitiesQuery](json) match {
case Success(terminalCapabilitiesQuery) => case Success(terminalCapabilitiesQuery) =>
val jline3 = terminalCapabilitiesQuery.jline3
val response = TerminalCapabilitiesResponse( val response = TerminalCapabilitiesResponse(
terminalCapabilitiesQuery.boolean terminalCapabilitiesQuery.boolean
.map(Terminal.console.getBooleanCapability(_, jline3)), .map(Terminal.console.getBooleanCapability(_)),
terminalCapabilitiesQuery.numeric terminalCapabilitiesQuery.numeric
.map( .map(c => Option(Terminal.console.getNumericCapability(c)).fold(-1)(_.toInt)),
c => Option(Terminal.console.getNumericCapability(c, jline3)).fold(-1)(_.toInt)
),
terminalCapabilitiesQuery.string terminalCapabilitiesQuery.string
.map( .map(s => Option(Terminal.console.getStringCapability(s)).getOrElse("null")),
s => Option(Terminal.console.getStringCapability(s, jline3)).getOrElse("null")
),
) )
sendCommandResponse( sendCommandResponse(
terminalCapabilitiesResponse, terminalCapabilitiesResponse,
@ -677,6 +685,21 @@ class NetworkClient(
sendCommandResponse("", TerminalSetSizeResponse(), msg.id) sendCommandResponse("", TerminalSetSizeResponse(), msg.id)
case Failure(_) => 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 _ => case _ =>
} }
} }
@ -787,6 +810,7 @@ class NetworkClient(
else if (noTab) updateCompletions() else if (noTab) updateCompletions()
else { else {
errorStream.print(s"\nNo cached $label names found. Press '<tab>' to compile: ") errorStream.print(s"\nNo cached $label names found. Press '<tab>' to compile: ")
startInputThread()
stdinBytes.take match { stdinBytes.take match {
case 9 => updateCompletions() case 9 => updateCompletions()
case _ => Nil case _ => Nil
@ -901,17 +925,18 @@ class NetworkClient(
start() start()
val stopped = new AtomicBoolean(false) val stopped = new AtomicBoolean(false)
override final def run(): Unit = { override final def run(): Unit = {
@tailrec def read(): Unit = { def read(): Unit = {
inputStream.read match { inputStream.read match {
case -1 => case -1 =>
case b => case b =>
inLock.synchronized(stdinBytes.offer(b)) inLock.synchronized(stdinBytes.offer(b))
if (attached.get()) drain() if (attached.get()) drain()
if (!stopped.get()) read()
} }
} }
try Terminal.console.withRawInput(read()) try read()
catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) } catch { case _: InterruptedException | NonFatal(_) => stopped.set(true) } finally {
inputThread.set(null)
}
} }
def drain(): Unit = inLock.synchronized { def drain(): Unit = inLock.synchronized {
@ -1095,6 +1120,7 @@ object NetworkClient {
System.out.flush() System.out.flush()
}) })
Runtime.getRuntime.addShutdownHook(hook) Runtime.getRuntime.addShutdownHook(hook)
if (Util.isNonCygwinWindows) sbt.internal.util.JLine3.forceWindowsJansi()
System.exit(Terminal.withStreams(false) { System.exit(Terminal.withStreams(false) {
val term = Terminal.console val term = Terminal.console
try client(base, restOfArgs, term.inputStream, System.err, term, useJNI) 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.concurrent.duration._
import scala.util.Try import scala.util.Try
import scala.util.control.NonFatal import scala.util.control.NonFatal
import Serialization.attach import Serialization.{ attach, cancelReadSystemIn, readSystemIn }
import sjsonnew._ import sjsonnew._
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
@ -643,12 +643,19 @@ final class NetworkChannel(
private[this] lazy val inputStream: InputStream = new InputStream { private[this] lazy val inputStream: InputStream = new InputStream {
override def read(): Int = { override def read(): Int = {
import sjsonnew.BasicJsonProtocol._
try { try {
jsonRpcNotify(readSystemIn, "")
inputBuffer.take & 0xFF match { inputBuffer.take & 0xFF match {
case -1 => throw new ClosedChannelException() case -1 => throw new ClosedChannelException()
case b => b 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 override def available(): Int = inputBuffer.size
} }
@ -816,34 +823,19 @@ final class NetworkChannel(
Some(result(queue.take)) Some(result(queue.take))
} }
} }
override def getBooleanCapability(capability: String, jline3: Boolean): Boolean = override def getBooleanCapability(capability: String): Boolean =
getCapability( getCapability(
TerminalCapabilitiesQuery( TerminalCapabilitiesQuery(boolean = Some(capability), numeric = None, string = None),
boolean = Some(capability),
numeric = None,
string = None,
jline3
),
_.boolean.getOrElse(false) _.boolean.getOrElse(false)
).getOrElse(false) ).getOrElse(false)
override def getNumericCapability(capability: String, jline3: Boolean): Integer = override def getNumericCapability(capability: String): Integer =
getCapability( getCapability(
TerminalCapabilitiesQuery( TerminalCapabilitiesQuery(boolean = None, numeric = Some(capability), string = None),
boolean = None,
numeric = Some(capability),
string = None,
jline3
),
(_: TerminalCapabilitiesResponse).numeric.map(Integer.valueOf).getOrElse(-1: Integer) (_: TerminalCapabilitiesResponse).numeric.map(Integer.valueOf).getOrElse(-1: Integer)
).getOrElse(-1: Integer) ).getOrElse(-1: Integer)
override def getStringCapability(capability: String, jline3: Boolean): String = override def getStringCapability(capability: String): String =
getCapability( getCapability(
TerminalCapabilitiesQuery( TerminalCapabilitiesQuery(boolean = None, numeric = None, string = Some(capability)),
boolean = None,
numeric = None,
string = Some(capability),
jline3
),
_.string.flatMap { _.string.flatMap {
case "null" => None case "null" => None
case s => Some(s) case s => Some(s)
@ -899,6 +891,25 @@ final class NetworkChannel(
try queue.take try queue.take
catch { case _: InterruptedException => } 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 flush(): Unit = doFlush()
override def toString: String = s"NetworkTerminal($name)" override def toString: String = s"NetworkTerminal($name)"

View File

@ -22,7 +22,9 @@ import sbt.protocol.Serialization.{
terminalCapabilities, terminalCapabilities,
terminalGetSize, terminalGetSize,
terminalPropertiesQuery, terminalPropertiesQuery,
terminalSetEcho,
terminalSetSize, terminalSetSize,
terminalSetRawMode,
} }
import sjsonnew.support.scalajson.unsafe.Converter import sjsonnew.support.scalajson.unsafe.Converter
import sbt.protocol.{ import sbt.protocol.{
@ -35,7 +37,9 @@ import sbt.protocol.{
TerminalGetSizeQuery, TerminalGetSizeQuery,
TerminalGetSizeResponse, TerminalGetSizeResponse,
TerminalSetAttributesCommand, TerminalSetAttributesCommand,
TerminalSetEchoCommand,
TerminalSetSizeCommand, TerminalSetSizeCommand,
TerminalSetRawModeCommand,
} }
import sbt.protocol.codec.JsonProtocol._ import sbt.protocol.codec.JsonProtocol._
import sbt.protocol.TerminalGetSizeResponse import sbt.protocol.TerminalGetSizeResponse
@ -53,6 +57,10 @@ object VirtualTerminal {
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]] new ConcurrentHashMap[(String, String), ArrayBlockingQueue[Unit]]
private[this] val pendingTerminalGetSize = private[this] val pendingTerminalGetSize =
new ConcurrentHashMap[(String, String), ArrayBlockingQueue[TerminalGetSizeResponse]] 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( private[sbt] def sendTerminalPropertiesQuery(
channelName: String, channelName: String,
jsonRpcRequest: (String, String, String) => Unit jsonRpcRequest: (String, String, String) => Unit
@ -134,6 +142,30 @@ object VirtualTerminal {
queue 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 => val handler = ServerHandler { cb =>
ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb)) ServerIntent(requestHandler(cb), responseHandler(cb), notificationHandler(cb))
} }
@ -193,6 +225,16 @@ object VirtualTerminal {
case null => case null =>
case buffer => buffer.put(response.getOrElse(TerminalGetSizeResponse(1, 1))) 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] = private val notificationHandler: Handler[JsonRpcNotificationMessage] =
callback => { 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 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 jline3Terminal = "org.scala-sbt.jline3" % "jline-terminal" % s"$jline3Version-sbt-211a082ed6326908dc84ca017ce4430728f18a8a"
val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version
val jline3JNA = "org.jline" % "jline-terminal-jna" % jline3Version
val jline3Reader = "org.jline" % "jline-reader" % jline3Version val jline3Reader = "org.jline" % "jline-reader" % jline3Version
val jansi = "org.fusesource.jansi" % "jansi" % "1.18" val jansi = "org.fusesource.jansi" % "jansi" % "1.18"
val scalatest = "org.scalatest" %% "scalatest" % "3.0.8" val scalatest = "org.scalatest" %% "scalatest" % "3.0.8"

View File

@ -7,23 +7,22 @@ package sbt.protocol
final class TerminalCapabilitiesQuery private ( final class TerminalCapabilitiesQuery private (
val boolean: Option[String], val boolean: Option[String],
val numeric: Option[String], val numeric: Option[String],
val string: Option[String], val string: Option[String]) extends sbt.protocol.CommandMessage() with Serializable {
val jline3: Boolean) extends sbt.protocol.CommandMessage() with Serializable {
override def equals(o: Any): Boolean = o match { override def equals(o: Any): Boolean = o match {
case x: TerminalCapabilitiesQuery => (this.boolean == x.boolean) && (this.numeric == x.numeric) && (this.string == x.string) && (this.jline3 == x.jline3) case x: TerminalCapabilitiesQuery => (this.boolean == x.boolean) && (this.numeric == x.numeric) && (this.string == x.string)
case _ => false case _ => false
} }
override def hashCode: Int = { 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 = { 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 = { private[this] def copy(boolean: Option[String] = boolean, numeric: Option[String] = numeric, string: Option[String] = string): TerminalCapabilitiesQuery = {
new TerminalCapabilitiesQuery(boolean, numeric, string, jline3) new TerminalCapabilitiesQuery(boolean, numeric, string)
} }
def withBoolean(boolean: Option[String]): TerminalCapabilitiesQuery = { def withBoolean(boolean: Option[String]): TerminalCapabilitiesQuery = {
copy(boolean = boolean) copy(boolean = boolean)
@ -43,12 +42,9 @@ final class TerminalCapabilitiesQuery private (
def withString(string: String): TerminalCapabilitiesQuery = { def withString(string: String): TerminalCapabilitiesQuery = {
copy(string = Option(string)) copy(string = Option(string))
} }
def withJline3(jline3: Boolean): TerminalCapabilitiesQuery = {
copy(jline3 = jline3)
}
} }
object TerminalCapabilitiesQuery { object TerminalCapabilitiesQuery {
def apply(boolean: Option[String], numeric: Option[String], string: Option[String], jline3: Boolean): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(boolean, numeric, 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, jline3: Boolean): TerminalCapabilitiesQuery = new TerminalCapabilitiesQuery(Option(boolean), Option(numeric), Option(string), jline3) 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 package sbt.protocol.codec
import _root_.sjsonnew.JsonFormat import _root_.sjsonnew.JsonFormat
trait CommandMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.InitCommandFormats with sbt.protocol.codec.ExecCommandFormats with sbt.protocol.codec.SettingQueryFormats with sbt.protocol.codec.AttachFormats with sbt.protocol.codec.TerminalCapabilitiesQueryFormats with sbt.protocol.codec.TerminalSetAttributesCommandFormats with sbt.protocol.codec.TerminalAttributesQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats => 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] = 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") 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 package sbt.protocol.codec
import _root_.sjsonnew.JsonFormat import _root_.sjsonnew.JsonFormat
trait EventMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.LogEventFormats with sbt.protocol.codec.ExecStatusEventFormats with sbt.internal.util.codec.JValueFormats with sbt.protocol.codec.SettingQuerySuccessFormats with sbt.protocol.codec.SettingQueryFailureFormats with sbt.protocol.codec.TerminalPropertiesResponseFormats with sbt.protocol.codec.TerminalCapabilitiesResponseFormats with sbt.protocol.codec.TerminalSetAttributesResponseFormats with sbt.protocol.codec.TerminalAttributesResponseFormats with sbt.protocol.codec.TerminalGetSizeResponseFormats with sbt.protocol.codec.TerminalSetSizeResponseFormats => 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] = 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") 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.TerminalAttributesQueryFormats
with sbt.protocol.codec.TerminalGetSizeQueryFormats with sbt.protocol.codec.TerminalGetSizeQueryFormats
with sbt.protocol.codec.TerminalSetSizeCommandFormats with sbt.protocol.codec.TerminalSetSizeCommandFormats
with sbt.protocol.codec.TerminalSetEchoCommandFormats
with sbt.protocol.codec.TerminalSetRawModeCommandFormats
with sbt.protocol.codec.CommandMessageFormats with sbt.protocol.codec.CommandMessageFormats
with sbt.protocol.codec.CompletionParamsFormats with sbt.protocol.codec.CompletionParamsFormats
with sbt.protocol.codec.ChannelAcceptedEventFormats with sbt.protocol.codec.ChannelAcceptedEventFormats
@ -28,6 +30,8 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.protocol.codec.TerminalAttributesResponseFormats with sbt.protocol.codec.TerminalAttributesResponseFormats
with sbt.protocol.codec.TerminalGetSizeResponseFormats with sbt.protocol.codec.TerminalGetSizeResponseFormats
with sbt.protocol.codec.TerminalSetSizeResponseFormats with sbt.protocol.codec.TerminalSetSizeResponseFormats
with sbt.protocol.codec.TerminalSetEchoResponseFormats
with sbt.protocol.codec.TerminalSetRawModeResponseFormats
with sbt.protocol.codec.EventMessageFormats with sbt.protocol.codec.EventMessageFormats
with sbt.protocol.codec.SettingQueryResponseFormats with sbt.protocol.codec.SettingQueryResponseFormats
with sbt.protocol.codec.CompletionResponseFormats with sbt.protocol.codec.CompletionResponseFormats

View File

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

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 boolean: String
numeric: String numeric: String
string: String string: String
jline3: Boolean!
} }
type TerminalCapabilitiesResponse implements EventMessage { type TerminalCapabilitiesResponse implements EventMessage {
@ -138,3 +137,15 @@ type TerminalSetSizeCommand implements CommandMessage {
} }
type TerminalSetSizeResponse implements EventMessage {} 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 { object Serialization {
private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8" private[sbt] val VsCode = "application/vscode-jsonrpc; charset=utf-8"
val readSystemIn = "sbt/readSystemIn"
val cancelReadSystemIn = "sbt/cancelReadSystemIn"
val systemIn = "sbt/systemIn" val systemIn = "sbt/systemIn"
val systemOut = "sbt/systemOut" val systemOut = "sbt/systemOut"
val systemErr = "sbt/systemErr" val systemErr = "sbt/systemErr"
@ -41,6 +43,8 @@ object Serialization {
val getTerminalAttributes = "sbt/getTerminalAttributes" val getTerminalAttributes = "sbt/getTerminalAttributes"
val terminalGetSize = "sbt/terminalGetSize" val terminalGetSize = "sbt/terminalGetSize"
val terminalSetSize = "sbt/terminalSetSize" val terminalSetSize = "sbt/terminalSetSize"
val terminalSetEcho = "sbt/terminalSetEcho"
val terminalSetRawMode = "sbt/terminalSetRawMode"
val CancelAll = "__CancelAll" val CancelAll = "__CancelAll"
@deprecated("unused", since = "1.4.0") @deprecated("unused", since = "1.4.0")