summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler')
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala26
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala187
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineReader.scala2
3 files changed, 187 insertions, 28 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index 4342f96a74..4a8837196d 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -869,7 +869,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
* fields and methods of x via reflection and returns their names to jline.
*/
def membersOfIdentifier(line: String): List[String] = {
- val filterMethods = List("", "hashCode", "equals", "wait", "notify", "notifyAll")
+ import Completion.{ isValidCompletion }
+ import scala.tools.nsc.util.NameTransformer.{ decode, encode } // e.g. $plus$plus => ++
+
val res = beQuietDuring {
for (name <- nameOfIdent(line) ; req <- requestForName(name)) yield {
if (interpret("val " + newInternalVarName() + " = " + name + methodsCode) != IR.Success) Nil
@@ -877,12 +879,13 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
val result = prevRequests.last.resultObjectName
val resultObj = Class.forName(result, true, classLoader)
val valMethod = resultObj.getMethod("result")
+ val str = valMethod.invoke(resultObj).toString
- valMethod.invoke(resultObj).toString .
- split(" ").toList .
- filter(x => !filterMethods.contains(x)) .
- filter(!_.contains("$")) .
- removeDuplicates
+ str.substring(str.indexOf('=') + 1).trim .
+ split(" ").toList .
+ map(decode) .
+ filter(isValidCompletion) .
+ removeDuplicates
}
}
}
@@ -891,11 +894,16 @@ class Interpreter(val settings: Settings, out: PrintWriter) {
}
/** Another entry point for tab-completion, ids in scope */
- def unqualifiedIds(line: String): List[String] =
+ def unqualifiedIds(): List[String] =
allBoundNames .
map(_.toString) .
- filter(!isSynthVarName(_)) .
- filter(_ startsWith line)
+ filter(!isSynthVarName(_))
+
+ /** For static/object method completion */
+ def tryToLoadClass(path: String): Option[Class[_]] = {
+ try { Some(Class.forName(path, false, classLoader)) }
+ catch { case _: ClassNotFoundException | _: NoClassDefFoundError => None }
+ }
// debugging
private var debuggingOutput = false
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
index cf804f4669..6bfb5cdea1 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
@@ -7,34 +7,183 @@
package scala.tools.nsc.interpreter
import jline._
+import java.net.URL
+import java.util.concurrent.ConcurrentHashMap
// REPL completor - queries supplied interpreter for valid completions
// based on current contents of buffer.
class Completion(val interpreter: Interpreter) extends Completor {
+ import Completion._
import java.util.{ List => JList }
+ import interpreter.compilerClasspath
- override def complete(_buffer: String, cursor: Int, candidates: JList[_]): Int = {
- val buffer = if (_buffer == null) "" else _buffer
- val clist = candidates.asInstanceOf[JList[String]]
+ // it takes a little while to look through the jars so we use a future and a concurrent map
+ val dottedPaths = new ConcurrentHashMap[String, List[String]]
+
+ private var doneExaminingJars = false
+ scala.concurrent.ops.future {
+ getDottedPaths(dottedPaths, interpreter)
+ doneExaminingJars = true
+ }
+
+ // Would like to find a nicer way to do this, but this works for now
+ lazy val topLevelPackagesVal = topLevelPackagesDef
+ def topLevelPackagesDef() = enumToList(dottedPaths.keys) filter (x => !x.contains('.'))
+ def topLevelPackages = if (doneExaminingJars) topLevelPackagesVal else topLevelPackagesDef
+
+ // One instance of a command line
+ class Buffer(s: String) {
+ val buffer = if (s == null) "" else s
+ val segments = buffer.split("\\.", -1).toList
val lastDot = buffer.lastIndexOf('.')
- val (path, stub) =
- if (lastDot < 0) (buffer, "")
- else (buffer.substring(0, lastDot), buffer.substring(lastDot + 1))
-
- def completeMembers(needsDot: Boolean): Int = {
- def withDot(s: String) = if (needsDot) "." + s else s
- val members = interpreter membersOfIdentifier path
- members.filter(_ startsWith stub).foreach(x => clist add withDot(x))
- buffer.length - stub.length
+ val hasDot = segments.size > 0 && segments.last == ""
+
+ // given foo.bar.baz, path = foo.bar and stub = baz
+ val (path, stub) = segments.size match {
+ case 0 => ("", "")
+ case 1 => (segments.head, "")
+ case _ => (segments.init.mkString("."), segments.last)
+ }
+
+ def filt(xs: List[String]) = xs filter (_ startsWith stub)
+
+ case class Result(candidates: List[String], position: Int) {
+ def getCandidates() = candidates.map(_.trim).removeDuplicates.sort(_ < _)
+ }
+
+ // work out completion candidates and position
+ def analyzeBuffer(clist: JList[String]): Result = {
+ lazy val ids = idsStartingWith(path)
+ lazy val pkgs = pkgsStartingWith(path)
+ lazy val count = (ids ::: pkgs).size
+
+ def doSimple(): Result = count match {
+ case 0 => Result(Nil, 0)
+ case 1 if pkgs.size > 0 => Result(pkgs, 0)
+ case 1 if buffer.length < ids.head.length => Result(ids, 0)
+ case 1 => Result(ids, 0)
+ // XXX for now commented out "dot inference" because it's overcomplicated
+ // val members = membersOfId(ids.head) filter (_ startsWith stub)
+ // if (members.isEmpty) Result(Nil, 0)
+ // else Result(members, path.length + 1)
+ case _ => Result(ids ::: pkgs, 0)
+ }
+
+ def doDotted(): Result = {
+ lazy val pkgs = filt(membersOfPath(path))
+ lazy val ids = filt(membersOfId(path))
+ lazy val statics = filt(completeStaticMembers(path))
+
+ if (!pkgs.isEmpty) Result(pkgs, path.length + 1)
+ else if (!ids.isEmpty) Result(ids, path.length + 1)
+ else Result(statics, path.length + 1)
+ }
+
+ segments.size match {
+ case 0 => Result(Nil, 0)
+ case 1 => doSimple()
+ case _ => doDotted()
+ }
}
- // if there is no dot, complete on unqualified identifiers; otherwise, id's members
- lazy val ids = interpreter unqualifiedIds path
- if (lastDot >= 0) completeMembers(false)
- else ids match {
- case Nil => 0
- case x :: Nil if x == path => completeMembers(true) // only one id matches, insert the dot
- case xs => xs foreach (x => clist add x) ; 0
+ def isValidId(s: String) = interpreter.unqualifiedIds contains s
+ def membersOfId(s: String) = interpreter membersOfIdentifier s
+
+ def isValidPath(s: String) = dottedPaths containsKey s
+ def membersOfPath(s: String) = if (isValidPath(s)) dottedPaths get s else Nil
+
+ def pkgsStartingWith(s: String) = topLevelPackages filter (_ startsWith s)
+ def idsStartingWith(s: String) = interpreter.unqualifiedIds filter (_ startsWith s)
+
+ def complete(clist: JList[String]): Int = {
+ val res = analyzeBuffer(clist)
+ res.getCandidates foreach (clist add _)
+ res.position
}
}
+
+ // jline's completion comes through here - we ask a Buffer for the candidates.
+ override def complete(_buffer: String, cursor: Int, candidates: JList[_]): Int =
+ new Buffer(_buffer).complete(candidates.asInstanceOf[JList[String]])
+
+ def completeStaticMembers(path: String): List[String] = {
+ import java.lang.reflect.Modifier.{ isPrivate, isProtected, isStatic }
+ def isVisible(x: Int) = !isPrivate(x) && !isProtected(x)
+ def isSingleton(x: Int, isJava: Boolean) = !isJava || isStatic(x)
+
+ def getMembers(c: Class[_], isJava: Boolean): List[String] =
+ c.getMethods.toList .
+ filter (x => isVisible(x.getModifiers)) .
+ filter (x => isSingleton(x.getModifiers, isJava)) .
+ map (_.getName) .
+ filter (isValidCompletion)
+
+ // java style, static methods
+ val js = interpreter.tryToLoadClass(path).map(getMembers(_, true)) getOrElse Nil
+ // scala style, methods on companion object
+ val ss = interpreter.tryToLoadClass(path + "$").map(getMembers(_, false)) getOrElse Nil
+
+ js ::: ss
+ }
}
+
+object Completion
+{
+ import java.io.File
+ import java.util.jar.{ JarEntry, JarFile }
+
+ def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToList(e, Nil)
+ def enumToList[T](e: java.util.Enumeration[T], xs: List[T]): List[T] =
+ if (e == null || !e.hasMoreElements) xs else enumToList(e, e.nextElement :: xs)
+
+ // methods to leave out of completion
+ val excludeMethods = List("", "hashCode", "equals", "wait", "notify", "notifyAll")
+
+ private def exists(path: String) = new File(path) exists
+
+ def isValidCompletion(x: String) = !(x contains "$") && !(excludeMethods contains x)
+ def isClass(x: String) = x endsWith ".class"
+ def dropClass(x: String) = x.substring(0, x.length - 6) // drop .class
+
+ def getClassFiles(path: String): List[String] = {
+ if (!exists(path)) return Nil
+
+ enumToList(new JarFile(path).entries) .
+ map (_.getName) .
+ filter (isClass) .
+ map (dropClass) .
+ filter (isValidCompletion)
+ }
+
+ // all the dotted path to classfiles we can find by poking through the jars
+ def getDottedPaths(
+ map: ConcurrentHashMap[String, List[String]],
+ interpreter: Interpreter): Unit =
+ {
+ val cp =
+ interpreter.compilerClasspath.map(_.getPath) ::: // compiler jars, scala-library.jar etc.
+ interpreter.settings.bootclasspath.value.split(':').toList // boot classpath, java.lang.* etc.
+
+ val jars = cp.removeDuplicates filter (_ endsWith ".jar")
+
+ // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C)
+ def subpaths(s: String): List[(String, String)] = {
+ val segs = s.split('.')
+ for (i <- List.range(0, segs.length - 1)) yield {
+ val k = segs.take(i+1).mkString(".")
+ val v = segs(i+1)
+ (k -> v)
+ }
+ }
+
+ def oneJar(jar: String): Unit = {
+ val classfiles = Completion.getClassFiles(jar).map(_.replace('/', '.'))
+ for (cl <- classfiles; (k, v) <- subpaths(cl)) {
+ if (map containsKey k) map.put(k, v :: map.get(k))
+ else map.put(k, List(v))
+ }
+ }
+
+ jars foreach oneJar
+ }
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
index 0e9d494206..0f9a87518a 100644
--- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
@@ -32,6 +32,8 @@ class JLineReader(interpreter: Interpreter) extends InteractiveReader {
val comp = new ArgumentCompletor(new Completion(interpreter), delims)
comp setStrict false
r addCompletor comp
+ // XXX make this use a setting
+ r setAutoprintThreshhold 250
}
r