summaryrefslogtreecommitdiff
path: root/src/repl/scala/tools/nsc/interpreter/LoopCommands.scala
blob: a2ce63996bf0368335fa5c336344d50c4960eb51 (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
/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Paul Phillips
 */

package scala
package tools
package nsc
package interpreter

import scala.language.implicitConversions

import scala.collection.mutable.ListBuffer

class ProcessResult(val line: String) {
  import scala.sys.process._
  private val buffer = new ListBuffer[String]

  val builder  = Process(line)
  val logger   = ProcessLogger(buffer += _)
  val exitCode = builder ! logger
  def lines    = buffer.toList

  override def toString = "`%s` (%d lines, exit %d)".format(line, buffer.size, exitCode)
}

trait LoopCommands { self: { def echo(msg: String): Unit } =>
  protected def out: JPrintWriter

  // So outputs can be suppressed.
  def echoCommandMessage(msg: String): Unit = out.println(msg)

  // available commands
  def commands: List[LoopCommand]

  // a single interpreter command
  abstract class LoopCommand(val name: String, val help: String) extends (String => Result) {
    def usage: String = ""
    def usageMsg: String = s":$name${
      if (usage == "") "" else " " + usage
    }"
    def apply(line: String): Result

    // called if no args are given
    def showUsage(): Result = {
      "usage is " + usageMsg
      Result(keepRunning = true, None)
    }

    // subclasses may provide completions
    def completion: Completion = NoCompletion
  }
  object LoopCommand {
    def nullary(name: String, help: String, f: () => Result): LoopCommand =
      new NullaryCmd(name, help, _ => f())

    def cmd(name: String, usage: String, help: String, f: String => Result, completion: Completion = NoCompletion): LoopCommand =
      if (usage == "") new NullaryCmd(name, help, f)
      else new LineCmd(name, usage, help, f, completion)
  }

  /** print a friendly help message */
  def helpCommand(line: String): Result = line match {
    case ""                => helpSummary()
    case CommandMatch(cmd) => echo(f"%n${cmd.help}")
    case _                 => ambiguousError(line)
  }

  def helpSummary() = {
    val usageWidth = commands map (_.usageMsg.length) max
    val formatStr  = s"%-${usageWidth}s %s"

    echo("All commands can be abbreviated, e.g., :he instead of :help.")

    for (cmd <- commands) echo(formatStr.format(cmd.usageMsg, cmd.help))
  }
  def ambiguousError(cmd: String): Result = {
    matchingCommands(cmd) match {
      case Nil  => echo(cmd + ": no such command.  Type :help for help.")
      case xs   => echo(cmd + " is ambiguous: did you mean " + xs.map(":" + _.name).mkString(" or ") + "?")
    }
    Result(keepRunning = true, None)
  }

  // all commands with given prefix
  private def matchingCommands(cmd: String) = commands.filter(_.name.startsWith(cmd.stripPrefix(":")))

  // extract command from partial name, or prefer exact match if multiple matches
  private object CommandMatch {
    def unapply(name: String): Option[LoopCommand] =
      matchingCommands(name) match {
        case Nil      => None
        case x :: Nil => Some(x)
        case xs       => xs find (_.name == name)
      }
  }

  // extract command name and rest of line
  private val commandish = """(\S+)(?:\s+)?(.*)""".r

  def colonCommand(line: String): Result = line.trim match {
    case ""                                  => helpSummary()
    case commandish(CommandMatch(cmd), rest) => cmd(rest)
    case commandish(name, _)                 => ambiguousError(name)
    case _                                   => echo("?")
  }

  import Completion.Candidates

  def colonCompletion(line: String, cursor: Int): Completion = line.trim match {
    case commandish(name @ CommandMatch(cmd), rest) =>
      if (name.length > cmd.name.length) cmd.completion
      else
        new Completion {
          def resetVerbosity(): Unit = ()
          def complete(buffer: String, cursor: Int) = Candidates(cursor - name.length + 1, List(cmd.name))
        }
    case commandish(name, _) if matchingCommands(name).nonEmpty =>
      new Completion {
        def resetVerbosity(): Unit = ()
        def complete(buffer: String, cursor: Int) = Candidates(cursor - name.length + 1, matchingCommands(name).map(_.name))
      }
    case _ => NoCompletion
  }

  class NullaryCmd(name: String, help: String, f: String => Result) extends LoopCommand(name, help) {
    def apply(line: String): Result = f(line)
  }

  class LineCmd(name: String, argWord: String, help: String, f: String => Result, override val completion: Completion) extends LoopCommand(name, help) {
    override def usage = argWord
    def apply(line: String): Result = f(line)
  }

  class VarArgsCmd(name: String, argWord: String, help: String, f: List[String] => Result)
            extends LoopCommand(name, help) {
    override def usage = argWord
    def apply(line: String): Result = apply(words(line))
    def apply(args: List[String]) = f(args)
  }

  // the result of a single command
  case class Result(keepRunning: Boolean, lineToRecord: Option[String])

  object Result {
    // the default result means "keep running, and don't record that line"
    val default = Result(keepRunning = true, None)

    // "keep running, and record this line"
    def recording(line: String) = Result(keepRunning = true, Option(line))

    // most commands do not want to micromanage the Result, but they might want
    // to print something to the console, so we accommodate Unit and String returns.
    implicit def resultFromUnit(x: Unit): Result = default
    implicit def resultFromString(msg: String): Result = {
      echoCommandMessage(msg)
      default
    }
  }
}