diff options
author | Paul Phillips <paulp@improving.org> | 2011-01-31 03:33:53 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-01-31 03:33:53 +0000 |
commit | 174a25e1b3bc054920a9418761bf9f4a8c3d9e79 (patch) | |
tree | 6ebde03729b33aab7a88eccfaf4f114b9dbd6411 /src | |
parent | c5f20ad02b6d1806e8d0dbca6639630726afc5ee (diff) | |
download | scala-174a25e1b3bc054920a9418761bf9f4a8c3d9e79.tar.gz scala-174a25e1b3bc054920a9418761bf9f4a8c3d9e79.tar.bz2 scala-174a25e1b3bc054920a9418761bf9f4a8c3d9e79.zip |
Undid some damage I did recently to tab-complet...
Undid some damage I did recently to tab-completion, and then made a
bunch of stuff work better while I was at it. Clearly past time for some
tests because I can't move a gear anywhere without unleashing an army of
monkeys bearing wrenches. No review.
Diffstat (limited to 'src')
9 files changed, 121 insertions, 122 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index 79f0317533..f9514c9379 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -45,9 +45,8 @@ object Completion { && !(code startsWith "./") && !(code startsWith "..") ) - def looksLikePath(code: String) = (code != null) && (code.length >= 2) && ( - Set("/", "\\", "./", "../", "~/") exists (code startsWith _) - ) + private val pathStarts = """/ \ ./ ../ ~/""" split ' ' toSet + def looksLikePath(code: String) = (code != null) && (pathStarts exists (code startsWith _)) object Forwarder { def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index d74ef8a663..c33675a83a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -64,16 +64,16 @@ trait CompletionAware { * to other CompletionAware objects. */ def completionsFor(parsed: Parsed): List[String] = { - import parsed._ - + import parsed.{ buffer, verbosity } val comps = completions(verbosity) filter (_ startsWith buffer) + val exact = comps contains buffer + val results = - if (isEmpty) comps - else if (isUnqualified && !isLastDelimiter) { - if (verbosity > 0 && (comps contains buffer)) alternativesFor(buffer) + if (parsed.isEmpty) comps + else if (parsed.isUnqualified && !parsed.isLastDelimiter) + if (verbosity > 0 && exact) alternativesFor(buffer) else comps - } - else follow(bufferHead) map (_ completionsFor bufferTail) getOrElse Nil + else follow(parsed.bufferHead) map (_ completionsFor parsed.bufferTail) getOrElse Nil results filterNot filterNotFunction map mapFunction sortWith (sortFunction _) } diff --git a/src/compiler/scala/tools/nsc/interpreter/Delimited.scala b/src/compiler/scala/tools/nsc/interpreter/Delimited.scala index 693154f7db..9d30c47820 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Delimited.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Delimited.scala @@ -9,7 +9,15 @@ package interpreter import scala.tools.jline.console.completer.ArgumentCompleter.{ ArgumentDelimiter, ArgumentList } class JLineDelimiter extends ArgumentDelimiter { - def delimit(buffer: CharSequence, cursor: Int) = Parsed(buffer.toString, cursor).asJlineArgumentList + def toJLine(args: List[String], cursor: Int) = args match { + case Nil => new ArgumentList(new Array[String](0), 0, 0, cursor) + case xs => new ArgumentList(xs.toArray, xs.size - 1, xs.last.length, cursor) + } + + def delimit(buffer: CharSequence, cursor: Int) = { + val p = Parsed(buffer.toString, cursor) + toJLine(p.args, cursor) + } def isDelimiter(buffer: CharSequence, cursor: Int) = Parsed(buffer.toString, cursor).isDelimiter } diff --git a/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala index b5b949dd78..e1eb938b3c 100644 --- a/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala @@ -19,8 +19,10 @@ import io.{ Directory, Path } */ object FileCompletion { def executionFor(buffer: String): Option[Path] = { - val p = Path(buffer) - if (p.exists) Some(p) else None + Some(Directory.Home match { + case Some(d) if buffer startsWith "~" => d / buffer.tail + case _ => Path(buffer) + }) filter (_.exists) } private def fileCompletionForwarder(buffer: String, where: Directory): List[String] = { diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 4b08907dde..0e2eb5d579 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -509,25 +509,28 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) // signal completion non-completion input has been received in.completion.resetVerbosity() - def reallyInterpret = intp.interpret(code) match { - 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 in.readLine(CONTINUATION_STRING) match { - case null => - // we know compilation is going to fail since we're at EOF and the - // parser thinks the input is still incomplete, but since this is - // a file being read non-interactively we want to fail. So we send - // it straight to the compiler for the nice error message. - intp.compileString(code) + def reallyInterpret = { + val reallyResult = intp.interpret(code) + (reallyResult, reallyResult match { + 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 - - case line => interpretStartingWith(code + "\n" + line) - } + } + else in.readLine(CONTINUATION_STRING) match { + case null => + // we know compilation is going to fail since we're at EOF and the + // parser thinks the input is still incomplete, but since this is + // a file being read non-interactively we want to fail. So we send + // it straight to the compiler for the nice error message. + intp.compileString(code) + None + + case line => interpretStartingWith(code + "\n" + line) + } + }) } /** Here we place ourselves between the user and the interpreter and examine @@ -549,12 +552,25 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) interpretStartingWith(intp.mostRecentVar + code) } else { - if (intp.isParseable(code)) reallyInterpret - else (in.completion execute code) match { - // completion took responsibility, so do not parse - // but do directly inject the result - case Some(res) => injectAndName(res) ; None - case _ => reallyInterpret // we know it will fail, this is to show the error + def runCompletion = in.completion execute code map (intp bindValue _) + /** Due to my accidentally letting file completion execution sneak ahead + * of actual parsing this now operates in such a way that the scala + * interpretation always wins. However to avoid losing useful file + * completion I let it fail and then check the others. So if you + * type /tmp it will echo a failure and then give you a Directory object. + * It's not pretty: maybe I'll implement the silence bits I need to avoid + * echoing the failure. + */ + if (intp isParseable code) { + val (code, result) = reallyInterpret + if (power != null && code == IR.Error) + runCompletion + + result + } + else runCompletion match { + case Some(_) => None // completion hit: avoid the latent error + case _ => reallyInterpret._2 // trigger the latent error } } } @@ -620,33 +636,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) true } - private def objClass(x: Any) = x.asInstanceOf[AnyRef].getClass - private def objName(x: Any) = { - val clazz = objClass(x) - clazz.getName + tpString(clazz) - } - - def tpString[T: Manifest] : String = - tpString(manifest[T].erasure) - - def tpString(clazz: Class[_]): String = { - clazz.getTypeParameters.size match { - case 0 => "" - case x => List.fill(x)("_").mkString("[", ", ", "]") - } - } - - def inject[T: Manifest](name: String, value: T): (String, String) = { - intp.bind[T](name, value) - (name, objName(value)) - } - def injectAndName(obj: Any): (String, String) = { - val name = intp.naming.freshUserVarName() - val className = objName(obj) - intp.bind(name, className, obj) - (name, className) - } - /** process command-line arguments and do as they request */ def process(args: Array[String]): Boolean = { def error1(msg: String) = out println ("scala: " + msg) diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 3231aee239..cb6971e808 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -114,8 +114,10 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { try { new _compiler.Run() compileSources List(new BatchSourceFile("<init>", source)) - if (isReplDebug || settings.debug.value) - printMessage("Repl compiler initialized.") + if (isReplDebug || settings.debug.value) { + // Can't use printMessage here, it deadlocks + Console.println("Repl compiler initialized.") + } true } catch { @@ -311,8 +313,11 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { DBG("Redefining term '%s'\n %s -> %s".format(name, t1, t2)) } } - def recordRequest(req: Request) { + if (req == null || referencedNameMap == null) { + DBG("Received null value at recordRequest.") + return + } def tripart[T](set1: Set[T], set2: Set[T]) = { val intersect = set1 intersect set2 List(set1 -- intersect, intersect, set2 -- intersect) @@ -466,11 +471,12 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { else Some(trees) } } + def isParseable(line: String): Boolean = { beSilentDuring { parse(line) match { - case Some(xs) => xs.nonEmpty - case _ => false + case Some(xs) => xs.nonEmpty // parses as-is + case None => true // incomplete } } } @@ -568,7 +574,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { if (succeeded) { if (printResults) show() - if (!synthetic) // book-keeping + if (!synthetic) // book-keeping recordRequest(req) IR.Success @@ -622,6 +628,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) def bind[T: Manifest](name: String, value: T): IR.Result = bind((name, value)) + def bindValue(x: Any): IR.Result = bind(freshUserVarName(), TypeStrings.fromValue(x), x) /** Reset this interpreter, forgetting all user-specified requests. */ def reset() { diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala index 1e84c6a829..1df850458c 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -11,6 +11,7 @@ import scala.tools.jline.console.completer._ import java.util.{ List => JList } import util.returning import Completion._ +import collection.mutable.ListBuffer // REPL completor - queries supplied interpreter for valid // completions based on current contents of buffer. @@ -117,7 +118,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput if (alts.nonEmpty) "" :: alts else Nil } - override def toString = "TypeMemberCompletion(%s)".format(tp) + override def toString = "%s (%d members)".format(tp, members.size) } class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { @@ -146,7 +147,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput override def completions(verbosity: Int) = intp.unqualifiedIds :+ "classOf" // we try to use the compiler and fall back on reflection if necessary // (which at present is for anything defined in the repl session.) - override def follow(id: String) = + override def follow(id: String) = { if (completions(0) contains id) { for (clazz <- intp clazzForIdent id) yield { // XXX The isMemberClass check is a workaround for the crasher described @@ -165,6 +166,8 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput } } else None + } + override def toString = "<repl ids> (%s)".format(completions(0).size) } // wildcard imports in the repl like "import global._" or "import String._" @@ -230,18 +233,21 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput } // the list of completion aware objects which should be consulted + // for top level unqualified, it's too noisy to let much in. lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) def topLevel = topLevelBase ++ imported + def topLevelThreshold = 50 // the first tier of top level objects (doesn't include file completion) def topLevelFor(parsed: Parsed): List[String] = { + val buf = new ListBuffer[String] topLevel foreach { ca => - ca completionsFor parsed match { - case Nil => () - case xs => return xs - } + buf ++= (ca completionsFor parsed) + + if (buf.size > topLevelThreshold) + return buf.toList.sorted } - Nil + buf.toList } // the most recent result @@ -289,18 +295,14 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput def isConsecutiveTabs(buf: String, cursor: Int) = cursor == lastCursor && buf == lastBuf - def sameHead(xs: List[String]) = { - if (xs.isEmpty || xs.head.length == 0) false - else { - val y :: ys = xs - val ch = y.head - ys forall (s => s.length > 0 && s.head == ch) - } - } // Longest common prefix def commonPrefix(xs: List[String]): String = { - if (sameHead(xs)) xs.head + commonPrefix(xs map (_.tail)) - else "" + if (xs.isEmpty || xs.contains("")) "" + else xs.head.head match { + case ch => + if (xs.tail forall (_.head == ch)) "" + ch + commonPrefix(xs map (_.tail)) + else "" + } } // This is jline's entry point for completion. @@ -310,23 +312,21 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput // we don't try lower priority completions unless higher ones return no results. def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Candidates] = { - completionFunction(p) match { - case Nil => None - case xs => - Some(Candidates( - if (xs contains "") p.cursor - else { - // update the last buffer unless this is an alternatives list - val advance = commonPrefix(xs) - lastCursor = p.position + advance.length - lastBuf = (buf take p.position) + advance - DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format( - p, lastBuf, lastCursor, p.position)) - p.position - }, - xs) - ) - } + val winners = completionFunction(p) + if (winners.isEmpty) + return None + val newCursor = + if (winners contains "") p.cursor + else { + val advance = commonPrefix(winners) + lastCursor = p.position + advance.length + lastBuf = (buf take p.position) + advance + DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format( + p, lastBuf, lastCursor, p.position)) + p.position + } + + Some(Candidates(newCursor, winners)) } def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala index be9736f1b6..6eccf1e1c5 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala @@ -49,18 +49,16 @@ class Parsed private ( def isEscaped = !isAtStart && isEscapeChar(currentChar) && !isEscapeChar(prev.currentChar) def isDelimiter = !isQuoted && !isEscaped && isDelimiterChar(currentChar) - def asJlineArgumentList = - if (isEmpty) new ArgumentList(Array[String](), 0, 0, cursor) - else new ArgumentList(args.toArray, args.size - 1, currentArg.length, cursor) - override def toString = "Parsed(%s / %d)".format(buffer, cursor) } object Parsed { + val DefaultDelimiters = "[]{},`; \t".toSet + private def onull(s: String) = if (s == null) "" else s def apply(s: String): Parsed = apply(onull(s), onull(s).length) - def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "{},`; \t" contains _) + def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, DefaultDelimiters) def apply(s: String, cursor: Int, delimited: Char => Boolean): Parsed = new Parsed(onull(s), cursor, delimited) diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index e611c5f0b6..e0b22ae511 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -60,20 +60,16 @@ class Power(repl: ILoop, intp: IMain) { /** Starts up power mode and runs whatever is in init. */ - def unleash(): Unit = { - def f = { - if (repl != null) { - intp.bind[ILoop]("repl", repl) - intp.bind[History]("history", repl.in.history) - intp.bind[Completion]("completion", repl.in.completion) - } - - intp.bind[IMain]("intp", intp) - intp.bind[Power]("power", this) - init split '\n' foreach interpret + def unleash(): Unit = beQuietDuring { + if (repl != null) { + intp.bind[ILoop]("repl", repl) + intp.bind[History]("history", repl.in.history) + intp.bind[Completion]("completion", repl.in.completion) } - if (isReplDebug) f - else beQuietDuring { f } + + intp.bind[IMain]("intp", intp) + intp.bind[Power]("power", this) + init split '\n' foreach interpret } object show { |