summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-01-23 20:30:01 +0000
committerPaul Phillips <paulp@improving.org>2010-01-23 20:30:01 +0000
commita0c0f0949797a563c9583d0f82c0f390a999ec7d (patch)
tree59a21db321f764372f3ad85930c527f7a1571f0e /src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
parentbb6e5958e63e3d70cd1f1a86f3fa0b5f3b670d8a (diff)
downloadscala-a0c0f0949797a563c9583d0f82c0f390a999ec7d.tar.gz
scala-a0c0f0949797a563c9583d0f82c0f390a999ec7d.tar.bz2
scala-a0c0f0949797a563c9583d0f82c0f390a999ec7d.zip
Another big REPL patch.
a proper commit message, I will just say it adds a couple of pretty frabjous features, in addition to cleaning up a whole bunch of questionable code. * Tab-completion now chains through intermediate results on fields and 0-arg methods * You can now define custom Completors which define their own contents. Details and demos to come in a wiki document about the repl.
Diffstat (limited to 'src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala')
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala129
1 files changed, 129 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
new file mode 100644
index 0000000000..f1c6717b2b
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
@@ -0,0 +1,129 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import java.net.URL
+import java.lang.reflect
+import java.util.concurrent.ConcurrentHashMap
+import scala.concurrent.DelayedLazyVal
+import scala.util.NameTransformer.{ decode, encode }
+import PackageCompletion._
+
+/** Completion among all known packages. It examines the jars in a
+ * separate thread so as not to slow down startup. If it arrives at
+ * an object, it delegates to StaticCompletion for that object.
+ */
+class PackageCompletion(classpath: List[URL]) extends CompletionAware {
+ // it takes a little while to look through the jars so we use a future and a concurrent map
+ class CompletionAgent {
+ val dottedPaths: ConcurrentHashMap[String, List[CompletionInfo]] = new ConcurrentHashMap[String, List[CompletionInfo]]
+ val topLevelPackages = new DelayedLazyVal(
+ () => enumToList(dottedPaths.keys) filterNot (_ contains '.'),
+ getDottedPaths(dottedPaths, classpath)
+ )
+ }
+ val agent = new CompletionAgent
+ import agent._
+
+ def completions() = topLevelPackages()
+ override def follow(id: String) =
+ if (dottedPaths containsKey id) Some(new SubCompletor(id))
+ else None
+
+ class SubCompletor(root: String) extends CompletionAware {
+ private def infos = dottedPaths get root
+ def completions() = infos map (_.visibleName)
+
+ override def follow(segment: String): Option[CompletionAware] = {
+ PackageCompletion.this.follow(root + "." + segment) orElse {
+ for (CompletionInfo(`segment`, className, _) <- infos) {
+ return Some(new StaticCompletion(className))
+ }
+ None
+ }
+ }
+ }
+}
+
+object PackageCompletion {
+ import java.io.File
+ import java.util.jar.{ JarEntry, JarFile }
+ import scala.tools.nsc.io.Streamable
+
+ def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToListInternal(e, Nil)
+ private def enumToListInternal[T](e: java.util.Enumeration[T], xs: List[T]): List[T] =
+ if (e == null || !e.hasMoreElements) xs else enumToListInternal(e, e.nextElement :: xs)
+
+ def getClassFiles(path: String): List[String] = {
+ def exists(path: String) = { new File(path) exists }
+ if (!exists(path)) return Nil
+
+ (enumToList(new JarFile(path).entries) map (_.getName)) .
+ partialMap { case x: String if x endsWith ".class" => x dropRight 6 } .
+ filterNot { ReflectionCompletion.shouldHide }
+ }
+
+ case class CompletionInfo(visibleName: String, className: String, jar: String) {
+ lazy val jarfile = new JarFile(jar)
+ lazy val entry = jarfile getEntry className
+
+ override def hashCode = visibleName.hashCode
+ override def equals(other: Any) = other match {
+ case x: CompletionInfo => visibleName == x.visibleName
+ case _ => false
+ }
+
+ def getBytes(): Array[Byte] = {
+ if (entry == null) Array() else {
+ val x = new Streamable.Bytes { def inputStream() = jarfile getInputStream entry }
+ x.toByteArray()
+ }
+ }
+ }
+
+ // all the dotted path to classfiles we can find by poking through the jars
+ def getDottedPaths(map: ConcurrentHashMap[String, List[CompletionInfo]], classpath: List[URL]): Unit = {
+ val cp = classpath map (_.getPath)
+ val jars = cp.removeDuplicates filter (_ endsWith ".jar")
+
+ // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C)
+ // and scala.Range$BigInt needs to go scala -> Range -> BigInt
+ def subpaths(s: String): List[(String, String)] = {
+ val segs = decode(s).split("""[/.]""")
+ val components = segs dropRight 1
+
+ (1 to components.length).toList flatMap { i =>
+ val k = components take i mkString "."
+ if (segs(i) contains "$") {
+ val dollarsegs = segs(i).split("$").toList
+ for (j <- 1 to (dollarsegs.length - 1) toList) yield {
+ val newk = k + "." + (dollarsegs take j mkString ".")
+ (k -> dollarsegs(j))
+ }
+ }
+ else List(k -> segs(i))
+ }
+ }
+
+ def oneJar(jar: String): Unit = {
+ val classfiles = getClassFiles(jar)
+
+ for (cl <- classfiles.removeDuplicates ; (k, _v) <- subpaths(cl)) {
+ val v = CompletionInfo(_v, cl, jar)
+
+ if (map containsKey k) {
+ val vs = map.get(k)
+ if (vs contains v) ()
+ else map.put(k, v :: vs)
+ }
+ else map.put(k, List(v))
+ }
+ }
+
+ jars foreach oneJar
+ }
+} \ No newline at end of file