aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/repl/ammonite/Utils.scala
blob: 64a2c14765b7f3b2182fe6f618f22fa3bab93777 (plain) (tree)
1
2
3
4
5
6
7
8
9




                         
                                                        


                               

                                                                              







                                                                              




                                                          

                                                                                  

 
                               



































                                                               

                




                                         
            


















































                                                                                              
                                                                          























                                                                        
 

                                                         


                 

                                                           
 

                                                                                   
                                         
                                 
              





                                                              
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))
      }
    }
  }
}