package dotty.tools
package dotc
package repl
package ammonite.terminal
import java.io.{FileOutputStream, Writer, File => JFile}
import scala.annotation.tailrec
/**
* Prints stuff to an ad-hoc logging file when running the repl or terminal in
* development mode
*
* Very handy for the common case where you're debugging terminal interactions
* and cannot use `println` because it will stomp all over your already messed
* up terminal state and block debugging. With [[Debug]], you can have a
* separate terminal open tailing the log file and log as verbosely as you
* want without affecting the primary terminal you're using to interact with
* Ammonite.
*/
object Debug {
lazy val debugOutput =
new FileOutputStream(new JFile("terminal/target/log"))
def apply(s: Any) =
if (System.getProperty("ammonite-sbt-build") == "true")
debugOutput.write((System.currentTimeMillis() + "\t\t" + s + "\n").getBytes)
}
class AnsiNav(output: Writer) {
def control(n: Int, c: Char) = output.write(s"\033[" + n + c)
/**
* Move up `n` squares
*/
def up(n: Int) = if (n == 0) "" else control(n, 'A')
/**
* Move down `n` squares
*/
def down(n: Int) = if (n == 0) "" else control(n, 'B')
/**
* Move right `n` squares
*/
def right(n: Int) = if (n == 0) "" else control(n, 'C')
/**
* Move left `n` squares
*/
def left(n: Int) = if (n == 0) "" else control(n, 'D')
/**
* Clear the screen
*
* n=0: clear from cursor to end of screen
* n=1: clear from cursor to start of screen
* n=2: clear entire screen
*/
def clearScreen(n: Int) = control(n, 'J')
/**
* Clear the current line
*
* n=0: clear from cursor to end of line
* n=1: clear from cursor to start of line
* n=2: clear entire line
*/
def clearLine(n: Int) = control(n, 'K')
}
object AnsiNav {
val resetUnderline = "\u001b[24m"
val resetForegroundColor = "\u001b[39m"
val resetBackgroundColor = "\u001b[49m"
}
object TTY {
// Prefer standard tools. Not sure why we need to do this, but for some
// reason the version installed by gnu-coreutils blows up sometimes giving
// "unable to perform all requested operations"
val pathedTput = if (new java.io.File("/usr/bin/tput").exists()) "/usr/bin/tput" else "tput"
val pathedStty = if (new java.io.File("/bin/stty").exists()) "/bin/stty" else "stty"
def consoleDim(s: String) = {
import sys.process._
Seq("bash", "-c", s"$pathedTput $s 2> /dev/tty").!!.trim.toInt
}
def init() = {
stty("-a")
val width = consoleDim("cols")
val height = consoleDim("lines")
// Debug("Initializing, Width " + width)
// Debug("Initializing, Height " + height)
val initialConfig = stty("-g").trim
stty("-icanon min 1 -icrnl -inlcr -ixon")
sttyFailTolerant("dsusp undef")
stty("-echo")
stty("intr undef")
// Debug("")
(width, height, initialConfig)
}
private def sttyCmd(s: String) = {
import sys.process._
Seq("bash", "-c", s"$pathedStty $s < /dev/tty"): ProcessBuilder
}
def stty(s: String) =
sttyCmd(s).!!
/*
* Executes a stty command for which failure is expected, hence the return
* status can be non-null and errors are ignored.
* This is appropriate for `stty dsusp undef`, since it's unsupported on Linux
* (http://man7.org/linux/man-pages/man3/termios.3.html).
*/
def sttyFailTolerant(s: String) =
sttyCmd(s ++ " 2> /dev/null").!
def restore(initialConfig: String) = {
stty(initialConfig)
}
}
/**
* A truly-lazy implementation of scala.Stream
*/
case class LazyList[T](headThunk: () => T, tailThunk: () => LazyList[T]) {
var rendered = false
lazy val head = {
rendered = true
headThunk()
}
lazy val tail = tailThunk()
def dropPrefix(prefix: Seq[T]) = {
@tailrec def rec(n: Int, l: LazyList[T]): Option[LazyList[T]] = {
if (n >= prefix.length) Some(l)
else if (prefix(n) == l.head) rec(n + 1, l.tail)
else None
}
rec(0, this)
}
override def toString = {
@tailrec def rec(l: LazyList[T], res: List[T]): List[T] = {
if (l.rendered) rec(l.tailThunk(), l.head :: res)
else res
}
s"LazyList(${(rec(this, Nil).reverse ++ Seq("...")).mkString(",")})"
}
def ~:(other: => T) = LazyList(() => other, () => this)
}
object LazyList {
object ~: {
def unapply[T](x: LazyList[T]) = Some((x.head, x.tail))
}
def continually[T](t: => T): LazyList[T] = LazyList(() => t, () =>continually(t))
implicit class CS(ctx: StringContext) {
val base = ctx.parts.mkString
object p {
def unapply(s: LazyList[Int]): Option[LazyList[Int]] = {
s.dropPrefix(base.map(_.toInt))
}
}
}
}