summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/interpreter/Completion.scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-01-29 01:43:10 +0000
committerPaul Phillips <paulp@improving.org>2011-01-29 01:43:10 +0000
commitcf492f472aa6d1154cd7336c37bab0b78125b872 (patch)
tree8735c22a25c019a5e694d8f93a0d66e9b4f47940 /src/compiler/scala/tools/nsc/interpreter/Completion.scala
parentb06bfabfa42cc089521e551acb9876a564ec027f (diff)
downloadscala-cf492f472aa6d1154cd7336c37bab0b78125b872.tar.gz
scala-cf492f472aa6d1154cd7336c37bab0b78125b872.tar.bz2
scala-cf492f472aa6d1154cd7336c37bab0b78125b872.zip
There is a lot of housecleaning to be done.
up the stray interpreter files and put them in the interpreter package. Would really love to change the name of that package. Went looking for some consistent divisions of responsibility and consistent naming. Made some progress. There are deprecated versions of most everything I changed so hopefully the carnage will be limited. This isn't completely baked but I just realized I broke the build earlier and this should fix it. I'll keep the oven on. No review.
Diffstat (limited to 'src/compiler/scala/tools/nsc/interpreter/Completion.scala')
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala366
1 files changed, 18 insertions, 348 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
index 04a1a4014a..3436c6631e 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
@@ -3,24 +3,34 @@
* @author Paul Phillips
*/
-
package scala.tools.nsc
package interpreter
-import scala.tools.jline._
-import scala.tools.jline.console.completer._
import java.util.{ List => JList }
-import util.returning
+
+/** An implementation-agnostic completion interface which makes no
+ * reference to the jline classes.
+ */
+trait Completion {
+ type ExecResult
+ trait Instance {
+ def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]): Int
+ }
+ def resetVerbosity(): Unit
+ def execute(line: String): Option[ExecResult]
+ def completer(): Instance
+}
object Completion {
object Empty extends Completion {
+ type ExecResult = Nothing
+ object NullCompleter extends Instance {
+ def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]) = -1
+ }
def resetVerbosity() = ()
def execute(line: String) = None
- def completer() = new NullCompleter
+ def completer() = NullCompleter
}
-
- def apply(repl: Interpreter): Completion = new CompletionImpl(repl)
-
def looksLikeInvocation(code: String) = (
(code != null)
&& (code startsWith ".")
@@ -31,7 +41,6 @@ object Completion {
def looksLikePath(code: String) = (code != null) && (code.length >= 2) && (
Set("/", "\\", "./", "../", "~/") exists (code startsWith _)
)
-
object Forwarder {
def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware {
def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil
@@ -39,342 +48,3 @@ object Completion {
}
}
}
-import Completion._
-
-trait Completion {
- def resetVerbosity(): Unit
- def execute(line: String): Option[Any]
- def completer(): Completer
-}
-
-// REPL completor - queries supplied interpreter for valid
-// completions based on current contents of buffer.
-class CompletionImpl(val repl: Interpreter) extends Completion with CompletionOutput {
- val global: repl.global.type = repl.global
- import global._
- import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage }
-
- // verbosity goes up with consecutive tabs
- private var verbosity: Int = 0
- def resetVerbosity() = verbosity = 0
-
- def isCompletionDebug = repl.isCompletionDebug
- def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString)
- def debugging[T](msg: String): T => T = (res: T) => returning[T](res)(x => DBG(msg + x))
-
- // 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)
- }
-
- 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 NullaryMethodType(resType) => resType
- case _ => tp
- }
-
- // 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)
-
- // XXX we'd like to say "filterNot (_.isDeprecated)" but this causes the
- // compiler to crash for reasons not yet known.
- 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
- }
-
- 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)
- }
-
- 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.filterNot(anyRefMethodsToShow contains) ++ List("_root_")
-
- def methodSignatureString(sym: Symbol) = {
- def asString = new MethodSymbolOutput(sym).methodString()
- atPhase(currentRun.typerPhase)(asString)
- }
-
- 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(verbosity: Int) =
- debugging(tp + " completions ==> ")(filtered(memberNames))
-
- override def follow(s: String): Option[CompletionAware] =
- debugging(tp + " -> '" + s + "' ==> ")(memberNamed(s) map (x => TypeMemberCompletion(x.tpe)))
-
- override def alternativesFor(id: String): List[String] =
- debugging(id + " alternatives ==> ") {
- val alts = members filter (x => x.isMethod && tos(x) == id) map methodSignatureString
-
- if (alts.nonEmpty) "" :: alts else Nil
- }
-
- 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) {
- override def completions(verbosity: Int) = verbosity match {
- case 0 => filtered(memberNames)
- case _ => memberNames
- }
- }
-
- class ImportCompletion(tp: Type) extends TypeMemberCompletion(tp) {
- override def completions(verbosity: Int) = verbosity match {
- case 0 => filtered(members filterNot (_.isSetter) map tos)
- case _ => super.completions(verbosity)
- }
- }
-
- // 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 {
- override def completions(verbosity: Int) = 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(0) contains id) {
- for (clazz <- repl clazzForIdent id) yield {
- // XXX The isMemberClass check is a workaround for the crasher described
- // in the comments of #3431. The issue as described by iulian is:
- //
- // Inner classes exist as symbols
- // inside their enclosing class, but also inside their package, with a mangled
- // name (A$B). The mangled names should never be loaded, and exist only for the
- // optimizer, which sometimes cannot get the right symbol, but it doesn't care
- // and loads the bytecode anyway.
- //
- // So this solution is incorrect, but in the short term the simple fix is
- // to skip the compiler any time completion is requested on a nested class.
- if (clazz.isMemberClass) new InstanceCompletion(clazz)
- else (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
- }
-
- def completions(verbosity: Int) = Nil
-
- override def follow(id: String) = simpleParse(id) match {
- case x: Literal => Some(new LiteralCompletion(x))
- case _ => None
- }
- }
-
- // 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")
- )
-
- override def completions(verbosity: Int) = verbosity match {
- case 0 => Nil
- case _ => super.completions(verbosity)
- }
- }
- // 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)
- )
-
- override def completions(verbosity: Int) = verbosity match {
- case 0 => filtered(packageNames ++ aliasNames)
- case _ => super.completions(verbosity)
- }
- }
- // 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")
-
- override def completions(verbosity: Int) = verbosity match {
- case 0 => filtered(packageNames)
- case _ => super.completions(verbosity)
- }
- }
-
- // 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
- * ignored as a delimiter, but on .<tab> it needs to be propagated.
- */
- val xs = lastResult completionsFor parsed
- if (parsed.isEmpty) xs map ("." + _) else xs
- }
-
- // chasing down results which won't parse
- def execute(line: String): Option[Any] = {
- val parsed = Parsed(line)
- def noDotOrSlash = line forall (ch => ch != '.' && ch != '/')
-
- if (noDotOrSlash) None // we defer all unqualified ids to the repl.
- else {
- (ids executionFor parsed) orElse
- (rootClass executionFor parsed) orElse
- (FileCompletion executionFor line)
- }
- }
-
- // generic interface for querying (e.g. interpreter loop, testing)
- def completions(buf: String): List[String] =
- topLevelFor(Parsed.dotted(buf + ".", buf.length + 1))
-
- def completer() = new JLineCompletion
-
- /** This gets a little bit hairy. It's no small feat delegating everything
- * and also keeping track of exactly where the cursor is and where it's supposed
- * to end up. The alternatives mechanism is a little hacky: if there is an empty
- * string in the list of completions, that means we are expanding a unique
- * completion, so don't update the "last" buffer because it'll be wrong.
- */
- class JLineCompletion extends Completer {
- // For recording the buffer on the last tab hit
- private var lastBuf: String = ""
- private var lastCursor: Int = -1
-
- // Does this represent two consecutive tabs?
- def isConsecutiveTabs(buf: String, cursor: Int) =
- cursor == lastCursor && buf == lastBuf
-
- // Longest common prefix
- def commonPrefix(xs: List[String]) =
- if (xs.isEmpty) ""
- else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString)
-
- // This is jline's entry point for completion.
- override def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = {
- val buf = if (_buf == null) "" else _buf
- verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0
- DBG("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity))
-
- // we don't try lower priority completions unless higher ones return no results.
- def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = {
- completionFunction(p) match {
- case Nil => None
- case xs =>
- // modify in place and return the position
- xs foreach (candidates add _)
-
- // update the last buffer unless this is an alternatives list
- if (xs contains "") Some(p.cursor)
- else {
- val advance = commonPrefix(xs)
- lastCursor = p.position + advance.length
- lastBuf = (buf take p.position) + advance
-
- DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position))
- Some(p.position)
- }
- }
- }
-
- def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity
- def mkUndelimited = Parsed.undelimited(buf, cursor) withVerbosity verbosity
-
- // a single dot is special cased to completion on the previous result
- def lastResultCompletion =
- if (!looksLikeInvocation(buf)) None
- else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor)
-
- def regularCompletion = tryCompletion(mkDotted, topLevelFor)
- def fileCompletion =
- if (!looksLikePath(buf)) None
- else tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer)
-
- /** This is the kickoff point for all manner of theoretically possible compiler
- * unhappiness - fault may be here or elsewhere, but we don't want to crash the
- * repl regardless. Hopefully catching Exception is enough, but because the
- * compiler still throws some Errors it may not be.
- */
- try {
- (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor
- }
- catch {
- case ex: Exception =>
- DBG("Error: complete(%s, %s, _) provoked %s".format(_buf, cursor, ex))
- candidates add " "
- candidates add "<completion error>"
- cursor
- }
- }
- }
-}