From 972450db5d4ccdf9f8a6bced572b15465c0a993b Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 18 Mar 2016 02:38:06 -0400 Subject: [PATCH 1/2] Refactor nulls to Option --- .../scala/sbt/internal/util/LineReader.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 e72341a23..ed68630bd 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 @@ -15,13 +15,12 @@ abstract class JLine extends LineReader { def readLine(prompt: String, mask: Option[Char] = None) = JLine.withJLine { unsynchronizedReadLine(prompt, mask) } - private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]) = - readLineWithHistory(prompt, mask) match { - case null => None - case x => Some(x.trim) + private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]): Option[String] = + readLineWithHistory(prompt, mask) map { x => + x.trim } - private[this] def readLineWithHistory(prompt: String, mask: Option[Char]): String = + private[this] def readLineWithHistory(prompt: String, mask: Option[Char]): Option[String] = reader.getHistory match { case fh: FileHistory => try { readLineDirect(prompt, mask) } @@ -29,17 +28,17 @@ abstract class JLine extends LineReader { case _ => readLineDirect(prompt, mask) } - private[this] def readLineDirect(prompt: String, mask: Option[Char]): String = + private[this] def readLineDirect(prompt: String, mask: Option[Char]): Option[String] = if (handleCONT) Signals.withHandler(() => resume(), signal = Signals.CONT)(() => readLineDirectRaw(prompt, mask)) else readLineDirectRaw(prompt, mask) - private[this] def readLineDirectRaw(prompt: String, mask: Option[Char]): String = + private[this] def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] = { val newprompt = handleMultilinePrompt(prompt) mask match { - case Some(m) => reader.readLine(newprompt, m) - case None => reader.readLine(newprompt) + case Some(m) => Option(reader.readLine(newprompt, m)) + case None => Option(reader.readLine(newprompt)) } } From 1dfd9db2c7ac6d86c343aa764b386076924e408f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 18 Mar 2016 21:58:40 -0400 Subject: [PATCH 2/2] Inject Thread.sleep periodically during read() to allow thread interruption --- .../scala/sbt/internal/util/LineReader.scala | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) 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 ed68630bd..93552b518 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 @@ -5,9 +5,11 @@ package sbt.internal.util import jline.console.ConsoleReader import jline.console.history.{ FileHistory, MemoryHistory } -import java.io.{ File, InputStream, PrintWriter } +import java.io.{ File, InputStream, PrintWriter, FileInputStream, FileDescriptor, FilterInputStream } import complete.Parser import java.util.concurrent.atomic.AtomicBoolean +import scala.concurrent.duration.Duration +import scala.annotation.tailrec abstract class JLine extends LineReader { protected[this] val handleCONT: Boolean @@ -94,10 +96,14 @@ private[sbt] object JLine { t.restore f(t) } - def createReader(): ConsoleReader = createReader(None) - def createReader(historyPath: Option[File]): ConsoleReader = + def createReader(): ConsoleReader = createReader(None, true) + def createReader(historyPath: Option[File], injectThreadSleep: Boolean): ConsoleReader = usingTerminal { t => - val cr = new ConsoleReader + val cr = if (injectThreadSleep) { + val originalIn = new FileInputStream(FileDescriptor.in) + val in = new InputStreamWrapper(originalIn, Duration("50 ms")) + new ConsoleReader(in, System.out) + } else new ConsoleReader cr.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650 cr.setBellEnabled(false) val h = historyPath match { @@ -115,25 +121,44 @@ private[sbt] object JLine { finally { t.restore } } - def simple(historyPath: Option[File], handleCONT: Boolean = HandleCONT): SimpleReader = new SimpleReader(historyPath, handleCONT) + def simple( + historyPath: Option[File], + handleCONT: Boolean = HandleCONT, + injectThreadSleep: Boolean = true + ): SimpleReader = new SimpleReader(historyPath, handleCONT, injectThreadSleep) val MaxHistorySize = 500 val HandleCONT = !java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT) } +private[sbt] class InputStreamWrapper(is: InputStream, val poll: Duration) extends FilterInputStream(is) { + @tailrec + final override def read(): Int = + if (is.available() != 0) is.read() + else { + Thread.sleep(poll.toMillis) + read() + } +} + trait LineReader { def readLine(prompt: String, mask: Option[Char] = None): Option[String] } -final class FullReader(historyPath: Option[File], complete: Parser[_], val handleCONT: Boolean = JLine.HandleCONT) extends JLine { +final class FullReader( + historyPath: Option[File], + complete: Parser[_], + val handleCONT: Boolean = JLine.HandleCONT, + val injectThreadSleep: Boolean = true +) extends JLine { protected[this] val reader = { - val cr = JLine.createReader(historyPath) + val cr = JLine.createReader(historyPath, injectThreadSleep) sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete) cr } } -class SimpleReader private[sbt] (historyPath: Option[File], val handleCONT: Boolean) extends JLine { - protected[this] val reader = JLine.createReader(historyPath) +class SimpleReader private[sbt] (historyPath: Option[File], val handleCONT: Boolean, val injectThreadSleep: Boolean) extends JLine { + protected[this] val reader = JLine.createReader(historyPath, injectThreadSleep) } -object SimpleReader extends SimpleReader(None, JLine.HandleCONT) +object SimpleReader extends SimpleReader(None, JLine.HandleCONT, true)