diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index d23bc91ff..b8704c97c 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -7,30 +7,26 @@ package sbt.internal.util +import java.io._ + import jline.console.ConsoleReader 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.concurrent.duration._ abstract class JLine extends LineReader { protected[this] def handleCONT: Boolean protected[this] def reader: ConsoleReader - protected[this] def injectThreadSleep: Boolean - protected[this] lazy val in: InputStream = { - // On Windows InputStream#available doesn't seem to return positive number. - JLine.makeInputStream(injectThreadSleep && !Util.isNonCygwinWindows) - } + @deprecated("For binary compatibility only", "1.4.0") + protected[this] def injectThreadSleep: Boolean = false + @deprecated("For binary compatibility only", "1.4.0") + 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 { - JLine.withJLine { - unsynchronizedReadLine(prompt, mask) - } + Terminal.withRawSystemIn(unsynchronizedReadLine(prompt, mask)) } catch { case _: InterruptedException => // println("readLine: InterruptedException") @@ -85,88 +81,53 @@ abstract class JLine extends LineReader { } private[this] def resume(): Unit = { - jline.TerminalFactory.reset - JLine.terminal.init + Terminal.reset() reader.drawLine() reader.flush() } } private[sbt] object JLine { - private[this] val TerminalProperty = "jline.terminal" - - 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) - () - } - } - + @deprecated("For binary compatibility only", "1.4.0") 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 = if (injectThreadSleep) new InputStreamWrapper(originalIn, 2.milliseconds) else originalIn // When calling this, ensure that enableEcho has been or will be called. // TerminalFactory.get will initialize the terminal to disable echo. - private[sbt] def terminal: Terminal = jline.TerminalFactory.get - - private def withTerminal[T](f: jline.Terminal => T): T = - synchronized { - val t = terminal - t.synchronized { f(t) } - } + @deprecated("Don't use jline.Terminal directly", "1.4.0") + private[sbt] def terminal: jline.Terminal = Terminal.deprecatedTeminal /** * For accessing the JLine Terminal object. * 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 = - withTerminal { t => - t.restore - val result = f(t) - t.restore - result - } + Terminal.withCanonicalIn(f(Terminal.deprecatedTeminal)) - def createReader(): ConsoleReader = createReader(None, JLine.makeInputStream(true)) + def createReader(): ConsoleReader = createReader(None, Terminal.wrappedSystemIn) - def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = - usingTerminal { _ => - val cr = new ConsoleReader(in, System.out) - cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650 - cr.setBellEnabled(false) - val h = historyPath match { - case None => new MemoryHistory - case Some(file) => (new FileHistory(file): MemoryHistory) - } - h.setMaxSize(MaxHistorySize) - cr.setHistory(h) - cr + def createReader(historyPath: Option[File], in: InputStream): ConsoleReader = { + val cr = Terminal.createReader(in) + cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650 + cr.setBellEnabled(false) + val h = historyPath match { + case None => new MemoryHistory + case Some(file) => new FileHistory(file): MemoryHistory } + h.setMaxSize(MaxHistorySize) + cr.setHistory(h) + cr + } - def withJLine[T](action: => T): T = - withTerminal { t => - t.init - try { - action - } finally { - t.restore - } - } + @deprecated("Avoid referencing JLine directly. Use Terminal.withRawSystemIn instead.", "1.4.0") + def withJLine[T](action: => T): T = Terminal.withRawSystemIn(action) def simple( historyPath: Option[File], @@ -180,6 +141,7 @@ private[sbt] object JLine { !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) extends FilterInputStream(is) { @tailrec final override def read(): Int = @@ -211,11 +173,17 @@ trait LineReader { final class FullReader( historyPath: Option[File], complete: Parser[_], - val handleCONT: Boolean = JLine.HandleCONT, - val injectThreadSleep: Boolean = false + val handleCONT: Boolean, + inputStream: InputStream, ) extends JLine { - protected[this] val reader = { - val cr = JLine.createReader(historyPath, in) + def this( + 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) cr } @@ -224,9 +192,12 @@ final class FullReader( class SimpleReader private[sbt] ( historyPath: Option[File], val handleCONT: Boolean, - val injectThreadSleep: Boolean + inputStream: InputStream ) 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) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala index 4142071b5..84af63381 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala @@ -7,17 +7,16 @@ package sbt.internal.util -import sbt.util._ import java.io.{ PrintStream, PrintWriter } import java.lang.StringBuilder -import java.util.Locale 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 scala.util.control.NonFatal -import ConsoleAppender._ +import org.apache.logging.log4j.core.{ LogEvent => XLogEvent } +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 { // 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 CursorLeft1000 = "\u001B[1000D" private[sbt] final val CursorDown1 = cursorDown(1) - private[sbt] lazy val terminalWidth = usingTerminal { t => - t.getWidth - } private[this] val showProgressHolder: AtomicBoolean = new AtomicBoolean(false) def setShowProgress(b: Boolean): Unit = showProgressHolder.set(b) def showProgress: Boolean = showProgressHolder.get @@ -293,31 +289,7 @@ object ConsoleAppender { private[sbt] def generateName(): String = "out-" + generateId.incrementAndGet - private[this] def jline1to2CompatMsg = "Found class jline.Terminal, but interface was expected" - - 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 - + private[this] def ansiSupported: Boolean = Terminal.isAnsiSupported } // 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") if (progress.length > 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)(_)) deleteConsoleLines(blankZone + pad) progress.foreach(printProgressLine) @@ -387,7 +359,7 @@ class ConsoleAppender private[ConsoleAppender] ( s" | => ${item.name} ${elapsed}s" } - val width = ConsoleAppender.terminalWidth + val width = Terminal.getWidth val currentLength = info.foldLeft(info.length)(_ + terminalLines(width)(_)) val previousLines = progressLines.getAndSet(info) val prevLength = previousLines.foldLeft(previousLines.length)(_ + terminalLines(width)(_)) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala new file mode 100644 index 000000000..5322b5869 --- /dev/null +++ b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala @@ -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 +} diff --git a/main-actions/src/main/scala/sbt/Console.scala b/main-actions/src/main/scala/sbt/Console.scala index 413fac896..f05a599e0 100644 --- a/main-actions/src/main/scala/sbt/Console.scala +++ b/main-actions/src/main/scala/sbt/Console.scala @@ -9,9 +9,10 @@ package sbt import java.io.File import sbt.internal.inc.{ AnalyzingCompiler, PlainVirtualFile } -import sbt.internal.util.JLine +import sbt.internal.util.Terminal import sbt.util.Logger -import xsbti.compile.{ Inputs, Compilers } +import xsbti.compile.{ Compilers, Inputs } + import scala.util.Try final class Console(compiler: AnalyzingCompiler) { @@ -51,10 +52,7 @@ final class Console(compiler: AnalyzingCompiler) { loader, bindings ) - JLine.usingTerminal { t => - t.init - Run.executeTrapExit(console0, log) - } + Terminal.withRawSystemIn(Run.executeTrapExit(console0, log)) } } diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index bc541a90e..0fe030fb6 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -9,15 +9,15 @@ package sbt import java.nio.file.Paths import sbt.util.Level -import sbt.internal.util.{ AttributeKey, FullReader } +import sbt.internal.util.{ AttributeKey, FullReader, JLine, Terminal } import sbt.internal.util.complete.{ Completion, Completions, DefaultParsers, - History => CHistory, HistoryCommands, Parser, - TokenCompletions + TokenCompletions, + History => CHistory } import sbt.internal.util.Types.{ const, idFun } 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.client.NetworkClient import DefaultParsers._ + import Function.tupled import Command.applyEffect import BasicCommandStrings._ import CommandUtil._ import BasicKeys._ - import java.io.File + import sbt.io.IO import scala.collection.mutable.ListBuffer @@ -372,7 +373,8 @@ object BasicCommands { def oldshell: Command = Command.command(OldShell, Help.more(Shell, OldShellDetailed)) { s => 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 reader = new FullReader(history, s.combinedParser) + val reader = + new FullReader(history, s.combinedParser, JLine.HandleCONT, Terminal.wrappedSystemIn) val line = reader.readLine(prompt) line match { case Some(line) => diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 2419f4747..1de8de473 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -12,19 +12,21 @@ package client import java.io.{ File, IOException } import java.util.UUID 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.util.{ JLine, ConsoleAppender } -import sbt.util.Level -import sbt.io.syntax._ +import sbt.internal.protocol._ +import sbt.internal.util.{ ConsoleAppender, JLine } import sbt.io.IO +import sbt.io.syntax._ +import sbt.protocol._ +import sbt.util.Level 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 => private val channelName = new AtomicReference("_") private val status = new AtomicReference("Ready") diff --git a/main/src/main/scala/sbt/CommandLineUIService.scala b/main/src/main/scala/sbt/CommandLineUIService.scala index e88521268..f2265a75f 100644 --- a/main/src/main/scala/sbt/CommandLineUIService.scala +++ b/main/src/main/scala/sbt/CommandLineUIService.scala @@ -7,7 +7,7 @@ package sbt -import sbt.internal.util.{ JLine, SimpleReader } +import sbt.internal.util.{ SimpleReader, Terminal } trait CommandLineUIService extends InteractionService { 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 diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 7f0f8d890..e6943ce54 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -10,12 +10,11 @@ package sbt import java.io.PrintWriter import java.util.Properties -import jline.TerminalFactory import sbt.internal.{ Aggregation, ShutdownHooks } import sbt.internal.langserver.ErrorCodes import sbt.internal.protocol.JsonRpcResponseError 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.protocol._ 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 // 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. - val shutdownHook = ShutdownHooks.add(() => TerminalFactory.get().restore()) + val shutdownHook = ShutdownHooks.add(Terminal.restore) try { runLoggedLoop(state, state.globalLogging.backing) diff --git a/main/src/main/scala/sbt/internal/ConsoleProject.scala b/main/src/main/scala/sbt/internal/ConsoleProject.scala index 9ea6a14c7..5e8bd9928 100644 --- a/main/src/main/scala/sbt/internal/ConsoleProject.scala +++ b/main/src/main/scala/sbt/internal/ConsoleProject.scala @@ -10,7 +10,7 @@ package internal import sbt.internal.classpath.AlternativeZincUtil import sbt.internal.inc.{ ScalaInstance, ZincLmUtil } -import sbt.internal.util.JLine +import sbt.internal.util.Terminal import sbt.util.Logger import xsbti.compile.ClasspathOptionsUtil @@ -61,7 +61,7 @@ object ConsoleProject { val importString = imports.mkString("", ";\n", ";\n\n") val initCommands = importString + extra - JLine.usingTerminal { _ => + Terminal.withCanonicalIn { // TODO - Hook up dsl classpath correctly... (new Console(compiler))( unit.classpath, diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 7b26e0776..82826c8fa 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -26,7 +26,7 @@ import sbt.internal.io.WatchState import sbt.internal.nio._ import sbt.internal.util.complete.Parser._ 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.Watch.{ Creation, Deletion, ShowOptions, Update } import sbt.nio.file.{ FileAttributes, Glob } @@ -272,11 +272,8 @@ private[sbt] object Continuous extends DeprecatedContinuous { f(s, valid, invalid) } - private[this] def withCharBufferedStdIn[R](f: InputStream => R): R = { - val terminal = JLine.terminal - terminal.init() - terminal.setEchoEnabled(true) - val wrapped = terminal.wrapInIfNeeded(System.in) + private[this] def withCharBufferedStdIn[R](f: InputStream => R): R = Terminal.withRawSystemIn { + val wrapped = Terminal.wrappedSystemIn if (Util.isNonCygwinWindows) { val inputStream: InputStream with AutoCloseable = new InputStream with AutoCloseable { private[this] val buffer = new java.util.LinkedList[Int] diff --git a/main/src/main/scala/sbt/internal/SettingGraph.scala b/main/src/main/scala/sbt/internal/SettingGraph.scala index 77e33c097..ae63dd713 100644 --- a/main/src/main/scala/sbt/internal/SettingGraph.scala +++ b/main/src/main/scala/sbt/internal/SettingGraph.scala @@ -8,13 +8,13 @@ package sbt package internal -import sbt.internal.util.{ JLine } import sbt.util.Show - 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 object SettingGraph { @@ -82,7 +82,7 @@ object Graph { // [info] | // [info] +-quux 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 def limitLine(s: String): String = if (s.length > maxColumn) s.slice(0, maxColumn - 2) + ".."