summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2009-03-05 19:33:14 +0000
committerPaul Phillips <paulp@improving.org>2009-03-05 19:33:14 +0000
commit9bedaaa81711cf077437d2868442431b1b15ee8d (patch)
tree21ead30ccb233813d20031d825152b8ab6eb5808 /src
parent276ed222116522b4966c36f38df70a10131dde0d (diff)
downloadscala-9bedaaa81711cf077437d2868442431b1b15ee8d.tar.gz
scala-9bedaaa81711cf077437d2868442431b1b15ee8d.tar.bz2
scala-9bedaaa81711cf077437d2868442431b1b15ee8d.zip
Another big Interpreter patch, this one mostly ...
Another big Interpreter patch, this one mostly attacking InterpreterLoop. This adds opening cuts of a number of features: tab completion for repl identifiers (requires using -Ycompletion for now), a :power repl command that enables power user commands, and more. I'll document it properly once it's a bit less experimental.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala105
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala378
-rw-r--r--src/compiler/scala/tools/nsc/Settings.scala1
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala28
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala14
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineReader.scala18
6 files changed, 365 insertions, 179 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index af9f3dda27..a41198a97a 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -169,6 +169,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
/** the previous requests this interpreter has processed */
private val prevRequests = new ArrayBuffer[Request]()
+ private def allUsedNames = prevRequests.toList.flatMap(_.usedNames).removeDuplicates
+ private def allBoundNames = prevRequests.toList.flatMap(_.boundNames).removeDuplicates
/** counter creator */
def mkNameCreator(s: String) = new Function0[String] with Function1[String,String] {
@@ -191,11 +193,12 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
private def newInternalVarName = () => newVarName(INTERPRETER_SYNTHVAR_PREFIX)
// private val newInternalVarName = mkNameCreator(INTERPRETER_SYNTHVAR_PREFIX)
- /** Check if a name looks like it was generated by newVarName */
- private def isGeneratedVarName(name: String): Boolean = {
- val pre = INTERPRETER_VAR_PREFIX
+ private def isGenerated(pre: String, name: String) =
(name startsWith pre) && (name drop pre.length).forall(_.isDigit)
- }
+
+ /** Check if a name looks like it was generated by newVarName */
+ private def isGeneratedVarName(name: String): Boolean = isGenerated(INTERPRETER_VAR_PREFIX, name)
+ private def isSynthVarName(name: String): Boolean = isGenerated(INTERPRETER_SYNTHVAR_PREFIX, name)
/** generate a string using a routine that wants to write on a stream */
private def stringFrom(writer: PrintWriter => Unit): String = {
@@ -785,6 +788,100 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
}
}
}
+
+ /** These methods are exposed so REPL commands can access them.
+ * The command infrastructure is in InterpreterLoop.
+ */
+ def dumpState(xs: List[String]): String = {
+ // println("Imports for " + req + " => " + req.importsPreamble)
+ // req.handlers foreach { h => println("Handler " + h + " used names: " + h.usedNames) }
+ // req.trees foreach { x => println("Tree: " + x) }
+ // xs foreach { x => println("membersOfIdentifier(" + x + ") = " + membersOfIdentifier(x)) }
+ List(
+ "allUsedNames = " + allUsedNames,
+ "allBoundNames = " + allBoundNames,
+ prevRequests.toList.map(req => " \"" + req.line + "\" => " + req.objectSourceCode)
+ ).mkString("", "\n", "\n")
+ }
+
+ // very simple right now, will get more interesting
+ def dumpTrees(xs: List[String]): String = {
+ val treestrs =
+ List.flatten(
+ for (x <- xs ; name <- nameOfIdent(x) ; req <- requestForName(name))
+ yield req.trees
+ )
+
+ if (treestrs.isEmpty) "No trees found."
+ else treestrs.map(t => t.toString + " (" + t.getClass.getSimpleName + ")\n").mkString
+ }
+
+ def powerUser(): String = {
+ beQuietDuring {
+ val mkTypeCmd =
+ """def mkType(name: String, what: String) = interpreter.interpret("type " + name + " = " + what)"""
+
+ this.bind("interpreter", "scala.tools.nsc.Interpreter", this)
+ interpret(mkTypeCmd)
+ }
+
+ """** Power User mode enabled - BEEP BOOP **
+ |** Launch scala with -Ycompletion for <tab> **
+ |** New vals! Try interpreter.<tab> **
+ |** New defs! Try mkType("T", "String") **
+ |** New cmds! :help to discover them **""".stripMargin
+ }
+
+ def nameOfIdent(line: String): Option[Name] = {
+ parse(line) match {
+ case Some(List(Ident(x))) => Some(x)
+ case _ => None
+ }
+ }
+
+ private def requestForName(name: Name): Option[Request] = {
+ for (req <- prevRequests.toList.reverse) {
+ if (req.handlers.exists(_.boundNames contains name))
+ return Some(req)
+ }
+ None
+ }
+
+ /** The main entry point for tab-completion. When the user types x.<tab>
+ * this method is called with "x" as an argument, and it discovers the
+ * fields and methods of x via reflection and returns their names to jline.
+ */
+ def membersOfIdentifier(line: String): List[String] = {
+ val methodsCode = """ .
+ | asInstanceOf[AnyRef].getClass.getMethods .
+ | filter(x => !java.lang.reflect.Modifier.isStatic(x.getModifiers)) .
+ | map(_.getName) .
+ | mkString(" ")""".stripMargin
+
+ val filterMethods = List("", "hashCode", "equals", "wait", "notify", "notifyAll")
+ val res = beQuietDuring {
+ for (name <- nameOfIdent(line) ; req <- requestForName(name)) yield {
+ if (interpret("val " + newInternalVarName() + " = " + name + methodsCode) != IR.Success) Nil
+ else {
+ val result = prevRequests.last.resultObjectName
+ val resultObj = Class.forName(result, true, classLoader)
+ val valMethod = resultObj.getMethod("result")
+
+ valMethod.invoke(resultObj).toString .
+ split(" ").toList .
+ filter(x => !filterMethods.contains(x)) .
+ filter(!_.contains("$")) .
+ removeDuplicates
+ }
+ }
+ }
+
+ res getOrElse Nil
+ }
+
+ // debugging
+ private var debuggingOutput = false
+ def DBG(s: String) = if (debuggingOutput) out println s else ()
}
/** Utility methods for the Interpreter. */
diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
index 928034be38..796fb4bbf9 100644
--- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala
+++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
@@ -13,6 +13,49 @@ import java.lang.{ClassLoader, System}
import scala.tools.nsc.{InterpreterResults => IR}
import scala.tools.nsc.interpreter._
+// Classes to wrap up interpreter commands and their results
+// You can add new commands by adding entries to val commands
+// inside InterpreterLoop.
+object InterpreterControl {
+ // the default result means "keep running, and don't record that line"
+ val defaultResult = Result(true, None)
+
+ // a single interpreter command
+ sealed abstract class Command extends Function1[List[String], Result] {
+ val name: String
+ val help: String
+ def error(msg: String) = {
+ println(":" + name + " " + msg + ".")
+ Result(true, None)
+ }
+ def getHelp(): String = ":" + name + " " + help + "."
+ }
+
+ case class NoArgs(name: String, help: String, f: () => Result) extends Command {
+ def apply(args: List[String]) = if (args.isEmpty) f() else error("accepts no arguments")
+ }
+
+ case class LineArg(name: String, help: String, f: (String) => Result) extends Command {
+ def apply(args: List[String]) =
+ if (args.size == 1) f(args.head)
+ else error("requires a line of input")
+ }
+
+ case class OneArg(name: String, help: String, f: (String) => Result) extends Command {
+ def apply(args: List[String]) =
+ if (args.size == 1) f(args.head)
+ else error("requires exactly one argument")
+ }
+
+ case class VarArgs(name: String, help: String, f: (List[String]) => Result) extends Command {
+ def apply(args: List[String]) = f(args)
+ }
+
+ // the result of a single command
+ case class Result(keepRunning: Boolean, lineToRecord: Option[String])
+}
+import InterpreterControl._
+
/** The
* <a href="http://scala-lang.org/" target="_top">Scala</a>
* interactive shell. It provides a read-eval-print loop around
@@ -28,35 +71,29 @@ import scala.tools.nsc.interpreter._
* @version 1.2
*/
class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
- def this(in0: BufferedReader, out: PrintWriter) =
- this(Some(in0), out)
-
+ def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out)
def this() = this(None, new PrintWriter(Console.out))
- /** The input stream from which interpreter commands come */
- var in: InteractiveReader = _ //set by main()
+ /** The input stream from which commands come, set by main() */
+ var in: InteractiveReader = _
/** The context class loader at the time this object was created */
- protected val originalClassLoader =
- Thread.currentThread.getContextClassLoader
+ protected val originalClassLoader = Thread.currentThread.getContextClassLoader
- var settings: Settings = _ // set by main()
- var interpreter: Interpreter = null // set by createInterpreter()
+ var settings: Settings = _ // set by main()
+ var interpreter: Interpreter = _ // set by createInterpreter()
def isettings = interpreter.isettings
- /** A reverse list of commands to replay if the user
- * requests a :replay */
+ /** A reverse list of commands to replay if the user requests a :replay */
var replayCommandsRev: List[String] = Nil
/** A list of commands to replay if the user requests a :replay */
def replayCommands = replayCommandsRev.reverse
- /** Record a command for replay should the user requset a :replay */
- def addReplay(cmd: String) =
- replayCommandsRev = cmd :: replayCommandsRev
+ /** Record a command for replay should the user request a :replay */
+ def addReplay(cmd: String) = replayCommandsRev = cmd :: replayCommandsRev
- /** Close the interpreter, if there is one, and set
- * interpreter to <code>null</code>. */
+ /** Close the interpreter and set the var to <code>null</code>. */
def closeInterpreter() {
if (interpreter ne null) {
interpreter.close
@@ -65,101 +102,126 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
}
}
- /** Create a new interpreter. Close the old one, if there
- * is one. */
+ /** Create a new interpreter. */
def createInterpreter() {
- //closeInterpreter()
-
interpreter = new Interpreter(settings, out) {
override protected def parentClassLoader = classOf[InterpreterLoop].getClassLoader
}
interpreter.setContextClassLoader()
}
- /** Bind the settings so that evaluated code can modiy them */
+ /** Bind the settings so that evaluated code can modify them */
def bindSettings() {
interpreter.beQuietDuring {
interpreter.compileString(InterpreterSettings.sourceCodeForClass)
-
- interpreter.bind(
- "settings",
- "scala.tools.nsc.InterpreterSettings",
- isettings)
+ interpreter.bind("settings", "scala.tools.nsc.InterpreterSettings", isettings)
}
}
-
/** print a friendly help message */
- def printHelp {
- printWelcome
- out.println("Type :load followed by a filename to load a Scala file.")
- out.println("Type :replay to reset execution and replay all previous commands.")
- out.println("Type :quit to exit the interpreter.")
+ def printHelp() = {
+ out println "All commands can be abbreviated - for example :h or :he instead of :help.\n"
+ commands foreach { c => out println c.getHelp }
}
/** Print a welcome message */
def printWelcome() {
- out.println("Welcome to Scala " + Properties.versionString + " (" +
- System.getProperty("java.vm.name") + ", Java " + System.getProperty("java.version") + ")." )
- out.println("Type in expressions to have them evaluated.")
- out.println("Type :help for more information.")
- out.flush()
+ val welcomeMsg =
+ """|Welcome to Scala %s (%s, Java %s).
+ |Type in expressions to have them evaluated.
+ |Type :help for more information.""" .
+ stripMargin.format(
+ Properties.versionString,
+ System.getProperty("java.vm.name"),
+ System.getProperty("java.version")
+ )
+
+ out println welcomeMsg
+ out.flush
}
/** Prompt to print when awaiting input */
val prompt = Properties.shellPromptString
+ // most commands do not want to micromanage the Result, but they might want
+ // to print something to the console, so we accomodate Unit and String returns.
+ object CommandImplicits {
+ implicit def u2ir(x: Unit): Result = defaultResult
+ implicit def s2ir(s: String): Result = {
+ out println s
+ defaultResult
+ }
+ }
+
+ /** Standard commands **/
+ val standardCommands: List[Command] = {
+ import CommandImplicits._
+ List(
+ NoArgs("help", "prints this help message", printHelp),
+ OneArg("load", "followed by a filename loads a Scala file", load),
+ NoArgs("replay", "resets execution and replays all previous commands", replay),
+ NoArgs("quit", "exits the interpreter", () => Result(false, None)),
+ NoArgs("power", "enable power user mode", power)
+ )
+ }
+
+ /** Power user commands */
+ // XXX - why does a third argument like "interpreter dumpState(_)" throw an NPE
+ // while the version below works?
+ var powerUserOn = false
+ val powerCommands: List[Command] = {
+ import CommandImplicits._
+ List(
+ VarArgs("dump", "displays a view of the interpreter's internal state",
+ (xs: List[String]) => interpreter dumpState xs),
+ VarArgs("tree", "displays ASTs for specified identifiers",
+ (xs: List[String]) => interpreter dumpTrees xs)
+ // LineArg("meta", "given code which produces scala code, executes the results",
+ // (xs: List[String]) => )
+ )
+ }
+
+ /** Available commands */
+ def commands: List[Command] = standardCommands ::: (if (powerUserOn) powerCommands else Nil)
+
/** The main read-eval-print loop for the interpreter. It calls
* <code>command()</code> for each line of input, and stops when
* <code>command()</code> returns <code>false</code>.
*/
def repl() {
- var first = true
- while (true) {
- out.flush()
-
- val line =
- if (first) {
- /* For some reason, the first interpreted command always takes
- * a second or two. So, wait until the welcome message
- * has been printed before calling bindSettings. That way,
- * the user can read the welcome message while this
- * command executes.
- */
- val futLine = scala.concurrent.ops.future(in.readLine(prompt))
-
- bindSettings()
- first = false
-
- futLine()
- } else {
- in.readLine(prompt)
+ def readOneLine() = {
+ out.flush
+ in readLine prompt
+ }
+ // return false if repl should exit
+ def processLine(line: String): Boolean =
+ if (line eq null) false // assume null means EOF
+ else command(line) match {
+ case Result(false, _) => false
+ case Result(_, Some(finalLine)) => addReplay(finalLine) ; true
+ case _ => true
}
- if (line eq null)
- return () // assumes null means EOF
-
- val (keepGoing, finalLineMaybe) = command(line)
-
- if (!keepGoing)
- return
-
- finalLineMaybe match {
- case Some(finalLine) => addReplay(finalLine)
- case None => ()
- }
- }
+ /* For some reason, the first interpreted command always takes
+ * a second or two. So, wait until the welcome message
+ * has been printed before calling bindSettings. That way,
+ * the user can read the welcome message while this
+ * command executes.
+ */
+ val futLine = scala.concurrent.ops.future(readOneLine)
+ bindSettings()
+ processLine(futLine())
+
+ // loops until false, then returns
+ while (processLine(readOneLine)) { }
}
/** interpret all lines from a specified file */
def interpretAllFrom(filename: String) {
- val fileIn = try {
- new FileReader(filename)
- } catch {
- case _:IOException =>
- out.println("Error opening file: " + filename)
- return
- }
+ val fileIn =
+ try { new FileReader(filename) }
+ catch { case _:IOException => return out.println("Error opening file: " + filename) }
+
val oldIn = in
val oldReplay = replayCommandsRev
try {
@@ -187,49 +249,46 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
}
}
- /** Run one command submitted by the user. Three values are returned:
+ def withFile(filename: String)(action: String => Unit) {
+ if (! new File(filename).exists) out.println("That file does not exist")
+ else action(filename)
+ }
+
+ def load(arg: String) = {
+ var shouldReplay: Option[String] = None
+ withFile(arg)(f => {
+ interpretAllFrom(f)
+ shouldReplay = Some(":load " + arg)
+ })
+ Result(true, shouldReplay)
+ }
+
+ def power() = {
+ powerUserOn = true
+ interpreter.powerUser()
+ }
+
+ /** Run one command submitted by the user. Two values are returned:
* (1) whether to keep running, (2) the line to record for replay,
* if any. */
- def command(line: String): (Boolean, Option[String]) = {
- def withFile(command: String)(action: String => Unit) {
- val spaceIdx = command.indexOf(' ')
- if (spaceIdx <= 0) {
- out.println("That command requires a filename to be specified.")
- return ()
- }
- val filename = command.substring(spaceIdx).trim
- if (! new File(filename).exists) {
- out.println("That file does not exist")
- return ()
- }
- action(filename)
+ def command(line: String): Result = {
+ def withError(msg: String) = {
+ out println msg
+ Result(true, None)
}
- val helpRegexp = ":h(e(l(p)?)?)?"
- val quitRegexp = ":q(u(i(t)?)?)?"
- val loadRegexp = ":l(o(a(d)?)?)?.*"
- val replayRegexp = ":r(e(p(l(a(y)?)?)?)?)?.*"
+ // not a command
+ if (!line.startsWith(":"))
+ return Result(true, interpretStartingWith(line))
- var shouldReplay: Option[String] = None
+ // this lets us add commands willy-nilly and only requires enough command to disambiguate
+ val (x :: args) = line.substring(1).split("""\s+""").toList
- if (line.matches(helpRegexp))
- printHelp
- else if (line.matches(quitRegexp))
- return (false, None)
- else if (line.matches(loadRegexp)) {
- withFile(line)(f => {
- interpretAllFrom(f)
- shouldReplay = Some(line)
- })
+ commands.filter(_.name startsWith x) match {
+ case List(x) => x(args)
+ case Nil => withError("Unknown command. Type :help for help.")
+ case xs => withError("Ambiguous: did you mean " + xs.map(":" + _.name).mkString(" or ") + "?")
}
- else if (line matches replayRegexp)
- replay
- else if (line startsWith ":")
- out.println("Unknown command. Type :help for help.")
- else
- shouldReplay = interpretStartingWith(line)
-
- (true, shouldReplay)
}
/** Interpret expressions starting with the first line.
@@ -238,62 +297,54 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
* read, go ahead and interpret it. Return the full string
* to be recorded for replay, if any.
*/
- def interpretStartingWith(code: String): Option[String] = {
+ def interpretStartingWith(code: String): Option[String] =
interpreter.interpret(code) match {
- case IR.Success => Some(code)
- case IR.Error => None
- case IR.Incomplete =>
+ case IR.Error => None
+ case IR.Success => Some(code)
+ case IR.Incomplete =>
if (in.interactive && code.endsWith("\n\n")) {
out.println("You typed two blank lines. Starting a new command.")
None
- } else {
- val nextLine = in.readLine(" | ")
- if (nextLine == null)
- None // end of file
- else
- interpretStartingWith(code + "\n" + nextLine)
}
- }
- }
-
- def loadFiles(settings: Settings) {
- settings match {
- case settings: GenericRunnerSettings =>
- for (filename <- settings.loadfiles.value) {
- val cmd = ":load " + filename
- command(cmd)
- replayCommandsRev = cmd :: replayCommandsRev
- out.println()
+ else in.readLine(" | ") match {
+ case null => None // end of file
+ case line => interpretStartingWith(code + "\n" + line)
}
- case _ =>
}
+
+ // runs :load <file> on any files passed via -i
+ def loadFiles(settings: Settings) = settings match {
+ case settings: GenericRunnerSettings =>
+ for (filename <- settings.loadfiles.value) {
+ val cmd = ":load " + filename
+ command(cmd)
+ replayCommandsRev = cmd :: replayCommandsRev
+ out.println()
+ }
+ case _ =>
}
def main(settings: Settings) {
this.settings = settings
+ createInterpreter()
- in =
- in0 match {
- case Some(in0) =>
- new SimpleReader(in0, out, true)
-
- case None =>
- val emacsShell = System.getProperty("env.emacs", "") != ""
- //println("emacsShell="+emacsShell) //debug
- if (settings.Xnojline.value || emacsShell)
- new SimpleReader()
- else
- InteractiveReader.createDefault()
- }
+ // sets in to some kind of reader depending on environmental cues
+ in = in0 match {
+ case Some(in0) => new SimpleReader(in0, out, true)
+ case None =>
+ val emacsShell = System.getProperty("env.emacs", "") != ""
- createInterpreter()
+ // tab completion off by default for now
+ if (settings.Xnojline.value || emacsShell) new SimpleReader
+ else if (settings.completion.value) InteractiveReader.createDefault(interpreter)
+ else InteractiveReader.createDefault()
+ }
loadFiles(settings)
-
try {
- if (interpreter.reporter.hasErrors) {
- return // it is broken on startup; go ahead and exit
- }
+ // it is broken on startup; go ahead and exit
+ if (interpreter.reporter.hasErrors) return
+
printWelcome()
repl()
} finally {
@@ -303,17 +354,16 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
/** process command-line arguments and do as they request */
def main(args: Array[String]) {
- def error1(msg: String) { out.println("scala: " + msg) }
- val command = new InterpreterCommand(List.fromArray(args), error1)
-
- if (!command.ok || command.settings.help.value || command.settings.Xhelp.value) {
- // either the command line is wrong, or the user
- // explicitly requested a help listing
- if (command.settings.help.value) out.println(command.usageMsg)
- if (command.settings.Xhelp.value) out.println(command.xusageMsg)
- out.flush
+ def error1(msg: String) = out println ("scala: " + msg)
+ val command = new InterpreterCommand(List fromArray args, error1)
+ def neededHelp(): String =
+ (if (command.settings.help.value) command.usageMsg + "\n" else "") +
+ (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "")
+
+ // if they asked for no help and command is valid, we call the real main
+ neededHelp() match {
+ case "" => if (command.ok) main(command.settings) // else nothing
+ case help => out print help ; out flush
}
- else
- main(command.settings)
}
}
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala
index c6e69b7cfd..e1d40df66d 100644
--- a/src/compiler/scala/tools/nsc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/Settings.scala
@@ -150,6 +150,7 @@ class Settings(error: String => Unit) {
val Xshowtrees = BooleanSetting ("-Yshow-trees", "Show detailed trees when used in connection with -print:phase").hideToIDE
val skip = PhasesSetting ("-Yskip", "Skip")
+ val completion = BooleanSetting ("-Ycompletion", "Enable tab-completion in the REPL").hideToIDE
val Xsqueeze = ChoiceSetting ("-Ysqueeze", "if on, creates compact code in matching", List("on","on","off"), "on").hideToIDE
val statistics = BooleanSetting ("-Ystatistics", "Print compiler statistics").hideToIDE
val stop = PhasesSetting ("-Ystop", "Stop after phase")
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
new file mode 100644
index 0000000000..7e2c8a9f04
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
@@ -0,0 +1,28 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2009 LAMP/EPFL
+ * @author Paul Phillips
+ */
+// $Id$
+
+package scala.tools.nsc.interpreter
+
+import jline._
+
+// REPL completor - queries supplied interpreter for valid completions
+// based on current contents of buffer.
+class Completion(val interpreter: Interpreter) extends Completor {
+ import java.util.{ List => JList }
+
+ override def complete(buffer: String, cursor: Int, candidates: JList[_]): Int = {
+ if (buffer == null) return 0
+ val clist = candidates.asInstanceOf[JList[String]]
+ val lastDot = buffer.lastIndexOf('.')
+ val (path, stub) =
+ if (lastDot < 0) (buffer, "")
+ else (buffer.substring(0, lastDot), buffer.substring(lastDot + 1))
+
+ val members = interpreter membersOfIdentifier path
+ members.filter(_ startsWith stub).foreach(x => clist add x)
+ buffer.length - stub.length
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
index aac3672601..4ae04b76ee 100644
--- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
@@ -33,16 +33,16 @@ object InteractiveReader {
val vendor = System.getProperty("java.vendor", "")
val msgEINTR = "Interrupted system call"
+ def createDefault(): InteractiveReader = createDefault(null)
+
/** Create an interactive reader. Uses <code>JLineReader</code> if the
- * library is available, but otherwise uses a
- * <code>SimpleReaderi</code>. */
- def createDefault(): InteractiveReader =
+ * library is available, but otherwise uses a <code>SimpleReader</code>.
+ */
+ def createDefault(interpreter: Interpreter): InteractiveReader =
try {
- new JLineReader
+ new JLineReader(interpreter)
} catch {
- case e =>
- //out.println("jline is not available: " + e) //debug
- new SimpleReader()
+ case e: Exception => new SimpleReader
}
}
diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
index 84bb11b5f4..8375328017 100644
--- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
@@ -7,9 +7,11 @@
package scala.tools.nsc.interpreter
import java.io.File
+import jline.{ History, ConsoleReader, ArgumentCompletor }
/** Reads from the console using JLine */
-class JLineReader extends InteractiveReader {
+class JLineReader(interpreter: Interpreter) extends InteractiveReader {
+ def this() = this(null)
val consoleReader = {
val history = try {
new jline.History(new File(System.getProperty("user.home"), ".scala_history"))
@@ -18,11 +20,19 @@ class JLineReader extends InteractiveReader {
case _ => new jline.History()
}
val r = new jline.ConsoleReader()
- r.setHistory(history)
- r.setBellEnabled(false)
+ r setHistory history
+ r setBellEnabled false
+
+ if (interpreter != null) {
+ val comp = new ArgumentCompletor(new Completion(interpreter))
+ comp setStrict false
+ r addCompletor comp
+ }
+
r
}
- def readOneLine(prompt: String) = consoleReader.readLine(prompt)
+
+ def readOneLine(prompt: String) = consoleReader readLine prompt
val interactive = true
}