Merge pull request #31 from sbt/wip/thread

Inject thread.sleep, which allows thread interruption during readLine
This commit is contained in:
eugene yokota 2016-03-29 15:53:46 -04:00
commit d84f82d8d4
2 changed files with 44 additions and 20 deletions

View File

@ -2,7 +2,7 @@ import Dependencies._
import Util._ import Util._
import com.typesafe.tools.mima.core._, ProblemFilters._ import com.typesafe.tools.mima.core._, ProblemFilters._
def baseVersion: String = "0.1.0-M8" def baseVersion: String = "0.1.0-M9"
def internalPath = file("internal") def internalPath = file("internal")
def commonSettings: Seq[Setting[_]] = Seq( def commonSettings: Seq[Setting[_]] = Seq(

View File

@ -5,9 +5,11 @@ package sbt.internal.util
import jline.console.ConsoleReader import jline.console.ConsoleReader
import jline.console.history.{ FileHistory, MemoryHistory } 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 complete.Parser
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import scala.concurrent.duration.Duration
import scala.annotation.tailrec
abstract class JLine extends LineReader { abstract class JLine extends LineReader {
protected[this] val handleCONT: Boolean protected[this] val handleCONT: Boolean
@ -15,13 +17,12 @@ abstract class JLine extends LineReader {
def readLine(prompt: String, mask: Option[Char] = None) = JLine.withJLine { unsynchronizedReadLine(prompt, mask) } def readLine(prompt: String, mask: Option[Char] = None) = JLine.withJLine { unsynchronizedReadLine(prompt, mask) }
private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]) = private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]): Option[String] =
readLineWithHistory(prompt, mask) match { readLineWithHistory(prompt, mask) map { x =>
case null => None x.trim
case x => Some(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 { reader.getHistory match {
case fh: FileHistory => case fh: FileHistory =>
try { readLineDirect(prompt, mask) } try { readLineDirect(prompt, mask) }
@ -29,17 +30,17 @@ abstract class JLine extends LineReader {
case _ => readLineDirect(prompt, mask) 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) if (handleCONT)
Signals.withHandler(() => resume(), signal = Signals.CONT)(() => readLineDirectRaw(prompt, mask)) Signals.withHandler(() => resume(), signal = Signals.CONT)(() => readLineDirectRaw(prompt, mask))
else else
readLineDirectRaw(prompt, mask) 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) val newprompt = handleMultilinePrompt(prompt)
mask match { mask match {
case Some(m) => reader.readLine(newprompt, m) case Some(m) => Option(reader.readLine(newprompt, m))
case None => reader.readLine(newprompt) case None => Option(reader.readLine(newprompt))
} }
} }
@ -95,10 +96,14 @@ private[sbt] object JLine {
t.restore t.restore
f(t) f(t)
} }
def createReader(): ConsoleReader = createReader(None) def createReader(): ConsoleReader = createReader(None, true)
def createReader(historyPath: Option[File]): ConsoleReader = def createReader(historyPath: Option[File], injectThreadSleep: Boolean): ConsoleReader =
usingTerminal { t => 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.setExpandEvents(false) // https://issues.scala-lang.org/browse/SI-7650
cr.setBellEnabled(false) cr.setBellEnabled(false)
val h = historyPath match { val h = historyPath match {
@ -116,25 +121,44 @@ private[sbt] object JLine {
finally { t.restore } 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 MaxHistorySize = 500
val HandleCONT = !java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT) 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 { trait LineReader {
def readLine(prompt: String, mask: Option[Char] = None): Option[String] 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 = protected[this] val reader =
{ {
val cr = JLine.createReader(historyPath) val cr = JLine.createReader(historyPath, injectThreadSleep)
sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete) sbt.internal.util.complete.JLineCompletion.installCustomCompletor(cr, complete)
cr cr
} }
} }
class SimpleReader private[sbt] (historyPath: Option[File], val handleCONT: Boolean) extends JLine { class SimpleReader private[sbt] (historyPath: Option[File], val handleCONT: Boolean, val injectThreadSleep: Boolean) extends JLine {
protected[this] val reader = JLine.createReader(historyPath) protected[this] val reader = JLine.createReader(historyPath, injectThreadSleep)
} }
object SimpleReader extends SimpleReader(None, JLine.HandleCONT) object SimpleReader extends SimpleReader(None, JLine.HandleCONT, true)