From 9bedaaa81711cf077437d2868442431b1b15ee8d Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 5 Mar 2009 19:33:14 +0000 Subject: 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. --- src/compiler/scala/tools/nsc/Interpreter.scala | 105 +++++- src/compiler/scala/tools/nsc/InterpreterLoop.scala | 378 ++++++++++++--------- src/compiler/scala/tools/nsc/Settings.scala | 1 + .../scala/tools/nsc/interpreter/Completion.scala | 28 ++ .../tools/nsc/interpreter/InteractiveReader.scala | 14 +- .../scala/tools/nsc/interpreter/JLineReader.scala | 18 +- 6 files changed, 365 insertions(+), 179 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/interpreter/Completion.scala (limited to 'src') 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 ** + |** New vals! Try interpreter. ** + |** 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. + * 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 * Scala * 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 null. */ + /** Close the interpreter and set the var to null. */ 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 * command() for each line of input, and stops when * command() returns false. */ 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 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 JLineReader if the - * library is available, but otherwise uses a - * SimpleReaderi. */ - def createDefault(): InteractiveReader = + * library is available, but otherwise uses a SimpleReader. + */ + 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 } -- cgit v1.2.3