summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-02-08 23:09:49 +0000
committerPaul Phillips <paulp@improving.org>2010-02-08 23:09:49 +0000
commitb627de8553e3e47efdc9ee7c2db59fbaf142e576 (patch)
tree7600e6d62f1f1279485782e98c8298187d182d98
parent171d21f11a58e905de8f96a9bac41f6aced3e384 (diff)
downloadscala-b627de8553e3e47efdc9ee7c2db59fbaf142e576.tar.gz
scala-b627de8553e3e47efdc9ee7c2db59fbaf142e576.tar.bz2
scala-b627de8553e3e47efdc9ee7c2db59fbaf142e576.zip
Until now directories on the classpath were not...
Until now directories on the classpath were not considered for repl completion because of the risk of accidentally traversing large chunks of the filesystem (e.g. "." in the path, run from /). Some low hanging fruit was available - at least SCALA_HOME can be considered safe, and then we get the scala classes. The main impact of this is that completion now works for the built-in classes when you run build/quick/bin/scala. Review by community.
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala83
1 files changed, 58 insertions, 25 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
index 5ee8877124..ff5c4b3d8b 100644
--- a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
@@ -9,6 +9,8 @@ package interpreter
import java.net.URL
import java.lang.reflect
import java.util.concurrent.ConcurrentHashMap
+import io.{ Path, Directory, File, Streamable }
+import scala.tools.util.PathResolver.Defaults.scalaHomeDir
import scala.concurrent.DelayedLazyVal
import scala.reflect.NameTransformer.{ decode, encode }
import PackageCompletion._
@@ -51,7 +53,7 @@ class PackageCompletion(classpath: List[URL]) extends CompletionAware {
override def follow(segment: String): Option[CompletionAware] = {
PackageCompletion.this.follow(root + "." + segment) orElse {
- for (CompletionInfo(`segment`, className, _) <- infos) {
+ for (CompletionInfo(`segment`, className) <- infos) {
return Some(new StaticCompletion(className))
}
@@ -63,9 +65,7 @@ class PackageCompletion(classpath: List[URL]) extends CompletionAware {
}
object PackageCompletion {
- import java.io.File
import java.util.jar.{ JarEntry, JarFile }
- import scala.tools.nsc.io.Streamable
val EXPAND_SEPARATOR_STRING = "$$"
val ANON_CLASS_NAME = "$anon"
@@ -83,24 +83,44 @@ object PackageCompletion {
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)) Nil else {
- (enumToList(new JarFile(path).entries) map (_.getName))
- . partialMap { case x: String if x endsWith ".class" => x dropRight 6 }
- . filterNot { ignoreClassName }
+ private def isClass(s: String) = s endsWith ".class"
+ private def processNames(xs: List[String]) = xs map (_ dropRight 6) filterNot ignoreClassName distinct
+
+ def getDirClassFiles(dir: Directory): List[String] =
+ processNames(dir.deepList() map (dir relativize _ path) filter isClass toList)
+
+ def getJarClassFiles(jar: File): List[String] =
+ if (!jar.exists) Nil
+ else processNames(enumToList(new JarFile(jar.path).entries) map (_.getName) filter isClass)
+
+ object CompletionInfo {
+ def unapply(that: Any) = that match {
+ case x: CompletionInfo => Some((x.visibleName, x.className))
+ case _ => None
}
}
- case class CompletionInfo(visibleName: String, className: String, jar: String) {
- lazy val jarfile = new JarFile(jar)
- lazy val entry = jarfile getEntry className
+ abstract class CompletionInfo {
+ def visibleName: String
+ def className: String
+ def getBytes(): Array[Byte]
override def hashCode = visibleName.hashCode
override def equals(other: Any) = other match {
case x: CompletionInfo => visibleName == x.visibleName
case _ => false
}
+ }
+
+ case class DirCompletionInfo(visibleName: String, className: String, dir: Directory) extends CompletionInfo {
+ lazy val file = dir / File(className)
+
+ def getBytes(): Array[Byte] = try file.toByteArray() catch { case _: Exception => Array() }
+ }
+
+ case class JarCompletionInfo(visibleName: String, className: String, jar: File) extends CompletionInfo {
+ lazy val jarfile = new JarFile(jar.path)
+ lazy val entry = jarfile getEntry className
def getBytes(): Array[Byte] = {
if (entry == null) Array() else {
@@ -112,8 +132,16 @@ object PackageCompletion {
// 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.distinct filter (_ endsWith ".jar")
+ val cp = classpath.distinct map (x => Path(x.getPath))
+ val jars = cp filter (_ hasExtension "jar") map (_.toFile)
+
+ /** If we process all dirs uncritically, someone who has '.' in their classpath and
+ * runs scala from the filesystem root directory will induce a traversal of their
+ * entire filesystem. We could apply some heuristics to avoid this, but for now we
+ * will look only in the scalaHome directories, which is most of what we want.
+ */
+ def isUnderScalaHome(d: Directory) = d.parents exists (_ == scalaHomeDir)
+ val dirs = cp partialMap { case x: Directory => x } filter isUnderScalaHome
// 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
@@ -134,21 +162,26 @@ object PackageCompletion {
}
}
- def oneJar(jar: String): Unit = {
- val classfiles = getClassFiles(jar)
+ def addToMap(key: String, info: CompletionInfo) = {
+ if (map containsKey key) {
+ val vs = map.get(key)
+ if (vs contains info) ()
+ else map.put(key, info :: vs)
+ }
+ else map.put(key, List(info))
+ }
- for (cl <- classfiles.distinct ; (k, _v) <- subpaths(cl)) {
- val v = CompletionInfo(_v, cl, jar)
+ def oneDir(dir: Directory) {
+ for (cl <- getDirClassFiles(dir) ; (k, v) <- subpaths(cl))
+ addToMap(k, DirCompletionInfo(v, cl, dir))
+ }
- 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))
- }
+ def oneJar(jar: File) {
+ for (cl <- getJarClassFiles(jar) ; (k, v) <- subpaths(cl))
+ addToMap(k, JarCompletionInfo(v, cl, jar))
}
jars foreach oneJar
+ dirs foreach oneDir
}
} \ No newline at end of file