Files
sbt/util/complete/LineReader.scala
T
Jason Zaugg 304f3d4ca6 Fix #552 Compensate for JLine's absent EOF detection.
In the unsupported terminal mode, JLine treats a broken
stdin as an endless stream of empty lines. This is problematic
for idea-sbt-plugin: if the IntelliJ process is forcibly killed
and leaves the child SBT process running, it consumes considerable
CPU processing these.

Patching JLine itself would be the cleanest solution (the change
has already been applied to JLine 2), but I've shied away from that
and instead wrapped the InputStream that is read by JLine to
intercept the result of -1 from read(). When this happens, the
flat `inputEof` is set to true.
2012-12-12 15:46:40 -05:00

141 lines
4.0 KiB
Scala

/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package sbt
import jline.{ConsoleReader, History}
import java.io.{File, InputStream, PrintWriter}
import complete.Parser
import java.util.concurrent.atomic.AtomicBoolean
abstract class JLine extends LineReader
{
protected[this] val handleCONT: Boolean
protected[this] val reader: ConsoleReader
/** Is the input stream at EOF? Compensates for absent EOF detection in JLine's UnsupportedTerminal. */
protected[this] val inputEof = new AtomicBoolean(false)
protected[this] val historyPath: Option[File]
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 readLineWithHistory(prompt: String, mask: Option[Char]): String =
historyPath match
{
case None => readLineDirect(prompt, mask)
case Some(file) =>
val h = reader.getHistory
JLine.loadHistory(h, file)
try { readLineDirect(prompt, mask) }
finally { JLine.saveHistory(h, file) }
}
private[this] def readLineDirect(prompt: String, mask: Option[Char]): 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 =
{
val line = mask match {
case Some(m) => reader.readLine(prompt, m)
case None => reader.readLine(prompt)
}
if (inputEof.get) null else line
}
private[this] def resume()
{
jline.Terminal.resetTerminal
JLine.terminal.disableEcho()
reader.drawLine()
reader.flushConsole()
}
}
private object JLine
{
// When calling this, ensure that enableEcho has been or will be called.
// getTerminal will initialize the terminal to disable echo.
private def terminal = jline.Terminal.getTerminal
private def withTerminal[T](f: jline.Terminal => T): T =
synchronized
{
val t = terminal
t.synchronized { f(t) }
}
/** For accessing the JLine Terminal object.
* This ensures synchronized access as well as re-enabling echo after getting the Terminal. */
def usingTerminal[T](f: jline.Terminal => T): T =
withTerminal { t =>
t.enableEcho()
f(t)
}
def createReader() =
usingTerminal { t =>
val cr = new ConsoleReader
cr.setBellEnabled(false)
cr
}
def withJLine[T](action: => T): T =
withTerminal { t =>
t.disableEcho()
try { action }
finally { t.enableEcho() }
}
private[sbt] def loadHistory(h: History, file: File)
{
h.setMaxSize(MaxHistorySize)
if(file.isFile) IO.reader(file)( h.load )
}
private[sbt] def saveHistory(h: History, file: File): Unit =
Using.fileWriter()(file) { writer =>
val out = new PrintWriter(writer, false)
h.setOutput(out)
h.flushBuffer()
out.close()
h.setOutput(null)
}
def simple(historyPath: Option[File], handleCONT: Boolean = HandleCONT): SimpleReader = new SimpleReader(historyPath, handleCONT)
val MaxHistorySize = 500
val HandleCONT = !java.lang.Boolean.getBoolean("sbt.disable.cont") && Signals.supported(Signals.CONT)
}
trait LineReader
{
def readLine(prompt: String, mask: Option[Char] = None): Option[String]
}
final class FullReader(val historyPath: Option[File], complete: Parser[_], val handleCONT: Boolean = JLine.HandleCONT) extends JLine
{
protected[this] val reader =
{
val cr = new ConsoleReader
if (!cr.getTerminal.isSupported) {
val input = cr.getInput
cr.setInput(new InputStream {
def read(): Int = {
val c = input.read()
if (c == -1) inputEof.set(true)
c
}
})
}
cr.setBellEnabled(false)
sbt.complete.JLineCompletion.installCustomCompletor(cr, complete)
cr
}
}
class SimpleReader private[sbt] (val historyPath: Option[File], val handleCONT: Boolean) extends JLine
{
protected[this] val reader = JLine.createReader()
}
object SimpleReader extends SimpleReader(None, JLine.HandleCONT)