diff options
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/scala/tools/nsc/Interpreter.scala | 26 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/Completion.scala | 187 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/JLineReader.scala | 2 |
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 |