Add Terminal abstraction

This commit aims to centralize all of the terminal interactions
throughout sbt. It also seeks to hide the jline implementation details
and only expose the apis that sbt needs for interacting with the
terminal.

In general, we should be able to assume that the terminal is in
canonical (line buffered) mode with echo enabled. To switch to raw mode
or to enable/disable echo, there are apis: Terminal.withRawSystemIn and
Terminal.withEcho that take a thunk as parameter to ensure that the
terminal is reset back to the canonical mode afterwards.
This commit is contained in:
Ethan Atkins 2019-12-12 11:24:48 -08:00
parent cd65543d10
commit 7902ec3b7d
11 changed files with 305 additions and 154 deletions

View File

@ -7,30 +7,26 @@
package sbt.internal.util package sbt.internal.util
import java.io._
import jline.console.ConsoleReader import jline.console.ConsoleReader
import jline.console.history.{ FileHistory, MemoryHistory } import jline.console.history.{ FileHistory, MemoryHistory }
import java.io.{ File, FileDescriptor, FileInputStream, FilterInputStream, InputStream } import sbt.internal.util.complete.Parser
import complete.Parser
import jline.Terminal
import scala.concurrent.duration._
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._
abstract class JLine extends LineReader { abstract class JLine extends LineReader {
protected[this] def handleCONT: Boolean protected[this] def handleCONT: Boolean
protected[this] def reader: ConsoleReader protected[this] def reader: ConsoleReader
protected[this] def injectThreadSleep: Boolean @deprecated("For binary compatibility only", "1.4.0")
protected[this] lazy val in: InputStream = { protected[this] def injectThreadSleep: Boolean = false
// On Windows InputStream#available doesn't seem to return positive number. @deprecated("For binary compatibility only", "1.4.0")
JLine.makeInputStream(injectThreadSleep && !Util.isNonCygwinWindows) protected[this] lazy val in: InputStream = Terminal.wrappedSystemIn
}
def readLine(prompt: String, mask: Option[Char] = None) = override def readLine(prompt: String, mask: Option[Char] = None): Option[String] =
try { try {
JLine.withJLine { Terminal.withRawSystemIn(unsynchronizedReadLine(prompt, mask))
unsynchronizedReadLine(prompt, mask)
}
} catch { } catch {
case _: InterruptedException => case _: InterruptedException =>
// println("readLine: InterruptedException") // println("readLine: InterruptedException")
@ -85,88 +81,53 @@ abstract class JLine extends LineReader {
} }
private[this] def resume(): Unit = { private[this] def resume(): Unit = {
jline.TerminalFactory.reset Terminal.reset()
JLine.terminal.init
reader.drawLine() reader.drawLine()
reader.flush() reader.flush()
} }
} }
private[sbt] object JLine { private[sbt] object JLine {
private[this] val TerminalProperty = "jline.terminal" @deprecated("For binary compatibility only", "1.4.0")
fixTerminalProperty()
// translate explicit class names to type in order to support
// older Scala, since it shaded classes but not the system property
private[sbt] def fixTerminalProperty(): Unit = {
val newValue = System.getProperty(TerminalProperty) match {
case "jline.UnixTerminal" => "unix"
case null if System.getProperty("sbt.cygwin") != null => "unix"
case "jline.WindowsTerminal" => "windows"
case "jline.AnsiWindowsTerminal" => "windows"
case "jline.UnsupportedTerminal" => "none"
case x => x
}
if (newValue != null) {
System.setProperty(TerminalProperty, newValue)
()
}
}
protected[this] val originalIn = new FileInputStream(FileDescriptor.in) protected[this] val originalIn = new FileInputStream(FileDescriptor.in)
@deprecated("Handled by Terminal.fixTerminalProperty", "1.4.0")
private[sbt] def fixTerminalProperty(): Unit = ()
private[sbt] def makeInputStream(injectThreadSleep: Boolean): InputStream = private[sbt] def makeInputStream(injectThreadSleep: Boolean): InputStream =
if (injectThreadSleep) new InputStreamWrapper(originalIn, 2.milliseconds) if (injectThreadSleep) new InputStreamWrapper(originalIn, 2.milliseconds)
else originalIn else originalIn
// When calling this, ensure that enableEcho has been or will be called. // When calling this, ensure that enableEcho has been or will be called.
// TerminalFactory.get will initialize the terminal to disable echo. // TerminalFactory.get will initialize the terminal to disable echo.
private[sbt] def terminal: Terminal = jline.TerminalFactory.get @deprecated("Don't use jline.Terminal directly", "1.4.0")
private[sbt] def terminal: jline.Terminal = Terminal.deprecatedTeminal
private def withTerminal[T](f: jline.Terminal => T): T =
synchronized {
val t = terminal
t.synchronized { f(t) }
}
/** /**
* For accessing the JLine Terminal object. * For accessing the JLine Terminal object.
* This ensures synchronized access as well as re-enabling echo after getting the Terminal. * This ensures synchronized access as well as re-enabling echo after getting the Terminal.
*/ */
@deprecated("Don't use jline.Terminal directly. Use Terminal.withCanonicalIn instead.", "1.4.0")
def usingTerminal[T](f: jline.Terminal => T): T = def usingTerminal[T](f: jline.Terminal => T): T =
withTerminal { t => Terminal.withCanonicalIn(f(Terminal.deprecatedTeminal))
t.restore
val result = f(t)
t.restore
result
}
def createReader(): ConsoleReader = createReader(None, JLine.makeInputStream(true)) def createReader(): ConsoleReader = createReader(None, Terminal.wrappedSystemIn)
def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = {
usingTerminal { _ => val cr = Terminal.createReader(in)
val cr = new ConsoleReader(in, System.out) cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650 cr.setBellEnabled(false)
cr.setBellEnabled(false) val h = historyPath match {
val h = historyPath match { case None => new MemoryHistory
case None => new MemoryHistory case Some(file) => new FileHistory(file): MemoryHistory
case Some(file) => (new FileHistory(file): MemoryHistory)
}
h.setMaxSize(MaxHistorySize)
cr.setHistory(h)
cr
} }
h.setMaxSize(MaxHistorySize)
cr.setHistory(h)
cr
}
def withJLine[T](action: => T): T = @deprecated("Avoid referencing JLine directly. Use Terminal.withRawSystemIn instead.", "1.4.0")
withTerminal { t => def withJLine[T](action: => T): T = Terminal.withRawSystemIn(action)
t.init
try {
action
} finally {
t.restore
}
}
def simple( def simple(
historyPath: Option[File], historyPath: Option[File],
@ -180,6 +141,7 @@ private[sbt] object JLine {
!java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT) !java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
} }
@deprecated("For binary compatibility only", "1.4.0")
private[sbt] class InputStreamWrapper(is: InputStream, val poll: Duration) private[sbt] class InputStreamWrapper(is: InputStream, val poll: Duration)
extends FilterInputStream(is) { extends FilterInputStream(is) {
@tailrec final override def read(): Int = @tailrec final override def read(): Int =
@ -211,11 +173,17 @@ trait LineReader {
final class FullReader( final class FullReader(
historyPath: Option[File], historyPath: Option[File],
complete: Parser[_], complete: Parser[_],
val handleCONT: Boolean = JLine.HandleCONT, val handleCONT: Boolean,
val injectThreadSleep: Boolean = false inputStream: InputStream,
) extends JLine { ) extends JLine {
protected[this] val reader = { def this(
val cr = JLine.createReader(historyPath, in) historyPath: Option[File],
complete: Parser[_],
handleCONT: Boolean = JLine.HandleCONT,
injectThreadSleep: Boolean = false
) = this(historyPath, complete, handleCONT, JLine.makeInputStream(injectThreadSleep))
protected[this] val reader: ConsoleReader = {
val cr = JLine.createReader(historyPath, inputStream)
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete) sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
cr cr
} }
@ -224,9 +192,12 @@ final class FullReader(
class SimpleReader private[sbt] ( class SimpleReader private[sbt] (
historyPath: Option[File], historyPath: Option[File],
val handleCONT: Boolean, val handleCONT: Boolean,
val injectThreadSleep: Boolean inputStream: InputStream
) extends JLine { ) extends JLine {
protected[this] val reader = JLine.createReader(historyPath, in) def this(historyPath: Option[File], handleCONT: Boolean, injectThreadSleep: Boolean) =
this(historyPath, handleCONT, Terminal.wrappedSystemIn)
protected[this] val reader: ConsoleReader =
JLine.createReader(historyPath, inputStream)
} }
object SimpleReader extends SimpleReader(None, JLine.HandleCONT, false) object SimpleReader extends SimpleReader(None, JLine.HandleCONT, false)

View File

@ -7,17 +7,16 @@
package sbt.internal.util package sbt.internal.util
import sbt.util._
import java.io.{ PrintStream, PrintWriter } import java.io.{ PrintStream, PrintWriter }
import java.lang.StringBuilder import java.lang.StringBuilder
import java.util.Locale
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger, AtomicReference }
import org.apache.logging.log4j.{ Level => XLevel }
import org.apache.logging.log4j.message.{ Message, ObjectMessage, ReusableObjectMessage }
import org.apache.logging.log4j.core.{ LogEvent => XLogEvent }
import org.apache.logging.log4j.core.appender.AbstractAppender import org.apache.logging.log4j.core.appender.AbstractAppender
import scala.util.control.NonFatal import org.apache.logging.log4j.core.{ LogEvent => XLogEvent }
import ConsoleAppender._ import org.apache.logging.log4j.message.{ Message, ObjectMessage, ReusableObjectMessage }
import org.apache.logging.log4j.{ Level => XLevel }
import sbt.internal.util.ConsoleAppender._
import sbt.util._
object ConsoleLogger { object ConsoleLogger {
// These are provided so other modules do not break immediately. // These are provided so other modules do not break immediately.
@ -110,9 +109,6 @@ object ConsoleAppender {
private[sbt] final val DeleteLine = "\u001B[2K" private[sbt] final val DeleteLine = "\u001B[2K"
private[sbt] final val CursorLeft1000 = "\u001B[1000D" private[sbt] final val CursorLeft1000 = "\u001B[1000D"
private[sbt] final val CursorDown1 = cursorDown(1) private[sbt] final val CursorDown1 = cursorDown(1)
private[sbt] lazy val terminalWidth = usingTerminal { t =>
t.getWidth
}
private[this] val showProgressHolder: AtomicBoolean = new AtomicBoolean(false) private[this] val showProgressHolder: AtomicBoolean = new AtomicBoolean(false)
def setShowProgress(b: Boolean): Unit = showProgressHolder.set(b) def setShowProgress(b: Boolean): Unit = showProgressHolder.set(b)
def showProgress: Boolean = showProgressHolder.get def showProgress: Boolean = showProgressHolder.get
@ -293,31 +289,7 @@ object ConsoleAppender {
private[sbt] def generateName(): String = "out-" + generateId.incrementAndGet private[sbt] def generateName(): String = "out-" + generateId.incrementAndGet
private[this] def jline1to2CompatMsg = "Found class jline.Terminal, but interface was expected" private[this] def ansiSupported: Boolean = Terminal.isAnsiSupported
private[this] def ansiSupported =
try {
usingTerminal { t =>
t.isAnsiSupported
}
} catch {
case NonFatal(_) => !isWindows
}
/**
* For accessing the JLine Terminal object.
* This ensures re-enabling echo after getting the Terminal.
*/
private[this] def usingTerminal[T](f: jline.Terminal => T): T = {
val t = jline.TerminalFactory.get
t.restore
val result = f(t)
t.restore
result
}
private[this] def os = System.getProperty("os.name")
private[this] def isWindows = os.toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
} }
// See http://stackoverflow.com/questions/24205093/how-to-create-a-custom-appender-in-log4j2 // See http://stackoverflow.com/questions/24205093/how-to-create-a-custom-appender-in-log4j2
@ -356,7 +328,7 @@ class ConsoleAppender private[ConsoleAppender] (
out.println(s"$DeleteLine$l") out.println(s"$DeleteLine$l")
if (progress.length > 0) { if (progress.length > 0) {
val pad = if (padding.get > 0) padding.decrementAndGet() else 0 val pad = if (padding.get > 0) padding.decrementAndGet() else 0
val width = ConsoleAppender.terminalWidth val width = Terminal.getWidth
val len: Int = progress.foldLeft(progress.length)(_ + terminalLines(width)(_)) val len: Int = progress.foldLeft(progress.length)(_ + terminalLines(width)(_))
deleteConsoleLines(blankZone + pad) deleteConsoleLines(blankZone + pad)
progress.foreach(printProgressLine) progress.foreach(printProgressLine)
@ -387,7 +359,7 @@ class ConsoleAppender private[ConsoleAppender] (
s" | => ${item.name} ${elapsed}s" s" | => ${item.name} ${elapsed}s"
} }
val width = ConsoleAppender.terminalWidth val width = Terminal.getWidth
val currentLength = info.foldLeft(info.length)(_ + terminalLines(width)(_)) val currentLength = info.foldLeft(info.length)(_ + terminalLines(width)(_))
val previousLines = progressLines.getAndSet(info) val previousLines = progressLines.getAndSet(info)
val prevLength = previousLines.foldLeft(previousLines.length)(_ + terminalLines(width)(_)) val prevLength = previousLines.foldLeft(previousLines.length)(_ + terminalLines(width)(_))

View File

@ -0,0 +1,210 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.io.{ InputStream, OutputStream }
import java.util.Locale
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import java.util.concurrent.locks.ReentrantLock
import jline.console.ConsoleReader
import scala.util.control.NonFatal
object Terminal {
/**
* Gets the current width of the terminal. The implementation reads a property from the jline
* config which is updated if it has been more than a second since the last update. It is thus
* possible for this value to be stale.
*
* @return the terminal width.
*/
def getWidth: Int = terminal.getWidth
/**
* Gets the current height of the terminal. The implementation reads a property from the jline
* config which is updated if it has been more than a second since the last update. It is thus
* possible for this value to be stale.
*
* @return the terminal height.
*/
def getHeight: Int = terminal.getHeight
/**
* Returns true if the current terminal supports ansi characters.
*
* @return true if the current terminal supports ansi escape codes.
*/
def isAnsiSupported: Boolean =
try terminal.isAnsiSupported
catch { case NonFatal(_) => !isWindows }
/**
* Returns true if System.in is attached. When sbt is run as a subprocess, like in scripted or
* as a server, System.in will not be attached and this method will return false. Otherwise
* it will return true.
*
* @return true if System.in is attached.
*/
def systemInIsAttached: Boolean = attached.get
/**
* Provides a wrapper around System.in. The wrapped stream in will check if the terminal is attached
* in available and read. If a read returns -1, it will mark System.in as unattached so that
* it can be detected by [[systemInIsAttached]].
*
* @return the wrapped InputStream
*/
private[sbt] def wrappedSystemIn: InputStream = WrappedSystemIn
/**
* Restore the terminal to its initial state.
*/
private[sbt] def restore(): Unit = terminal.restore()
/**
* Runs a thunk ensuring that the terminal has echo enabled. Most of the time sbt should have
* echo mode on except when it is explicitly set to raw mode via [[withRawSystemIn]].
*
* @param f the thunk to run
* @tparam T the result type of the thunk
* @return the result of the thunk
*/
private[sbt] def withEcho[T](toggle: Boolean)(f: => T): T = {
val previous = terminal.isEchoEnabled
terminalLock.lockInterruptibly()
try {
terminal.setEchoEnabled(toggle)
f
} finally {
terminal.setEchoEnabled(previous)
terminalLock.unlock()
}
}
/**
* Runs a thunk ensuring that the terminal is in canonical mode:
* [[https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html Canonical or Not]].
* Most of the time sbt should be in canonical mode except when it is explicitly set to raw mode
* via [[withRawSystemIn]].
*
* @param f the thunk to run
* @tparam T the result type of the thunk
* @return the result of the thunk
*/
private[sbt] def withCanonicalIn[T](f: => T): T = withTerminal { t =>
t.restore()
f
}
/**
* Runs a thunk ensuring that the terminal is in in non-canonical mode:
* [[https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html Canonical or Not]].
* This should be used when sbt is reading user input, e.g. in `shell` or a continuous build.
* @param f the thunk to run
* @tparam T the result type of the thunk
* @return the result of the thunk
*/
private[sbt] def withRawSystemIn[T](f: => T): T = withTerminal { t =>
t.init()
f
}
private[this] def withTerminal[T](f: jline.Terminal => T): T = {
val t = terminal
terminalLock.lockInterruptibly()
try f(t)
finally {
t.restore()
terminalLock.unlock()
}
}
private[this] object WrappedSystemIn extends InputStream {
private[this] val in = terminal.wrapInIfNeeded(System.in)
override def available(): Int = if (attached.get) in.available else 0
override def read(): Int = synchronized {
if (attached.get) {
val res = in.read
if (res == -1) attached.set(false)
res
} else -1
}
}
private[this] val terminalLock = new ReentrantLock()
private[this] val attached = new AtomicBoolean(true)
private[this] val terminalHolder = new AtomicReference(wrap(jline.TerminalFactory.get))
private[this] lazy val isWindows =
System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).indexOf("windows") >= 0
private[this] def wrap(terminal: jline.Terminal): jline.Terminal = {
val term: jline.Terminal = new jline.Terminal {
private[this] val hasConsole = System.console != null
private[this] def alive = hasConsole && attached.get
override def init(): Unit = if (alive) terminal.init()
override def restore(): Unit = if (alive) terminal.restore()
override def reset(): Unit = if (alive) terminal.reset()
override def isSupported: Boolean = terminal.isSupported
override def getWidth: Int = terminal.getWidth
override def getHeight: Int = terminal.getHeight
override def isAnsiSupported: Boolean = terminal.isAnsiSupported
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
}
term.restore()
term.setEchoEnabled(true)
term
}
private[util] def reset(): Unit = {
jline.TerminalFactory.reset()
terminalHolder.set(wrap(jline.TerminalFactory.get))
}
// translate explicit class names to type in order to support
// older Scala, since it shaded classes but not the system property
private[this] def fixTerminalProperty(): Unit = {
val terminalProperty = "jline.terminal"
val newValue = System.getProperty(terminalProperty) match {
case "jline.UnixTerminal" => "unix"
case null if System.getProperty("sbt.cygwin") != null => "unix"
case "jline.WindowsTerminal" => "windows"
case "jline.AnsiWindowsTerminal" => "windows"
case "jline.UnsupportedTerminal" => "none"
case x => x
}
if (newValue != null) {
System.setProperty(terminalProperty, newValue)
()
}
}
fixTerminalProperty()
private[sbt] def createReader(in: InputStream): ConsoleReader =
new ConsoleReader(in, System.out, terminal)
private[this] def terminal: jline.Terminal = terminalHolder.get match {
case null => throw new IllegalStateException("Uninitialized terminal.")
case term => term
}
@deprecated("For compatibility only", "1.4.0")
private[sbt] def deprecatedTeminal: jline.Terminal = terminal
}

View File

@ -9,9 +9,10 @@ package sbt
import java.io.File import java.io.File
import sbt.internal.inc.{ AnalyzingCompiler, PlainVirtualFile } import sbt.internal.inc.{ AnalyzingCompiler, PlainVirtualFile }
import sbt.internal.util.JLine import sbt.internal.util.Terminal
import sbt.util.Logger import sbt.util.Logger
import xsbti.compile.{ Inputs, Compilers } import xsbti.compile.{ Compilers, Inputs }
import scala.util.Try import scala.util.Try
final class Console(compiler: AnalyzingCompiler) { final class Console(compiler: AnalyzingCompiler) {
@ -51,10 +52,7 @@ final class Console(compiler: AnalyzingCompiler) {
loader, loader,
bindings bindings
) )
JLine.usingTerminal { t => Terminal.withRawSystemIn(Run.executeTrapExit(console0, log))
t.init
Run.executeTrapExit(console0, log)
}
} }
} }

View File

@ -9,15 +9,15 @@ package sbt
import java.nio.file.Paths import java.nio.file.Paths
import sbt.util.Level import sbt.util.Level
import sbt.internal.util.{ AttributeKey, FullReader } import sbt.internal.util.{ AttributeKey, FullReader, JLine, Terminal }
import sbt.internal.util.complete.{ import sbt.internal.util.complete.{
Completion, Completion,
Completions, Completions,
DefaultParsers, DefaultParsers,
History => CHistory,
HistoryCommands, HistoryCommands,
Parser, Parser,
TokenCompletions TokenCompletions,
History => CHistory
} }
import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util.Types.{ const, idFun }
import sbt.internal.util.Util.{ AnyOps, nil, nilSeq, none } import sbt.internal.util.Util.{ AnyOps, nil, nilSeq, none }
@ -25,13 +25,14 @@ import sbt.internal.inc.classpath.ClasspathUtil.toLoader
import sbt.internal.inc.ModuleUtilities import sbt.internal.inc.ModuleUtilities
import sbt.internal.client.NetworkClient import sbt.internal.client.NetworkClient
import DefaultParsers._ import DefaultParsers._
import Function.tupled import Function.tupled
import Command.applyEffect import Command.applyEffect
import BasicCommandStrings._ import BasicCommandStrings._
import CommandUtil._ import CommandUtil._
import BasicKeys._ import BasicKeys._
import java.io.File import java.io.File
import sbt.io.IO import sbt.io.IO
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@ -372,7 +373,8 @@ object BasicCommands {
def oldshell: Command = Command.command(OldShell, Help.more(Shell, OldShellDetailed)) { s => def oldshell: Command = Command.command(OldShell, Help.more(Shell, OldShellDetailed)) { s =>
val history = (s get historyPath) getOrElse (new File(s.baseDir, ".history")).some val history = (s get historyPath) getOrElse (new File(s.baseDir, ".history")).some
val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " } val prompt = (s get shellPrompt) match { case Some(pf) => pf(s); case None => "> " }
val reader = new FullReader(history, s.combinedParser) val reader =
new FullReader(history, s.combinedParser, JLine.HandleCONT, Terminal.wrappedSystemIn)
val line = reader.readLine(prompt) val line = reader.readLine(prompt)
line match { line match {
case Some(line) => case Some(line) =>

View File

@ -12,19 +12,21 @@ package client
import java.io.{ File, IOException } import java.io.{ File, IOException }
import java.util.UUID import java.util.UUID
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import scala.collection.mutable.ListBuffer
import scala.util.control.NonFatal
import scala.util.{ Success, Failure }
import scala.sys.process.{ BasicIO, Process, ProcessLogger }
import sbt.protocol._
import sbt.internal.protocol._
import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams } import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams }
import sbt.internal.util.{ JLine, ConsoleAppender } import sbt.internal.protocol._
import sbt.util.Level import sbt.internal.util.{ ConsoleAppender, JLine }
import sbt.io.syntax._
import sbt.io.IO import sbt.io.IO
import sbt.io.syntax._
import sbt.protocol._
import sbt.util.Level
import sjsonnew.support.scalajson.unsafe.Converter import sjsonnew.support.scalajson.unsafe.Converter
import scala.collection.mutable.ListBuffer
import scala.sys.process.{ BasicIO, Process, ProcessLogger }
import scala.util.control.NonFatal
import scala.util.{ Failure, Success }
class NetworkClient(configuration: xsbti.AppConfiguration, arguments: List[String]) { self => class NetworkClient(configuration: xsbti.AppConfiguration, arguments: List[String]) { self =>
private val channelName = new AtomicReference("_") private val channelName = new AtomicReference("_")
private val status = new AtomicReference("Ready") private val status = new AtomicReference("Ready")

View File

@ -7,7 +7,7 @@
package sbt package sbt
import sbt.internal.util.{ JLine, SimpleReader } import sbt.internal.util.{ SimpleReader, Terminal }
trait CommandLineUIService extends InteractionService { trait CommandLineUIService extends InteractionService {
override def readLine(prompt: String, mask: Boolean): Option[String] = { override def readLine(prompt: String, mask: Boolean): Option[String] = {
@ -27,9 +27,9 @@ trait CommandLineUIService extends InteractionService {
} }
} }
override def terminalWidth: Int = JLine.terminal.getWidth override def terminalWidth: Int = Terminal.getWidth
override def terminalHeight: Int = JLine.terminal.getHeight override def terminalHeight: Int = Terminal.getHeight
} }
object CommandLineUIService extends CommandLineUIService object CommandLineUIService extends CommandLineUIService

View File

@ -10,12 +10,11 @@ package sbt
import java.io.PrintWriter import java.io.PrintWriter
import java.util.Properties import java.util.Properties
import jline.TerminalFactory
import sbt.internal.{ Aggregation, ShutdownHooks } import sbt.internal.{ Aggregation, ShutdownHooks }
import sbt.internal.langserver.ErrorCodes import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.JsonRpcResponseError import sbt.internal.protocol.JsonRpcResponseError
import sbt.internal.util.complete.Parser import sbt.internal.util.complete.Parser
import sbt.internal.util.{ ErrorHandling, GlobalLogBacking } import sbt.internal.util.{ ErrorHandling, GlobalLogBacking, Terminal }
import sbt.io.{ IO, Using } import sbt.io.{ IO, Using }
import sbt.protocol._ import sbt.protocol._
import sbt.util.Logger import sbt.util.Logger
@ -31,7 +30,7 @@ object MainLoop {
// We've disabled jline shutdown hooks to prevent classloader leaks, and have been careful to always restore // We've disabled jline shutdown hooks to prevent classloader leaks, and have been careful to always restore
// the jline terminal in finally blocks, but hitting ctrl+c prevents finally blocks from being executed, in that // the jline terminal in finally blocks, but hitting ctrl+c prevents finally blocks from being executed, in that
// case the only way to restore the terminal is in a shutdown hook. // case the only way to restore the terminal is in a shutdown hook.
val shutdownHook = ShutdownHooks.add(() => TerminalFactory.get().restore()) val shutdownHook = ShutdownHooks.add(Terminal.restore)
try { try {
runLoggedLoop(state, state.globalLogging.backing) runLoggedLoop(state, state.globalLogging.backing)

View File

@ -10,7 +10,7 @@ package internal
import sbt.internal.classpath.AlternativeZincUtil import sbt.internal.classpath.AlternativeZincUtil
import sbt.internal.inc.{ ScalaInstance, ZincLmUtil } import sbt.internal.inc.{ ScalaInstance, ZincLmUtil }
import sbt.internal.util.JLine import sbt.internal.util.Terminal
import sbt.util.Logger import sbt.util.Logger
import xsbti.compile.ClasspathOptionsUtil import xsbti.compile.ClasspathOptionsUtil
@ -61,7 +61,7 @@ object ConsoleProject {
val importString = imports.mkString("", ";\n", ";\n\n") val importString = imports.mkString("", ";\n", ";\n\n")
val initCommands = importString + extra val initCommands = importString + extra
JLine.usingTerminal { _ => Terminal.withCanonicalIn {
// TODO - Hook up dsl classpath correctly... // TODO - Hook up dsl classpath correctly...
(new Console(compiler))( (new Console(compiler))(
unit.classpath, unit.classpath,

View File

@ -26,7 +26,7 @@ import sbt.internal.io.WatchState
import sbt.internal.nio._ import sbt.internal.nio._
import sbt.internal.util.complete.Parser._ import sbt.internal.util.complete.Parser._
import sbt.internal.util.complete.{ Parser, Parsers } import sbt.internal.util.complete.{ Parser, Parsers }
import sbt.internal.util.{ AttributeKey, JLine, Util } import sbt.internal.util.{ AttributeKey, Terminal, Util }
import sbt.nio.Keys.{ fileInputs, _ } import sbt.nio.Keys.{ fileInputs, _ }
import sbt.nio.Watch.{ Creation, Deletion, ShowOptions, Update } import sbt.nio.Watch.{ Creation, Deletion, ShowOptions, Update }
import sbt.nio.file.{ FileAttributes, Glob } import sbt.nio.file.{ FileAttributes, Glob }
@ -272,11 +272,8 @@ private[sbt] object Continuous extends DeprecatedContinuous {
f(s, valid, invalid) f(s, valid, invalid)
} }
private[this] def withCharBufferedStdIn[R](f: InputStream => R): R = { private[this] def withCharBufferedStdIn[R](f: InputStream => R): R = Terminal.withRawSystemIn {
val terminal = JLine.terminal val wrapped = Terminal.wrappedSystemIn
terminal.init()
terminal.setEchoEnabled(true)
val wrapped = terminal.wrapInIfNeeded(System.in)
if (Util.isNonCygwinWindows) { if (Util.isNonCygwinWindows) {
val inputStream: InputStream with AutoCloseable = new InputStream with AutoCloseable { val inputStream: InputStream with AutoCloseable = new InputStream with AutoCloseable {
private[this] val buffer = new java.util.LinkedList[Int] private[this] val buffer = new java.util.LinkedList[Int]

View File

@ -8,13 +8,13 @@
package sbt package sbt
package internal package internal
import sbt.internal.util.{ JLine }
import sbt.util.Show import sbt.util.Show
import java.io.File import java.io.File
import Def.{ compiled, flattenLocals, ScopedKey }
import Predef.{ any2stringadd => _, _ }
import Def.{ ScopedKey, compiled, flattenLocals }
import sbt.internal.util.Terminal
import Predef.{ any2stringadd => _, _ }
import sbt.io.IO import sbt.io.IO
object SettingGraph { object SettingGraph {
@ -82,7 +82,7 @@ object Graph {
// [info] | // [info] |
// [info] +-quux // [info] +-quux
def toAscii[A](top: A, children: A => Seq[A], display: A => String, defaultWidth: Int): String = { def toAscii[A](top: A, children: A => Seq[A], display: A => String, defaultWidth: Int): String = {
val maxColumn = math.max(JLine.terminal.getWidth, defaultWidth) - 8 val maxColumn = math.max(Terminal.getWidth, defaultWidth) - 8
val twoSpaces = " " + " " // prevent accidentally being converted into a tab val twoSpaces = " " + " " // prevent accidentally being converted into a tab
def limitLine(s: String): String = def limitLine(s: String): String =
if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".." if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".."