summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-01-31 03:33:53 +0000
committerPaul Phillips <paulp@improving.org>2011-01-31 03:33:53 +0000
commit174a25e1b3bc054920a9418761bf9f4a8c3d9e79 (patch)
tree6ebde03729b33aab7a88eccfaf4f114b9dbd6411
parentc5f20ad02b6d1806e8d0dbca6639630726afc5ee (diff)
downloadscala-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.
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala5
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala14
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Delimited.scala10
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/FileCompletion.scala6
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ILoop.scala91
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/IMain.scala19
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala68
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Parsed.scala8
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Power.scala22
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 {