aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools/dotc/repl/ammonite/Terminal.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/dotty/tools/dotc/repl/ammonite/Terminal.scala')
-rw-r--r--src/dotty/tools/dotc/repl/ammonite/Terminal.scala320
1 files changed, 0 insertions, 320 deletions
diff --git a/src/dotty/tools/dotc/repl/ammonite/Terminal.scala b/src/dotty/tools/dotc/repl/ammonite/Terminal.scala
deleted file mode 100644
index 4b18b38e3..000000000
--- a/src/dotty/tools/dotc/repl/ammonite/Terminal.scala
+++ /dev/null
@@ -1,320 +0,0 @@
-package dotty.tools
-package dotc
-package repl
-package ammonite
-package terminal
-
-import scala.annotation.tailrec
-import scala.collection.mutable
-
-/**
- * The core logic around a terminal; it defines the base `filters` API
- * through which anything (including basic cursor-navigation and typing)
- * interacts with the terminal.
- *
- * Maintains basic invariants, such as "cursor should always be within
- * the buffer", and "ansi terminal should reflect most up to date TermState"
- */
-object Terminal {
-
- /**
- * Computes how tall a line of text is when wrapped at `width`.
- *
- * Even 0-character lines still take up one row!
- *
- * width = 2
- * 0 -> 1
- * 1 -> 1
- * 2 -> 1
- * 3 -> 2
- * 4 -> 2
- * 5 -> 3
- */
- def fragHeight(length: Int, width: Int) = math.max(1, (length - 1) / width + 1)
-
- def splitBuffer(buffer: Vector[Char]) = {
- val frags = mutable.Buffer.empty[Int]
- frags.append(0)
- for(c <- buffer){
- if (c == '\n') frags.append(0)
- else frags(frags.length - 1) = frags.last + 1
- }
- frags
- }
- def calculateHeight(buffer: Vector[Char],
- width: Int,
- prompt: String): Seq[Int] = {
- val rowLengths = splitBuffer(buffer)
-
- calculateHeight0(rowLengths, width - prompt.length)
- }
-
- /**
- * Given a buffer with characters and newlines, calculates how high
- * the buffer is and where the cursor goes inside of it.
- */
- def calculateHeight0(rowLengths: Seq[Int],
- width: Int): Seq[Int] = {
- val fragHeights =
- rowLengths
- .inits
- .toVector
- .reverse // We want shortest-to-longest, inits gives longest-to-shortest
- .filter(_.nonEmpty) // Without the first empty prefix
- .map{ x =>
- fragHeight(
- // If the frag barely fits on one line, give it
- // an extra spot for the cursor on the next line
- x.last + 1,
- width
- )
- }
-// Debug("fragHeights " + fragHeights)
- fragHeights
- }
-
- def positionCursor(cursor: Int,
- rowLengths: Seq[Int],
- fragHeights: Seq[Int],
- width: Int) = {
- var leftoverCursor = cursor
- // Debug("leftoverCursor " + leftoverCursor)
- var totalPreHeight = 0
- var done = false
- // Don't check if the cursor exceeds the last chunk, because
- // even if it does there's nowhere else for it to go
- for(i <- 0 until rowLengths.length -1 if !done) {
- // length of frag and the '\n' after it
- val delta = rowLengths(i) + 1
- // Debug("delta " + delta)
- val nextCursor = leftoverCursor - delta
- if (nextCursor >= 0) {
- // Debug("nextCursor " + nextCursor)
- leftoverCursor = nextCursor
- totalPreHeight += fragHeights(i)
- }else done = true
- }
-
- val cursorY = totalPreHeight + leftoverCursor / width
- val cursorX = leftoverCursor % width
-
- (cursorY, cursorX)
- }
-
-
- type Action = (Vector[Char], Int) => (Vector[Char], Int)
- type MsgAction = (Vector[Char], Int) => (Vector[Char], Int, String)
-
-
- def noTransform(x: Vector[Char], i: Int) = (Ansi.Str.parse(x), i)
- /**
- * Blockingly reads a line from the given input stream and returns it.
- *
- * @param prompt The prompt to display when requesting input
- * @param reader The input-stream where characters come in, e.g. System.in
- * @param writer The output-stream where print-outs go, e.g. System.out
- * @param filters A set of actions that can be taken depending on the input,
- * @param displayTransform code to manipulate the display of the buffer and
- * cursor, without actually changing the logical
- * values inside them.
- */
- def readLine(prompt: Prompt,
- reader: java.io.Reader,
- writer: java.io.Writer,
- filters: Filter,
- displayTransform: (Vector[Char], Int) => (Ansi.Str, Int) = noTransform)
- : Option[String] = {
-
- /**
- * Erases the previous line and re-draws it with the new buffer and
- * cursor.
- *
- * Relies on `ups` to know how "tall" the previous line was, to go up
- * and erase that many rows in the console. Performs a lot of horrific
- * math all over the place, incredibly prone to off-by-ones, in order
- * to at the end of the day position the cursor in the right spot.
- */
- def redrawLine(buffer: Ansi.Str,
- cursor: Int,
- ups: Int,
- rowLengths: Seq[Int],
- fullPrompt: Boolean = true,
- newlinePrompt: Boolean = false) = {
-
-
- // Enable this in certain cases (e.g. cursor near the value you are
- // interested into) see what's going on with all the ansi screen-cursor
- // movement
- def debugDelay() = if (false){
- Thread.sleep(200)
- writer.flush()
- }
-
-
- val promptLine =
- if (fullPrompt) prompt.full
- else prompt.lastLine
-
- val promptWidth = if(newlinePrompt) 0 else prompt.lastLine.length
- val actualWidth = width - promptWidth
-
- ansi.up(ups)
- ansi.left(9999)
- ansi.clearScreen(0)
- writer.write(promptLine.toString)
- if (newlinePrompt) writer.write("\n")
-
- // I'm not sure why this is necessary, but it seems that without it, a
- // cursor that "barely" overshoots the end of a line, at the end of the
- // buffer, does not properly wrap and ends up dangling off the
- // right-edge of the terminal window!
- //
- // This causes problems later since the cursor is at the wrong X/Y,
- // confusing the rest of the math and ending up over-shooting on the
- // `ansi.up` calls, over-writing earlier lines. This prints a single
- // space such that instead of dangling it forces the cursor onto the
- // next line for-realz. If it isn't dangling the extra space is a no-op
- val lineStuffer = ' '
- // Under `newlinePrompt`, we print the thing almost-verbatim, since we
- // want to avoid breaking code by adding random indentation. If not, we
- // are guaranteed that the lines are short, so we can indent the newlines
- // without fear of wrapping
- val newlineReplacement =
- if (newlinePrompt) {
-
- Array(lineStuffer, '\n')
- } else {
- val indent = " " * prompt.lastLine.length
- Array('\n', indent:_*)
- }
-
- writer.write(
- buffer.render.flatMap{
- case '\n' => newlineReplacement
- case x => Array(x)
- }.toArray
- )
- writer.write(lineStuffer)
-
- val fragHeights = calculateHeight0(rowLengths, actualWidth)
- val (cursorY, cursorX) = positionCursor(
- cursor,
- rowLengths,
- fragHeights,
- actualWidth
- )
- ansi.up(fragHeights.sum - 1)
- ansi.left(9999)
- ansi.down(cursorY)
- ansi.right(cursorX)
- if (!newlinePrompt) ansi.right(prompt.lastLine.length)
-
- writer.flush()
- }
-
- @tailrec
- def readChar(lastState: TermState, ups: Int, fullPrompt: Boolean = true): Option[String] = {
- val moreInputComing = reader.ready()
-
- lazy val (transformedBuffer0, cursorOffset) = displayTransform(
- lastState.buffer,
- lastState.cursor
- )
-
- lazy val transformedBuffer = transformedBuffer0 ++ lastState.msg
- lazy val lastOffsetCursor = lastState.cursor + cursorOffset
- lazy val rowLengths = splitBuffer(
- lastState.buffer ++ lastState.msg.plainText
- )
- val narrowWidth = width - prompt.lastLine.length
- val newlinePrompt = rowLengths.exists(_ >= narrowWidth)
- val promptWidth = if(newlinePrompt) 0 else prompt.lastLine.length
- val actualWidth = width - promptWidth
- val newlineUp = if (newlinePrompt) 1 else 0
- if (!moreInputComing) redrawLine(
- transformedBuffer,
- lastOffsetCursor,
- ups,
- rowLengths,
- fullPrompt,
- newlinePrompt
- )
-
- lazy val (oldCursorY, _) = positionCursor(
- lastOffsetCursor,
- rowLengths,
- calculateHeight0(rowLengths, actualWidth),
- actualWidth
- )
-
- def updateState(s: LazyList[Int],
- b: Vector[Char],
- c: Int,
- msg: Ansi.Str): (Int, TermState) = {
-
- val newCursor = math.max(math.min(c, b.length), 0)
- val nextUps =
- if (moreInputComing) ups
- else oldCursorY + newlineUp
-
- val newState = TermState(s, b, newCursor, msg)
-
- (nextUps, newState)
- }
- // `.get` because we assume that *some* filter is going to match each
- // character, even if only to dump the character to the screen. If nobody
- // matches the character then we can feel free to blow up
- filters.op(TermInfo(lastState, actualWidth)).get match {
- case Printing(TermState(s, b, c, msg), stdout) =>
- writer.write(stdout)
- val (nextUps, newState) = updateState(s, b, c, msg)
- readChar(newState, nextUps)
-
- case TermState(s, b, c, msg) =>
- val (nextUps, newState) = updateState(s, b, c, msg)
- readChar(newState, nextUps, false)
-
- case Result(s) =>
- redrawLine(
- transformedBuffer, lastState.buffer.length,
- oldCursorY + newlineUp, rowLengths, false, newlinePrompt
- )
- writer.write(10)
- writer.write(13)
- writer.flush()
- Some(s)
- case ClearScreen(ts) =>
- ansi.clearScreen(2)
- ansi.up(9999)
- ansi.left(9999)
- readChar(ts, ups)
- case Exit =>
- None
- }
- }
-
- lazy val ansi = new AnsiNav(writer)
- lazy val (width, _, initialConfig) = TTY.init()
- try {
- readChar(TermState(LazyList.continually(reader.read()), Vector.empty, 0, ""), 0)
- }finally{
-
- // Don't close these! Closing these closes stdin/stdout,
- // which seems to kill the entire program
-
- // reader.close()
- // writer.close()
- TTY.stty(initialConfig)
- }
- }
-}
-object Prompt {
- implicit def construct(prompt: String): Prompt = {
- val parsedPrompt = Ansi.Str.parse(prompt)
- val index = parsedPrompt.plainText.lastIndexOf('\n')
- val (_, last) = parsedPrompt.splitAt(index+1)
- Prompt(parsedPrompt, last)
- }
-}
-
-case class Prompt(full: Ansi.Str, lastLine: Ansi.Str)