summaryrefslogtreecommitdiff
path: root/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala
blob: f0fce13fe82f7cd5a913b6b6b76996c7a5dbe533 (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
/** NSC -- new Scala compiler
  *
  * Copyright 2005-2015 LAMP/EPFL
  * @author Stepan Koltsov
  * @author Adriaan Moors
  */

package scala.tools.nsc.interpreter.jline

import java.util.{Collection => JCollection, List => JList}

import _root_.jline.{console => jconsole}
import jconsole.completer.{Completer, ArgumentCompleter}
import jconsole.history.{History => JHistory}


import scala.tools.nsc.interpreter
import scala.tools.nsc.interpreter.Completion
import scala.tools.nsc.interpreter.Completion.Candidates
import scala.tools.nsc.interpreter.session.History

/**
 * Reads from the console using JLine.
 *
 * Eagerly instantiates all relevant JLine classes, so that we can detect linkage errors on `new JLineReader` and retry.
 */
class InteractiveReader(completer: () => Completion) extends interpreter.InteractiveReader {
  val interactive = true

  val history: History = new JLineHistory.JLineFileHistory()

  private val consoleReader = {
    val reader = new JLineConsoleReader()

    reader setPaginationEnabled interpreter.`package`.isPaged

    // ASAP
    reader setExpandEvents false

    reader setHistory history.asInstanceOf[JHistory]

    reader
  }

  private[this] var _completion: Completion = interpreter.NoCompletion
  def completion: Completion = _completion

  override def postInit() = {
    _completion = completer()

    consoleReader.initCompletion(completion)
  }

  def reset()                     = consoleReader.getTerminal().reset()
  def redrawLine()                = consoleReader.redrawLineAndFlush()
  def readOneLine(prompt: String) = consoleReader.readLine(prompt)
  def readOneKey(prompt: String)  = consoleReader.readOneKey(prompt)
}

// implements a jline interface
private class JLineConsoleReader extends jconsole.ConsoleReader with interpreter.VariColumnTabulator {
  val isAcross   = interpreter.`package`.isAcross
  val marginSize = 3

  def width  = getTerminal.getWidth()
  def height = getTerminal.getHeight()

  private def morePrompt = "--More--"

  private def emulateMore(): Int = {
    val key = readOneKey(morePrompt)
    try key match {
      case '\r' | '\n' => 1
      case 'q' => -1
      case _ => height - 1
    }
    finally {
      eraseLine()
      // TODO: still not quite managing to erase --More-- and get
      // back to a scala prompt without another keypress.
      if (key == 'q') {
        putString(getPrompt())
        redrawLine()
        flush()
      }
    }
  }

  override def printColumns(items: JCollection[_ <: CharSequence]): Unit = {
    import scala.tools.nsc.interpreter.javaCharSeqCollectionToScala
    printColumns_(items: List[String])
  }

  private def printColumns_(items: List[String]): Unit = if (items exists (_ != "")) {
    val grouped = tabulate(items)
    var linesLeft = if (isPaginationEnabled()) height - 1 else Int.MaxValue
    grouped foreach { xs =>
      println(xs.mkString)
      linesLeft -= 1
      if (linesLeft <= 0) {
        linesLeft = emulateMore()
        if (linesLeft < 0)
          return
      }
    }
  }

  def readOneKey(prompt: String) = {
    this.print(prompt)
    this.flush()
    this.readCharacter()
  }

  def eraseLine() = resetPromptLine("", "", 0)

  def redrawLineAndFlush(): Unit = {
    flush(); drawLine(); flush()
  }

  // A hook for running code after the repl is done initializing.
  def initCompletion(completion: Completion): Unit = {
    this setBellEnabled false

    if (completion ne interpreter.NoCompletion) {
      val jlineCompleter = new ArgumentCompleter(new JLineDelimiter,
          new Completer {
            val tc = completion.completer()
            def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = {
              val buf = if (_buf == null) "" else _buf
              val Candidates(newCursor, newCandidates) = tc.complete(buf, cursor)
              newCandidates foreach (candidates add _)
              newCursor
            }
          }
        )

      jlineCompleter setStrict false

      this addCompleter jlineCompleter
      this setAutoprintThreshold 400 // max completion candidates without warning
    }
  }
}