summaryrefslogtreecommitdiff
path: root/src/repl/scala/tools/nsc/interpreter/InteractiveReader.scala
blob: 88a011e99629fc01c55402a08bb48139bac4361c (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
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author Stepan Koltsov
 */

package scala.tools.nsc
package interpreter

import java.io.IOException
import session.History
import InteractiveReader._
import Properties.isMac

/** Reads lines from an input stream */
trait InteractiveReader {
  def postInit(): Unit = {}

  val interactive: Boolean

  def reset(): Unit
  def history: History
  def completion: Completion
  def redrawLine(): Unit

  def readYesOrNo(prompt: String, alt: => Boolean): Boolean = readOneKey(prompt) match {
    case 'y'  => true
    case 'n'  => false
    case -1   => false // EOF
    case _    => alt
  }

  protected def readOneLine(prompt: String): String
  protected def readOneKey(prompt: String): Int

  def readLine(prompt: String): String =
    // hack necessary for OSX jvm suspension because read calls are not restarted after SIGTSTP
    if (isMac) restartSysCalls(readOneLine(prompt), reset())
    else readOneLine(prompt)
}

object InteractiveReader {
  val msgEINTR = "Interrupted system call"
  def restartSysCalls[R](body: => R, reset: => Unit): R =
    try body catch {
      case e: IOException if e.getMessage == msgEINTR => reset ; body
    }

  def apply(): InteractiveReader = SimpleReader()
  @deprecated("Use `apply` instead.", "2.9.0")
  def createDefault(): InteractiveReader = apply() // used by sbt
}

/** Collect one line of user input from the supplied reader.
 *  Runs on a new thread while the REPL is initializing on the main thread.
 *
 *  The user can enter text or a `:paste` command.
 */
class SplashLoop(reader: InteractiveReader, prompt: String) extends Runnable {
  import java.util.concurrent.SynchronousQueue
  import scala.compat.Platform.EOL

  private val result = new SynchronousQueue[Option[String]]
  @volatile private var running: Boolean = _
  private var thread: Thread = _

  /** Read one line of input which can be retrieved with `line`. */
  def run(): Unit = {
    var line = ""
    try
      do {
        line = reader.readLine(prompt)
        if (line != null) {
          line = process(line.trim)
        }
      } while (line != null && line.isEmpty && running)
    finally {
      result.put(Option(line))
    }
  }

  /** Check for `:paste` command. */
  private def process(line: String): String = {
    def isPrefix(s: String, p: String, n: Int) = (
      //s != null && p.inits.takeWhile(_.length >= n).exists(s == _)
      s != null && s.length >= n && s.length <= p.length && s == p.take(s.length)
    )
    if (isPrefix(line, ":paste", 3)) {
      // while collecting lines, check running flag
      var help = f"// Entering paste mode (ctrl-D to finish)%n%n"
      def readWhile(cond: String => Boolean) = {
        Iterator continually reader.readLine(help) takeWhile { x =>
          help = ""
          x != null && cond(x)
        }
      }
      val text = (readWhile(_ => running) mkString EOL).trim
      val next =
        if (text.isEmpty) "// Nothing pasted, nothing gained."
        else "// Exiting paste mode, now interpreting."
      Console println f"%n${next}%n"
      text
    } else {
      line
    }
  }

  def start(): Unit = result.synchronized {
    require(thread == null, "Already started")
    thread = new Thread(this)
    running = true
    thread.start()
  }

  def stop(): Unit = result.synchronized {
    running = false
    if (thread != null) thread.interrupt()
    thread = null
  }

  /** Block for the result line, or null on ctrl-D. */
  def line: String = result.take getOrElse null
}
object SplashLoop {
  def apply(reader: SplashReader, prompt: String): SplashLoop = new SplashLoop(reader, prompt)
}

/** Reader during splash. Handles splash-completion with a stub, otherwise delegates. */
class SplashReader(reader: InteractiveReader, postIniter: InteractiveReader => Unit) extends InteractiveReader {
  /** Invoke the postInit action with the underlying reader. */
  override def postInit(): Unit = postIniter(reader)

  override val interactive: Boolean = reader.interactive

  override def reset(): Unit = reader.reset()
  override def history: History = reader.history
  override val completion: Completion = NoCompletion
  override def redrawLine(): Unit = reader.redrawLine()

  override protected[interpreter] def readOneLine(prompt: String): String = ???   // unused
  override protected[interpreter] def readOneKey(prompt: String): Int     = ???   // unused

  override def readLine(prompt: String): String = reader.readLine(prompt)
}
object SplashReader {
  def apply(reader: InteractiveReader)(postIniter: InteractiveReader => Unit) =
    new SplashReader(reader, postIniter)
}