summaryrefslogtreecommitdiff
path: root/src/repl
diff options
context:
space:
mode:
Diffstat (limited to 'src/repl')
-rw-r--r--src/repl/scala/tools/nsc/interpreter/Formatting.scala35
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ILoop.scala119
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala9
-rw-r--r--src/repl/scala/tools/nsc/interpreter/LoopCommands.scala84
-rw-r--r--src/repl/scala/tools/nsc/interpreter/ReplReporter.scala33
-rw-r--r--src/repl/scala/tools/nsc/interpreter/Scripted.scala2
6 files changed, 168 insertions, 114 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/Formatting.scala b/src/repl/scala/tools/nsc/interpreter/Formatting.scala
deleted file mode 100644
index 4a9548730a..0000000000
--- a/src/repl/scala/tools/nsc/interpreter/Formatting.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Paul Phillips
- */
-
-package scala.tools.nsc
-package interpreter
-
-import util.stringFromWriter
-
-class Formatting(indent: Int) {
-
- private val indentation = " " * indent
-
- private def indenting(code: String): Boolean = {
- /** Heuristic to avoid indenting and thereby corrupting """-strings and XML literals. */
- val tokens = List("\"\"\"", "</", "/>")
- val noIndent = (code contains "\n") && (tokens exists code.contains)
-
- !noIndent
- }
- /** Indent some code by the width of the scala> prompt.
- * This way, compiler error messages read better.
- */
- def indentCode(code: String) = stringFromWriter(str =>
- for (line <- code.lines) {
- if (indenting(code)) str print indentation
- str println line
- str.flush()
- }
- )
-}
-object Formatting {
- def forPrompt(prompt: String) = new Formatting(prompt.lines.toList.last.length)
-}
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
index d1096fda4e..9635f320fe 100644
--- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala
@@ -9,6 +9,7 @@ package interpreter
import scala.language.{ implicitConversions, existentials }
import scala.annotation.tailrec
import Predef.{ println => _, _ }
+import PartialFunction.{cond => when}
import interpreter.session._
import StdReplTags._
import scala.tools.asm.ClassReader
@@ -17,7 +18,7 @@ import scala.tools.nsc.util.{ ClassPath, stringFromStream }
import scala.reflect.classTag
import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader, NoPosition }
import ScalaClassLoader._
-import scala.reflect.io.File
+import scala.reflect.io.{Directory, File, Path}
import scala.tools.util._
import io.AbstractFile
import scala.concurrent.{ ExecutionContext, Await, Future }
@@ -26,6 +27,8 @@ import java.io.BufferedReader
import scala.util.{ Try, Success, Failure }
+import Completion._
+
/** The Scala interactive shell. It provides a read-eval-print loop
* around the Interpreter class.
* After instantiation, clients should call the main() method.
@@ -119,37 +122,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
intp = new ILoopInterpreter
}
- /** 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)
- }
- private 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))
- }
- private 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)
- }
- // this lets us add commands willy-nilly and only requires enough command to disambiguate
- private def matchingCommands(cmd: String) = commands filter (_.name startsWith cmd)
- private object CommandMatch {
- def unapply(name: String): Option[LoopCommand] =
- matchingCommands(name) match {
- case x :: Nil => Some(x)
- case xs => xs find (_.name == name) // accept an exact match
- }
- }
-
/** Show the history */
lazy val historyCommand = new LoopCommand("history", "show the history (optional num is commands to show)") {
override def usage = "[num]"
@@ -214,16 +186,16 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
cmd("implicits", "[-v]", "show the implicits in scope", intp.implicitsCommand),
cmd("javap", "<path|class>", "disassemble a file or class name", javapCommand),
cmd("line", "<id>|<line>", "place line(s) at the end of history", lineCommand),
- cmd("load", "<path>", "interpret lines in a file", loadCommand),
- cmd("paste", "[-raw] [path]", "enter paste mode or paste a file", pasteCommand),
+ cmd("load", "<path>", "interpret lines in a file", loadCommand, fileCompletion),
+ cmd("paste", "[-raw] [path]", "enter paste mode or paste a file", pasteCommand, fileCompletion),
nullary("power", "enable power user mode", powerCmd),
nullary("quit", "exit the interpreter", () => Result(keepRunning = false, None)),
- cmd("replay", "[options]", "reset the repl and replay all previous commands", replayCommand),
+ cmd("replay", "[options]", "reset the repl and replay all previous commands", replayCommand, settingsCompletion),
cmd("require", "<path>", "add a jar to the classpath", require),
- cmd("reset", "[options]", "reset the repl to its initial state, forgetting all session entries", resetCommand),
- cmd("save", "<path>", "save replayable session to a file", saveCommand),
+ cmd("reset", "[options]", "reset the repl to its initial state, forgetting all session entries", resetCommand, settingsCompletion),
+ cmd("save", "<path>", "save replayable session to a file", saveCommand, fileCompletion),
shCommand,
- cmd("settings", "<options>", "update compiler options, if possible; see reset", changeSettings),
+ cmd("settings", "<options>", "update compiler options, if possible; see reset", changeSettings, settingsCompletion),
nullary("silent", "disable/enable automatic printing of results", verbosity),
cmd("type", "[-v] <expr>", "display the type of an expression without evaluating it", typeCommand),
cmd("kind", "[-v] <expr>", "display the kind of expression's type", kindCommand),
@@ -235,6 +207,46 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
cmd("phase", "<phase>", "set the implicit phase for power commands", phaseCommand)
)
+ // complete filename
+ val fileCompletion: Completion = new Completion {
+ def resetVerbosity(): Unit = ()
+ val emptyWord = """(\s+)$""".r.unanchored
+ val directorily = """(\S*/)$""".r.unanchored
+ val trailingWord = """(\S+)$""".r.unanchored
+ def listed(i: Int, dir: Option[Path]) =
+ dir.filter(_.isDirectory).map(d => Candidates(i, d.toDirectory.list.map(_.name).toList)).getOrElse(NoCandidates)
+ def listedIn(dir: Directory, name: String) = dir.list.filter(_.name.startsWith(name)).map(_.name).toList
+ def complete(buffer: String, cursor: Int): Candidates =
+ buffer.substring(0, cursor) match {
+ case emptyWord(s) => listed(cursor, Directory.Current)
+ case directorily(s) => listed(cursor, Option(Path(s)))
+ case trailingWord(s) =>
+ val f = File(s)
+ val (i, maybes) =
+ if (f.isFile) (cursor - s.length, List(f.toAbsolute.path))
+ else if (f.isDirectory) (cursor - s.length, List(s"${f.toAbsolute.path}/"))
+ else if (f.parent.exists) (cursor - f.name.length, listedIn(f.parent.toDirectory, f.name))
+ else (-1, Nil)
+ if (maybes.isEmpty) NoCandidates else Candidates(i, maybes)
+ case _ => NoCandidates
+ }
+ }
+
+ // complete settings name
+ val settingsCompletion: Completion = new Completion {
+ def resetVerbosity(): Unit = ()
+ val trailingWord = """(\S+)$""".r.unanchored
+ def complete(buffer: String, cursor: Int): Candidates = {
+ buffer.substring(0, cursor) match {
+ case trailingWord(s) =>
+ val maybes = settings.visibleSettings.filter(_.name.startsWith(s)).map(_.name)
+ .filterNot(when(_) { case "-"|"-X"|"-Y" => true }).toList.sorted
+ if (maybes.isEmpty) NoCandidates else Candidates(cursor - s.length, maybes)
+ case _ => NoCandidates
+ }
+ }
+ }
+
private def importsCommand(line: String): Result = {
val tokens = words(line)
val handlers = intp.languageWildcardHandlers ++ intp.importHandlers
@@ -681,20 +693,11 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
* (1) whether to keep running, (2) the line to record for replay, if any.
*/
def command(line: String): Result = {
- if (line startsWith ":") colonCommand(line.tail)
+ if (line startsWith ":") colonCommand(line)
else if (intp.global == null) Result(keepRunning = false, None) // Notice failure to create compiler
else Result(keepRunning = true, interpretStartingWith(line))
}
- private val commandish = """(\S+)(?:\s+)?(.*)""".r
-
- private def colonCommand(line: String): Result = line.trim match {
- case "" => helpSummary()
- case commandish(CommandMatch(cmd), rest) => cmd(rest)
- case commandish(name, _) => ambiguousError(name)
- case _ => echo("?")
- }
-
private def readWhile(cond: String => Boolean) = {
Iterator continually in.readLine("") takeWhile (x => x != null && cond(x))
}
@@ -849,6 +852,18 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
}
+ // delegate to command completion or presentation compiler
+ class ReplCompletion(intp: IMain) extends Completion {
+ val pc = new PresentationCompilerCompleter(intp)
+ def resetVerbosity(): Unit = pc.resetVerbosity()
+ def complete(buffer: String, cursor: Int): Completion.Candidates = {
+ if (buffer.startsWith(":"))
+ colonCompletion(buffer, cursor).complete(buffer, cursor)
+ else
+ pc.complete(buffer, cursor)
+ }
+ }
+
/** Tries to create a jline.InteractiveReader, falling back to SimpleReader,
* unless settings or properties are such that it should start with SimpleReader.
* The constructor of the InteractiveReader must take a Completion strategy,
@@ -868,7 +883,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
}
def mkReader(maker: ReaderMaker) = maker { () =>
- if (settings.noCompletion) NoCompletion else new PresentationCompilerCompleter(intp)
+ if (settings.noCompletion) NoCompletion else new ReplCompletion(intp)
}
def internalClass(kind: String) = s"scala.tools.nsc.interpreter.$kind.InteractiveReader"
@@ -1014,6 +1029,7 @@ object ILoop {
// like if you'd just typed it into the repl.
def runForTranscript(code: String, settings: Settings, inSession: Boolean = false): String = {
import java.io.{ BufferedReader, StringReader, OutputStreamWriter }
+ import java.lang.System.{lineSeparator => EOL}
stringFromStream { ostream =>
Console.withOut(ostream) {
@@ -1021,10 +1037,9 @@ object ILoop {
// skip margin prefix for continuation lines, unless preserving session text for test
// should test for repl.paste.ContinueString or replProps.continueText.contains(ch)
override def write(str: String) =
- if (!inSession && (str forall (ch => ch.isWhitespace || ch == '|'))) ()
- else super.write(str)
+ if (inSession || (str.exists(ch => ch != ' ' && ch != '|'))) super.write(str)
}
- val input = new BufferedReader(new StringReader(code.trim + "\n")) {
+ val input = new BufferedReader(new StringReader(s"${code.trim}${EOL}")) {
override def readLine(): String = {
mark(1) // default buffer is 8k
val c = read()
diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala
index 980d12f9b8..2ae3b207b7 100644
--- a/src/repl/scala/tools/nsc/interpreter/IMain.scala
+++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala
@@ -111,11 +111,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
try body finally label = saved
}
- // the expanded prompt but without color escapes and without leading newline, for purposes of indenting
- lazy val formatting = Formatting.forPrompt(replProps.promptText)
lazy val reporter: ReplReporter = new ReplReporter(this)
- import formatting.indentCode
import reporter.{ printMessage, printUntruncatedMessage }
// This exists mostly because using the reporter too early leads to deadlock.
@@ -867,8 +864,8 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
|${preambleHeader format lineRep.readName}
|${envLines mkString (" ", ";\n ", ";\n")}
|$importsPreamble
- |${indentCode(toCompute)}""".stripMargin
- def preambleLength = preamble.length - toCompute.length - 1
+ |${toCompute}""".stripMargin
+ def preambleLength = preamble.length - toCompute.length
val generate = (m: MemberHandler) => m extraCodeToEvaluate Request.this
@@ -1105,7 +1102,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends
def apply(line: String): Result = debugging(s"""parse("$line")""") {
var isIncomplete = false
- def parse = {
+ def parse = withoutWarnings {
reporter.reset()
val trees = newUnitParser(line, label).parseStats()
if (reporter.hasErrors) Error(trees)
diff --git a/src/repl/scala/tools/nsc/interpreter/LoopCommands.scala b/src/repl/scala/tools/nsc/interpreter/LoopCommands.scala
index 8c3b174513..a2ce63996b 100644
--- a/src/repl/scala/tools/nsc/interpreter/LoopCommands.scala
+++ b/src/repl/scala/tools/nsc/interpreter/LoopCommands.scala
@@ -24,18 +24,21 @@ class ProcessResult(val line: String) {
override def toString = "`%s` (%d lines, exit %d)".format(line, buffer.size, exitCode)
}
-trait LoopCommands {
+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
+ 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 = ":" + name + (
+ def usageMsg: String = s":$name${
if (usage == "") "" else " " + usage
- )
+ }"
def apply(line: String): Result
// called if no args are given
@@ -43,21 +46,88 @@ trait LoopCommands {
"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): LoopCommand =
+ 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)
+ 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) extends LoopCommand(name, help) {
+ 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)
}
diff --git a/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala b/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala
index 3a0b69f41e..b01d242d44 100644
--- a/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala
+++ b/src/repl/scala/tools/nsc/interpreter/ReplReporter.scala
@@ -9,7 +9,7 @@ package interpreter
import reporters._
import IMain._
-import scala.reflect.internal.util.Position
+import scala.reflect.internal.util.{OffsetPosition, Position}
/** Like ReplGlobal, a layer for ensuring extra functionality.
*/
@@ -40,14 +40,25 @@ class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, Console.i
case INFO => RESET
}
+ private val promptLength = replProps.promptText.lines.toList.last.length
+ private val indentation = " " * promptLength
+
+ // colorized console labels
+ override protected def clabel(severity: Severity): String = {
+ val label0 = super.clabel(severity)
+ if (replProps.colorOk) s"${severityColor(severity)}${label0}${RESET}" else label0
+ }
+
+ // shift indentation for source text entered at prompt
override def print(pos: Position, msg: String, severity: Severity) {
- val prefix = (
- if (replProps.colorOk)
- severityColor(severity) + clabel(severity) + RESET
- else
- clabel(severity)
- )
- printMessage(pos, prefix + msg)
+ val adjusted =
+ if (pos.source.file.name == "<console>")
+ new OffsetPosition(pos.source, pos.offset.getOrElse(0)) {
+ override def lineContent = s"${indentation}${super.lineContent}"
+ override def lineCaret = s"${indentation}${super.lineCaret}"
+ }
+ else pos
+ super.print(adjusted, msg, severity)
}
override def printMessage(msg: String) {
@@ -63,12 +74,8 @@ class ReplReporter(intp: IMain) extends ConsoleReporter(intp.settings, Console.i
else Console.println("[init] " + msg)
}
- override def displayPrompt() {
- if (intp.totalSilence) ()
- else super.displayPrompt()
- }
+ override def displayPrompt() = if (!intp.totalSilence) super.displayPrompt()
override def rerunWithDetails(setting: reflect.internal.settings.MutableSettings#Setting, name: String) =
s"; for details, enable `:setting $name' or `:replay $name'"
-
}
diff --git a/src/repl/scala/tools/nsc/interpreter/Scripted.scala b/src/repl/scala/tools/nsc/interpreter/Scripted.scala
index 6aef486957..8d87d98e53 100644
--- a/src/repl/scala/tools/nsc/interpreter/Scripted.scala
+++ b/src/repl/scala/tools/nsc/interpreter/Scripted.scala
@@ -331,7 +331,7 @@ class WriterOutputStream(writer: Writer) extends OutputStream {
byteBuffer.flip()
val result = decoder.decode(byteBuffer, charBuffer, /*eoi=*/ false)
if (byteBuffer.remaining == 0) byteBuffer.clear()
- if (charBuffer.position > 0) {
+ if (charBuffer.position() > 0) {
charBuffer.flip()
writer write charBuffer.toString
charBuffer.clear()