diff options
author | Paul Phillips <paulp@improving.org> | 2010-04-25 23:20:59 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-04-25 23:20:59 +0000 |
commit | ce27db80214ee574c6eace6339b7ab0a06344a61 (patch) | |
tree | b005095b93f7a5770227bd3971fcb83a5a5a19f4 | |
parent | bd66ed93afc0b6b1bb7293f7930ab6c2d2c55a49 (diff) | |
download | scala-ce27db80214ee574c6eace6339b7ab0a06344a61.tar.gz scala-ce27db80214ee574c6eace6339b7ab0a06344a61.tar.bz2 scala-ce27db80214ee574c6eace6339b7ab0a06344a61.zip |
Some overdue improvements in repl completion, w...
Some overdue improvements in repl completion, which has been largely
awol since the pickler format change. Where possible, completion is now
done using the compiler's internal model rather than reflection. Many
handy things now work which did not before, such as wildcard imports
causing all imported identifiers to henceforth be completable. Note also
that there is a verbosity counter now, so hitting tab twice may yield
more results than hitting it once.
scala> import java.util.concurrent.atomic._
import java.util.concurrent.atomic._
scala> Atomic<tab><tab>
AtomicBoolean AtomicInteger
AtomicIntegerArray AtomicIntegerFieldUpdater
AtomicLong AtomicLongArray
[etc]
Review by community.
10 files changed, 259 insertions, 405 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index d3f51541bf..a494352b9b 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -236,6 +236,12 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private val boundNameMap = new HashMap[Name, Request]() private def allHandlers = prevRequests.toList flatMap (_.handlers) + def printAllTypeOf = { + prevRequests foreach { req => + req.typeOf foreach { case (k, v) => Console.println(k + " => " + v) } + } + } + /** Most recent tree handled which wasn't wholly synthetic. */ private def mostRecentlyHandledTree: Option[Tree] = { for { @@ -511,11 +517,13 @@ class Interpreter(val settings: Settings, out: PrintWriter) { def mkType(id: String): compiler.Type = { // if it's a recognized identifier, the type of that; otherwise treat the - // String like it is itself a type (e.g. scala.collection.Map) . - val typeName = typeForIdent(id) getOrElse id + // String like a value (e.g. scala.collection.Map) . + def findType = typeForIdent(id) match { + case Some(x) => definitions.getClass(newTermName(x)).tpe + case _ => definitions.getModule(newTermName(id)).tpe + } - try definitions.getClass(newTermName(typeName)).tpe - catch { case _: Throwable => NoType } + try findType catch { case _: MissingRequirementError => NoType } } private[nsc] val powerMkImports = List( @@ -796,16 +804,32 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } private class ImportHandler(imp: Import) extends MemberHandler(imp) { + lazy val Import(expr, selectors) = imp + def targetType = mkType(expr.toString) match { + case NoType => None + case x => Some(x) + } + + private def selectorWild = selectors filter (_.name == USCOREkw) // wildcard imports, e.g. import foo._ + private def selectorMasked = selectors filter (_.rename == USCOREkw) // masking imports, e.g. import foo.{ bar => _ } + private def selectorNames = selectors map (_.name) + private def selectorRenames = selectors map (_.rename) filterNot (_ == null) + /** Whether this import includes a wildcard import */ - val importsWildcard = imp.selectors map (_.name) contains USCOREkw + val importsWildcard = selectorWild.nonEmpty + + /** Complete list of names imported by a wildcard */ + def wildcardImportedNames: List[Name] = ( + for (tpe <- targetType ; if importsWildcard) yield + tpe.nonPrivateMembers filter (x => x.isMethod && x.isPublic) map (_.name) distinct + ).toList.flatten /** The individual names imported by this statement */ - val importedNames: List[Name] = ( - imp.selectors - . map (x => x.rename) - . filter (x => x != null && x != USCOREkw) - . flatMap (x => List(x.toTypeName, x.toTermName)) - ) + /** XXX come back to this and see what can be done with wildcards now that + * we know how to enumerate the identifiers. + */ + val importedNames: List[Name] = + selectorRenames filterNot (_ == USCOREkw) flatMap (x => List(x.toTypeName, x.toTermName)) override def resultExtractionCode(req: Request, code: PrintWriter) = code println codegenln(imp.toString) @@ -830,9 +854,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** def and val names */ def defNames = partialFlatMap(handlers) { case x: DefHandler => x.boundNames } - def valAndVarNames = partialFlatMap(handlers) { + def valueNames = partialFlatMap(handlers) { case x: AssignHandler => List(x.helperName) case x: ValHandler => boundNames + case x: ModuleHandler => List(x.name) } /** Code to import bound names from previous lines - accessPath is code to @@ -940,7 +965,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { !reporter.hasErrors } - def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) /** The outermost wrapper object */ @@ -972,7 +996,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - getTypes(valAndVarNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) + getTypes(valueNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) } /** load and run the code using reflection */ @@ -1052,11 +1076,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private def requestForName(name: Name): Option[Request] = prevRequests.reverse find (_.boundNames contains name) - private def requestForIdent(line: String): Option[Request] = - requestForName(newTermName(line)) + private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) def typeForIdent(id: String): Option[String] = - requestForIdent(id) map (_ typeOf newTermName(id)) + requestForIdent(id) flatMap (x => x.typeOf get newTermName(id)) def methodsOf(name: String) = evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) @@ -1160,6 +1183,22 @@ class Interpreter(val settings: Settings, out: PrintWriter) { case x: ImportHandler => x.importedNames } filterNot isSynthVarName + /** Types which have been wildcard imported, such as: + * val x = "abc" ; import x._ // type java.lang.String + * import java.lang.String._ // object java.lang.String + * + * Used by tab completion. + * + * XXX right now this gets import x._ and import java.lang.String._, + * but doesn't figure out import String._. There's a lot of ad hoc + * scope twiddling which should be swept away in favor of digging + * into the compiler scopes. + */ + def wildcardImportedTypes(): List[Type] = { + val xs = allHandlers collect { case x: ImportHandler if x.importsWildcard => x.targetType } + xs.flatten.reverse.distinct + } + /** Another entry point for tab-completion, ids in scope */ def unqualifiedIds() = (unqualifiedIdNames() map (_.toString)).distinct.sorted @@ -1191,6 +1230,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { // debugging def isReplDebug = settings.Yrepldebug.value + def isCompletionDebug = settings.Ycompletion.value def DBG(s: String) = if (isReplDebug) out println s else () } diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index aadb8700de..4dd6c88eaa 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -217,9 +217,9 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite val powerCommands: List[Command] = { import CommandImplicits._ List( + OneArg("completions", "generate list of completions for a given String", completions), VarArgs("dump", "displays a view of the interpreter's internal state", - (xs: List[String]) => interpreter dumpState xs), - OneArg("search", "search the classpath for classes matching regex", search) + (xs: List[String]) => interpreter dumpState xs) // VarArgs("tree", "displays ASTs for specified identifiers", // (xs: List[String]) => interpreter dumpTrees xs) @@ -331,35 +331,11 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite else out.println("The path '" + f + "' doesn't seem to exist.") } - /** This isn't going to win any efficiency awards, but it's only - * available in power mode so I'm unconcerned for the moment. - */ - def search(arg: String) { - val MAX_RESULTS = 40 - if (in.completion.isEmpty) return println("No classpath data available") - val comp = in.completion.get - - import java.util.regex.PatternSyntaxException - import comp.pkgs.agent._ - import scala.collection.JavaConversions._ + def completions(arg: String): Unit = { + val comp = in.completion getOrElse { return println("Completion unavailable.") } + val xs = comp completions arg - try { - val regex = arg.r - val matches = ( - for ((k, vs) <- dottedPaths) yield { - val pkgs = if (regex findFirstMatchIn k isDefined) List("package " + k) else Nil - val classes = vs filter (regex findFirstMatchIn _.visibleName isDefined) map (" class " + k + "." + _.visibleName) - - pkgs ::: classes - } - ).flatten - - matches take MAX_RESULTS foreach println - } - catch { - case _: PatternSyntaxException => - return println("Invalid regular expression: you must use java.util.regex.Pattern syntax.") - } + injectAndName(xs) } def power() { @@ -496,6 +472,9 @@ class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWrite * to be recorded for replay, if any. */ def interpretStartingWith(code: String): Option[String] = { + // signal completion non-completion input has been received + in.completion foreach (_.resetVerbosity()) + def reallyInterpret = { interpreter.interpret(code) match { case IR.Error => None diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index b90da5bf98..bfb375bf67 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -3,17 +3,6 @@ * @author Paul Phillips */ -// -// TODO, if practical: -// -// 1) Types: val s: String = x.<tab> should only show members which result in a String. -// Possible approach: evaluate buffer as if current identifier is -// 2) Implicits: x.<tab> should show not only x's members but those of anything for which -// there is an implicit conversion from x. -// 4) Imports: after import scala.collection.mutable._, HashMap should be among -// my top level identifiers. -// 5) Caching: parsing the jars every startup seems wasteful, but experimentally -// there is little to no gain from caching. package scala.tools.nsc package interpreter @@ -26,12 +15,6 @@ import scala.tools.util.PathResolver import io.{ Path, Directory } object Completion { - // methods to leave out of completion - val excludeMethods = List("hashCode", "equals", "wait", "notify", "notifyAll") - - // strings to look for an exclude by default - val excludeStrings = List("$$super", "MODULE$") - def looksLikeInvocation(code: String) = ( (code != null) && (code startsWith ".") @@ -40,79 +23,205 @@ object Completion { && !(code startsWith "..") ) - trait Forwarder extends CompletionAware { - def forwardTo: Option[CompletionAware] - - override def completions() = forwardTo map (_.completions()) getOrElse Nil - override def follow(s: String) = forwardTo flatMap (_ follow s) + object Forwarder { + def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { + def completions() = forwardTo() map (_.completions()) getOrElse Nil + override def follow(s: String) = forwardTo() flatMap (_ follow s) + } } } import Completion._ // REPL completor - queries supplied interpreter for valid // completions based on current contents of buffer. -class Completion(repl: Interpreter) { - self => +class Completion(val repl: Interpreter) { + // verbosity goes up with consecutive tabs + private var verbosity: Int = 0 + def resetVerbosity() = verbosity = 0 - private lazy val classPath = repl.compilerClasspath + def isCompletionDebug = repl.isCompletionDebug + def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString) - // the unqualified vals/defs/etc visible in the repl - val ids = new IdentCompletion(repl) - // the top level packages we know about - val pkgs = new PackageCompletion(classPath) - // members of Predef - val predef = new StaticCompletion(classOf[scala.Predef$]) { - override def filterNotFunction(s: String) = ( - (s contains "2") || - (s startsWith "wrap") || - (s endsWith "Wrapper") || - (s endsWith "Ops") - ) + lazy val global: repl.compiler.type = repl.compiler + import global._ + import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } + + // XXX not yet used. + lazy val dottedPaths = { + def walk(tp: Type): scala.List[Symbol] = { + val pkgs = tp.nonPrivateMembers filter (_.isPackage) + pkgs ++ (pkgs map (_.tpe) flatMap walk) + } + walk(RootClass.tpe) } - // members of scala.* - val scalalang = new pkgs.SubCompletor("scala") with Forwarder { - def forwardTo = pkgs follow "scala" - val arityClasses = { - val names = List("Tuple", "Product", "Function") - val expanded = for (name <- names ; index <- 0 to 22 ; dollar <- List("", "$")) yield name + index + dollar - Set(expanded: _*) + def getType(name: String, isModule: Boolean) = { + val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) + try Some(f(name).tpe) + catch { case _: MissingRequirementError => None } + } + + def typeOf(name: String) = getType(name, false) + def moduleOf(name: String) = getType(name, true) + + trait CompilerCompletion { + def tp: Type + def effectiveTp = tp match { + case MethodType(Nil, resType) => resType + case _ => tp } - override def filterNotFunction(s: String) = { - val simple = s.reverse takeWhile (_ != '.') reverse + // for some reason any's members don't show up in subclasses, which + // we need so 5.<tab> offers asInstanceOf etc. + private def anyMembers = AnyClass.tpe.nonPrivateMembers + def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") + + def tos(sym: Symbol) = sym.name.decode.toString + def memberNamed(s: String) = members find (x => tos(x) == s) + def hasMethod(s: String) = methods exists (x => tos(x) == s) + + def members = (effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic) + def methods = members filter (_.isMethod) + def packages = members filter (_.isPackage) + def aliases = members filter (_.isAliasType) + + def memberNames = members map tos + def methodNames = methods map tos + def packageNames = packages map tos + def aliasNames = aliases map tos + } - (arityClasses contains simple) || - (s endsWith "Exception") || - (s endsWith "Error") + object TypeMemberCompletion { + def apply(tp: Type): TypeMemberCompletion = { + if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) + else new TypeMemberCompletion(tp) } + def imported(tp: Type) = new ImportCompletion(tp) } - // members of java.lang.* - val javalang = new pkgs.SubCompletor("java.lang") with Forwarder { - def forwardTo = pkgs follow "java.lang" - import reflect.Modifier.isPublic - private def existsAndPublic(s: String): Boolean = { - val name = if (s contains ".") s else "java.lang." + s - val clazz = classForName(name) getOrElse (return false) - - isPublic(clazz.getModifiers) + + class TypeMemberCompletion (val tp: Type) extends CompletionAware with CompilerCompletion { + def excludeEndsWith: List[String] = Nil + def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. + def excludeNames: List[String] = anyref.methodNames -- anyRefMethodsToShow ++ List("_root_") + + def exclude(name: String): Boolean = ( + (name contains "$") || + (excludeNames contains name) || + (excludeEndsWith exists (name endsWith _)) || + (excludeStartsWith exists (name startsWith _)) + ) + def filtered(xs: List[String]) = xs filterNot exclude distinct + def completions = { + returning(filtered(memberNames))(xs => DBG("%s completions: %s".format(tp, xs))) + } + + override def follow(s: String): Option[CompletionAware] = + memberNamed(s) map (x => TypeMemberCompletion(x.tpe)) + + override def toString = "TypeMemberCompletion(%s)".format(tp) + } + + class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def excludeNames = anyref.methodNames + } + + class LiteralCompletion(lit: Literal) extends TypeMemberCompletion(lit.value.tpe) { + private lazy val completions0 = filtered(memberNames) + private lazy val completions1 = memberNames + override def completions = if (verbosity == 0) completions0 else completions1 + } + + class ImportCompletion(tp: Type) extends TypeMemberCompletion(tp) { + private lazy val completions0 = filtered(methods filterNot (_.isSetter) map tos) + private lazy val completions1 = super.completions + override def completions = if (verbosity == 0) completions0 else completions1 + } + + // not for completion but for excluding + object anyref extends TypeMemberCompletion(AnyRefClass.tpe) { } + + // the unqualified vals/defs/etc visible in the repl + object ids extends CompletionAware { + def completions() = repl.unqualifiedIds ::: List("classOf") + // we try to use the compiler and fall back on reflection if necessary + // (which at present is for anything defined in the repl session.) + override def follow(id: String) = + if (completions contains id) { + for (clazz <- repl clazzForIdent id) yield { + (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) + } + } + else None + } + + // wildcard imports in the repl like "import global._" or "import String._" + private def imported = repl.wildcardImportedTypes map TypeMemberCompletion.imported + + // literal Ints, Strings, etc. + object literals extends CompletionAware { + def simpleParse(code: String): Tree = { + val unit = new CompilationUnit(new util.BatchSourceFile("<console>", code)) + val scanner = new syntaxAnalyzer.UnitParser(unit) + val tss = scanner.templateStatSeq(false)._2 + + if (tss.size == 1) tss.head else EmptyTree } - override def filterNotFunction(s: String) = { - (s endsWith "Exception") || - (s endsWith "Error") || - (s endsWith "Impl") || - (s startsWith "CharacterData") + + val completions = Nil + + override def follow(id: String) = simpleParse(id) match { + case x: Literal => Some(new LiteralCompletion(x)) + case _ => None } - override def completions() = super.completions() filter existsAndPublic } - val literals = new LiteralCompletion { - lazy val global = repl.compiler - val parent = self + + // top level packages + object rootClass extends TypeMemberCompletion(RootClass.tpe) { } + // members of Predef + object predef extends TypeMemberCompletion(PredefModule.tpe) { + override def excludeEndsWith = super.excludeEndsWith ++ List("Wrapper", "ArrayOps") + override def excludeStartsWith = super.excludeStartsWith ++ List("wrap") + override def excludeNames = anyref.methodNames + + override def exclude(name: String) = super.exclude(name) || ( + (name contains "2") + ) + + private lazy val completions0 = Nil + private lazy val completions1 = super.completions + override def completions = if (verbosity == 0) completions0 else completions1 } + // members of scala.* + object scalalang extends PackageCompletion(ScalaPackage.tpe) { + def arityClasses = List("Product", "Tuple", "Function") + def skipArity(name: String) = arityClasses exists (x => name != x && (name startsWith x)) + override def exclude(name: String) = super.exclude(name) || ( + skipArity(name) + ) - def lastResult = new Forwarder { - def forwardTo = ids follow repl.mostRecentVar + private lazy val completions0 = filtered(packageNames ++ aliasNames) + private lazy val completions1 = super.completions + override def completions = if (verbosity == 0) completions0 else completions1 } + // members of java.lang.* + object javalang extends PackageCompletion(JavaLangPackage.tpe) { + override lazy val excludeEndsWith = super.excludeEndsWith ++ List("Exception", "Error") + override lazy val excludeStartsWith = super.excludeStartsWith ++ List("CharacterData") + + private lazy val completions0 = filtered(packageNames) + private lazy val completions1 = super.completions + override def completions = if (verbosity == 0) completions0 else completions1 + } + + // the list of completion aware objects which should be consulted + lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) + def topLevel = topLevelBase ++ imported + + // the first tier of top level objects (doesn't include file completion) + def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) + + // the most recent result + def lastResult = Forwarder(() => ids follow repl.mostRecentVar) def lastResultFor(parsed: Parsed) = { /** The logic is a little tortured right now because normally '.' is @@ -122,12 +231,6 @@ class Completion(repl: Interpreter) { if (parsed.isEmpty) xs map ("." + _) else xs } - // the list of completion aware objects which should be consulted - val topLevel: List[CompletionAware] = List(ids, pkgs, predef, scalalang, javalang, literals) - - // the first tier of top level objects (doesn't include file completion) - def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) - // chasing down results which won't parse def execute(line: String): Option[Any] = { val parsed = Parsed(line) @@ -136,13 +239,14 @@ class Completion(repl: Interpreter) { if (noDotOrSlash) None // we defer all unqualified ids to the repl. else { (ids executionFor parsed) orElse - (pkgs executionFor parsed) orElse + (rootClass executionFor parsed) orElse (FileCompletion executionFor line) } } - // override if history is available - def lastCommand: Option[String] = None + // generic interface for querying (e.g. interpreter loop, testing) + def completions(buf: String): List[String] = + topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) // jline's entry point lazy val jline: ArgumentCompletor = @@ -150,20 +254,17 @@ class Completion(repl: Interpreter) { class JLineCompletion extends Completor { // For recording the buffer on the last tab hit - private var lastTab: (String, String) = (null, null) + private var lastTab: (String, Int) = ("", -1) // Does this represent two consecutive tabs? - def isConsecutiveTabs(buf: String) = (buf, lastCommand orNull) == lastTab - - // verbosity goes up with consecutive tabs - // TODO - actually implement. - private var verbosity = 0 + def isConsecutiveTabs(current: (String, Int)) = current == lastTab // This is jline's entry point for completion. override def complete(buf: String, cursor: Int, candidates: JList[String]): Int = { - // println("complete: buf = %s, cursor = %d".format(buf, cursor)) - verbosity = if (isConsecutiveTabs(buf)) verbosity + 1 else 0 - lastTab = (buf, lastCommand orNull) + verbosity = if (isConsecutiveTabs((buf, cursor))) verbosity + 1 else 0 + DBG("complete(%s, %d), verbosity: %s".format(buf, cursor, verbosity)) + + lastTab = (buf, cursor) // we don't try lower priority completions unless higher ones return no results. def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { @@ -172,6 +273,8 @@ class Completion(repl: Interpreter) { case xs => // modify in place and return the position xs foreach (candidates add _) + // DBG("Completion candidates: " + (xs mkString " ")) + Some(p.position) } } diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index 7e94b687bf..fe05ac70b5 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -54,7 +54,7 @@ trait CompletionAware { def completionsFor(parsed: Parsed): List[String] = { import parsed._ - val cs = + def cs = if (isEmpty) completions() else if (isUnqualified && !isLastDelimiter) completions(buffer) else follow(bufferHead) map (_ completionsFor bufferTail) getOrElse Nil diff --git a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala deleted file mode 100644 index b0152dbbc6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala +++ /dev/null @@ -1,25 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -/** Top level identifiers visible in the repl. It immediately - * delegates to an InstanceCompletion. - */ -class IdentCompletion(repl: Interpreter) extends CompletionAware { - val INTERPRETER_VAR_PREFIX = "res" - - def completions() = repl.unqualifiedIds ::: List("classOf") - override def follow(id: String) = - // XXX this will be nice but needs solidifying. - // (repl completionAwareImplicit id) orElse - if (completions contains id) { - (repl completionAware id) orElse { - repl clazzForIdent id map (x => new InstanceCompletion(x)) - } - } - else None -} diff --git a/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala deleted file mode 100644 index 3b74549d27..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/LiteralCompletion.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package scala.tools.nsc -package interpreter - -import util.BatchSourceFile - -/** Literals, so we can pretend they are objects with methods. - */ -abstract class LiteralCompletion extends CompletionAware { - val parent: Completion - val global: Global - - import global._ - - // TODO - figure out how to enumerate available implicit conversions. - // def richInt = new InstanceCompletion(classOf[scala.runtime.RichInt]) - - class PrimitiveCompletion(x: Type) extends CompletionAware { - lazy val completions = x.nonPrivateMembers map (_.name.toString) - override def follow(s: String) = { - val member = x.nonPrivateMembers find (_.name.toString == s) - member flatMap (m => Option(m.tpe)) map (_.resultType) map (x => new PrimitiveCompletion(x)) - } - } - - def simpleParse(code: String): Tree = { - val unit = new CompilationUnit(new BatchSourceFile("<console>", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - - // only single statements - scanner.templateStatSeq(false) match { - case (_, List(t)) => t - case (_, x) => EmptyTree - } - } - - def completions() = Nil - override def follow(id: String) = simpleParse(id) match { - case Literal(c @ Constant(_)) => Some(new PrimitiveCompletion(c.tpe)) - // TODO - more AST trees. - // case Apply(fn @ Ident(name), args) => - // classForName(name.toString) map (x => new StaticCompletion(x)) - // None - case x => None - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala deleted file mode 100644 index 26ae4106c6..0000000000 --- a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala +++ /dev/null @@ -1,187 +0,0 @@ -/* 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 io.{ Path, Directory, File, Streamable } -import scala.tools.util.PathResolver.Defaults.scalaHomeDir -import scala.concurrent.DelayedLazyVal -import scala.reflect.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 { - // Look for a type alias - private def aliasCompletor(path: String): Option[CompletionAware] = - for (name <- ByteCode aliasForType path ; clazz <- classForName(name + "$")) yield - new StaticCompletion(clazz) - - lazy val pkgObject = classForName(root + ".package$") map (x => new PackageObjectCompletion(x)) - def pkgObjectMembers = pkgObject map (_ completionsFor Parsed("")) getOrElse Nil - - private def infos = Option(dottedPaths get root) getOrElse Nil - def completions() = { - val xs = infos map (_.visibleName) filterNot (_ == "package") - xs ::: pkgObjectMembers - } - - override def follow(segment: String): Option[CompletionAware] = { - PackageCompletion.this.follow(root + "." + segment) orElse { - for (CompletionInfo(`segment`, className) <- infos ; clazz <- classForName(className)) { - return Some(new StaticCompletion(clazz)) - } - - aliasCompletor(root + "." + segment) - } - } - override def toString = "SubCompletor(%s)" format root - } -} - -object PackageCompletion { - import java.util.jar.{ JarEntry, JarFile } - - val EXPAND_SEPARATOR_STRING = "$$" - val ANON_CLASS_NAME = "$anon" - val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" - val IMPL_CLASS_SUFFIX ="$class" - - def ignoreClassName(x: String) = - (x contains EXPAND_SEPARATOR_STRING) || - (x contains ANON_CLASS_NAME) || - (x contains TRAIT_SETTER_SEPARATOR_STRING) || - (x endsWith IMPL_CLASS_SUFFIX) || - (x matches """.*\$\d+$""") - - 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) - - 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 - } - } - - 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 { - 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.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 collect { 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 - 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 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)) - } - - def oneDir(dir: Directory) { - for (cl <- getDirClassFiles(dir) ; (k, v) <- subpaths(cl)) - addToMap(k, DirCompletionInfo(v, cl, dir)) - } - - 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 diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala index b130396cc6..810116d0cf 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala @@ -15,7 +15,7 @@ class Parsed private ( val cursor: Int, val delimited: Char => Boolean ) extends Delimited { - def isEmpty = buffer == "" + def isEmpty = args.isEmpty def isUnqualified = args.size == 1 def isQualified = args.size > 1 def isAtStart = cursor <= 0 diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala index 89490119ff..3d621cdbd9 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala @@ -12,7 +12,6 @@ import Modifier.{ isPrivate, isProtected, isStatic } import scala.reflect.NameTransformer import scala.collection.mutable.HashMap import ReflectionCompletion._ -import Completion.{ excludeMethods } trait ReflectionCompletion extends CompletionAware { def clazz: Class[_] @@ -30,12 +29,6 @@ trait ReflectionCompletion extends CompletionAware { case x => error(x.toString) } - override def filterNotFunction(s: String): Boolean = { - (excludeMethods contains s) || - (s contains "$$super") || - (s == "MODULE$") - } - lazy val (staticMethods, instanceMethods) = clazz.getMethods.toList partition (x => isStatic(x.getModifiers)) lazy val (staticFields, instanceFields) = clazz.getFields.toList partition (x => isStatic(x.getModifiers)) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 54f4146f31..51b47f87d6 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -135,6 +135,7 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings { val Ytyperdebug = BooleanSetting ("-Ytyper-debug", "Trace all type assignements") val Ypmatdebug = BooleanSetting ("-Ypmat-debug", "Trace all pattern matcher activity.") val Yrepldebug = BooleanSetting ("-Yrepl-debug", "Trace all repl activity.") + val Ycompletion = BooleanSetting ("-Ycompletion-debug", "Trace all tab completion activity.") val Ypmatnaive = BooleanSetting ("-Ypmat-naive", "Desugar matches as naively as possible..") val Yjenkins = BooleanSetting ("-Yjenkins-hashCodes", "Use jenkins hash algorithm for case class generated hashCodes.") |