aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala
blob: faa97c348cdb1ea949f42d3dfc69ff86013f183b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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)
  }
}