package dotty.tools package dotc package repl package ammonite package terminal package filters import ammonite.terminal.FilterTools._ import ammonite.terminal.LazyList._ import ammonite.terminal.SpecialKeys._ import ammonite.terminal.Filter import ammonite.terminal._ /** * Filters for simple operation of a terminal: cursor-navigation * (including with all the modifier keys), enter/ctrl-c-exit, etc. */ object BasicFilters { def all = Filter.merge( navFilter, exitFilter, enterFilter, clearFilter, //loggingFilter, typingFilter ) def injectNewLine(b: Vector[Char], c: Int, rest: LazyList[Int], indent: Int = 0) = { val (first, last) = b.splitAt(c) TermState(rest, (first :+ '\n') ++ last ++ Vector.fill(indent)(' '), c + 1 + indent) } def navFilter = Filter.merge( Case(Up)((b, c, m) => moveUp(b, c, m.width)), Case(Down)((b, c, m) => moveDown(b, c, m.width)), Case(Right)((b, c, m) => (b, c + 1)), Case(Left)((b, c, m) => (b, c - 1)) ) def tabColumn(indent: Int, b: Vector[Char], c: Int, rest: LazyList[Int]) = { val (chunks, chunkStarts, chunkIndex) = FilterTools.findChunks(b, c) val chunkCol = c - chunkStarts(chunkIndex) val spacesToInject = indent - (chunkCol % indent) val (lhs, rhs) = b.splitAt(c) TS(rest, lhs ++ Vector.fill(spacesToInject)(' ') ++ rhs, c + spacesToInject) } def tabFilter(indent: Int): Filter = Filter("tabFilter") { case TS(9 ~: rest, b, c, _) => tabColumn(indent, b, c, rest) } def loggingFilter: Filter = Filter("loggingFilter") { case TS(Ctrl('q') ~: rest, b, c, _) => println("Char Display Mode Enabled! Ctrl-C to exit") var curr = rest while (curr.head != 3) { println("Char " + curr.head) curr = curr.tail } TS(curr, b, c) } def typingFilter: Filter = Filter("typingFilter") { case TS(p"\u001b[3~$rest", b, c, _) => // Debug("fn-delete") val (first, last) = b.splitAt(c) TS(rest, first ++ last.drop(1), c) case TS(127 ~: rest, b, c, _) => // Backspace val (first, last) = b.splitAt(c) TS(rest, first.dropRight(1) ++ last, c - 1) case TS(char ~: rest, b, c, _) => // Debug("NORMAL CHAR " + char) val (first, last) = b.splitAt(c) TS(rest, (first :+ char.toChar) ++ last, c + 1) } def doEnter(b: Vector[Char], c: Int, rest: LazyList[Int]) = { val (chunks, chunkStarts, chunkIndex) = FilterTools.findChunks(b, c) if (chunkIndex == chunks.length - 1) Result(b.mkString) else injectNewLine(b, c, rest) } def enterFilter: Filter = Filter("enterFilter") { case TS(13 ~: rest, b, c, _) => doEnter(b, c, rest) // Enter case TS(10 ~: rest, b, c, _) => doEnter(b, c, rest) // Enter case TS(10 ~: 13 ~: rest, b, c, _) => doEnter(b, c, rest) // Enter case TS(13 ~: 10 ~: rest, b, c, _) => doEnter(b, c, rest) // Enter } def exitFilter: Filter = Filter("exitFilter") { case TS(Ctrl('c') ~: rest, b, c, _) => Result("") case TS(Ctrl('d') ~: rest, b, c, _) => // only exit if the line is empty, otherwise, behave like // "delete" (i.e. delete one char to the right) if (b.isEmpty) Exit else { val (first, last) = b.splitAt(c) TS(rest, first ++ last.drop(1), c) } case TS(-1 ~: rest, b, c, _) => Exit // java.io.Reader.read() produces -1 on EOF } def clearFilter: Filter = Filter("clearFilter") { case TS(Ctrl('l') ~: rest, b, c, _) => ClearScreen(TS(rest, b, c)) } def moveStart(b: Vector[Char], c: Int, w: Int) = { val (_, chunkStarts, chunkIndex) = findChunks(b, c) val currentColumn = (c - chunkStarts(chunkIndex)) % w b -> (c - currentColumn) } def moveEnd(b: Vector[Char], c: Int, w: Int) = { val (chunks, chunkStarts, chunkIndex) = findChunks(b, c) val currentColumn = (c - chunkStarts(chunkIndex)) % w val c1 = chunks.lift(chunkIndex + 1) match { case Some(next) => val boundary = chunkStarts(chunkIndex + 1) - 1 if ((boundary - c) > (w - currentColumn)) { val delta= w - currentColumn c + delta } else boundary case None => c + 1 * 9999 } b -> c1 } def moveUpDown( b: Vector[Char], c: Int, w: Int, boundaryOffset: Int, nextChunkOffset: Int, checkRes: Int, check: (Int, Int) => Boolean, isDown: Boolean ) = { val (chunks, chunkStarts, chunkIndex) = findChunks(b, c) val offset = chunkStarts(chunkIndex + boundaryOffset) if (check(checkRes, offset)) checkRes else chunks.lift(chunkIndex + nextChunkOffset) match { case None => c + nextChunkOffset * 9999 case Some(next) => val boundary = chunkStarts(chunkIndex + boundaryOffset) val currentColumn = (c - chunkStarts(chunkIndex)) % w if (isDown) boundary + math.min(currentColumn, next) else boundary + math.min(currentColumn - next % w, 0) - 1 } } def moveUp(b: Vector[Char], c: Int, w: Int) = { b -> moveUpDown(b, c, w, 0, -1, c - w, _ > _, false) } def moveDown(b: Vector[Char], c: Int, w: Int) = { b -> moveUpDown(b, c, w, 1, 1, c + w, _ <= _, true) } }