mirror of
https://github.com/sbt/sbt.git
synced 2026-10-06 18:14:04 +02:00
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.
141 lines
4.0 KiB
Scala
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)
|
|
|