diff options
author | Paul Phillips <paulp@improving.org> | 2010-01-23 20:30:01 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-01-23 20:30:01 +0000 |
commit | a0c0f0949797a563c9583d0f82c0f390a999ec7d (patch) | |
tree | 59a21db321f764372f3ad85930c527f7a1571f0e /src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala | |
parent | bb6e5958e63e3d70cd1f1a86f3fa0b5f3b670d8a (diff) | |
download | scala-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.scala | 129 |
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 |