diff options
Diffstat (limited to 'src/dotty/tools/dotc/repl/ammonite/Utils.scala')
-rw-r--r-- | src/dotty/tools/dotc/repl/ammonite/Utils.scala | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/src/dotty/tools/dotc/repl/ammonite/Utils.scala b/src/dotty/tools/dotc/repl/ammonite/Utils.scala new file mode 100644 index 000000000..64a2c1476 --- /dev/null +++ b/src/dotty/tools/dotc/repl/ammonite/Utils.scala @@ -0,0 +1,169 @@ +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)) + } + } + } +} |