diff options
author | Paul Phillips <paulp@improving.org> | 2010-04-26 20:42:03 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-04-26 20:42:03 +0000 |
commit | cf552d7f27ce5aaf27d890b5f4068914aca76444 (patch) | |
tree | 423c48e48c7bbd2117010a65db151134c8f13094 | |
parent | bc17cc6c0363a15fa28ea8c487e66bfb324569af (diff) | |
download | scala-cf552d7f27ce5aaf27d890b5f4068914aca76444.tar.gz scala-cf552d7f27ce5aaf27d890b5f4068914aca76444.tar.bz2 scala-cf552d7f27ce5aaf27d890b5f4068914aca76444.zip |
Mostly finishing the ball that got rolling in r...
Mostly finishing the ball that got rolling in r21679.
scala> String.cop<tab>
scala> String.copyValueOf<tab>
def copyValueOf(Array[Char]): String
def copyValueOf(Array[Char], Int, Int): String
scala> Nil.mkString<tab><tab>
def mkString(sep: String): String
def mkString(start: String, sep: String, end: String): String
def mkString: String
Lines which are not simply delimited don't work yet, so don't go
expecting List(1).<tab> to do the right thing. Yet. No review.
6 files changed, 146 insertions, 57 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index bfb375bf67..ca7bb54ab0 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -25,7 +25,7 @@ object Completion { object Forwarder { def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { - def completions() = forwardTo() map (_.completions()) getOrElse Nil + def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil override def follow(s: String) = forwardTo() flatMap (_ follow s) } } @@ -41,10 +41,11 @@ class Completion(val repl: Interpreter) { 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)) lazy val global: repl.compiler.type = repl.compiler import global._ - import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } + import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage, isRepeatedParamType } // XXX not yet used. lazy val dottedPaths = { @@ -68,6 +69,7 @@ class Completion(val repl: Interpreter) { def tp: Type def effectiveTp = tp match { case MethodType(Nil, resType) => resType + case PolyType(Nil, resType) => resType case _ => tp } @@ -76,10 +78,46 @@ class Completion(val repl: Interpreter) { private def anyMembers = AnyClass.tpe.nonPrivateMembers def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") + /** Only prints the parameter names if they're not synthetic, + * since "x$1: Int" does not offer any more information than "Int". + */ + def typeString(tp: Type): String = { + val str = tp.toString + val prefixes = List("java.lang.", "scala.collection.") + + prefixes.foldLeft(str)(_ stripPrefix _) + } + + def methodSignatureString(name: String, sym: Symbol) = atPhase(currentRun.typerPhase) { + def assembleParams(params: List[Symbol]): String = { + if (params.isEmpty) + return "()" + if (isRepeatedParamType(params.last.tpe)) // ( + return assembleParams(params.init).init + "*)" + + val xs = + if (params exists (_.isSynthetic)) params map (x => typeString(x.tpe)) + else params map (_.defString) + + xs.mkString("(", ", ", ")") + } + + def assemble(paramPart: String, resType: Type): String = + "def " + name + paramPart + ": " + typeString(resType) + + sym.info match { + case MethodType(params, resType) => assemble(assembleParams(params), resType) + case PolyType(tparams, resType) => assemble("", resType) + case x => x.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) @@ -99,7 +137,7 @@ class Completion(val repl: Interpreter) { def imported(tp: Type) = new ImportCompletion(tp) } - class TypeMemberCompletion (val tp: Type) extends CompletionAware with CompilerCompletion { + 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_") @@ -111,12 +149,19 @@ class Completion(val repl: Interpreter) { (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))) - } + + def completions(verbosity: Int) = + debugging(tp + " completions ==> ")(filtered(memberNames)) override def follow(s: String): Option[CompletionAware] = - memberNamed(s) map (x => TypeMemberCompletion(x.tpe)) + 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 (sym => methodSignatureString(id, sym)) + + if (alts.nonEmpty) "" :: alts else Nil + } override def toString = "TypeMemberCompletion(%s)".format(tp) } @@ -126,15 +171,17 @@ class Completion(val repl: Interpreter) { } 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 + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(memberNames) + case _ => memberNames + } } 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 + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(methods filterNot (_.isSetter) map tos) + case _ => super.completions(verbosity) + } } // not for completion but for excluding @@ -142,11 +189,11 @@ class Completion(val repl: Interpreter) { // the unqualified vals/defs/etc visible in the repl object ids extends CompletionAware { - def completions() = repl.unqualifiedIds ::: List("classOf") + 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 contains id) { + if (completions(0) contains id) { for (clazz <- repl clazzForIdent id) yield { (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) } @@ -167,7 +214,7 @@ class Completion(val repl: Interpreter) { if (tss.size == 1) tss.head else EmptyTree } - val completions = Nil + def completions(verbosity: Int) = Nil override def follow(id: String) = simpleParse(id) match { case x: Literal => Some(new LiteralCompletion(x)) @@ -187,9 +234,10 @@ class Completion(val repl: Interpreter) { (name contains "2") ) - private lazy val completions0 = Nil - private lazy val completions1 = super.completions - override def completions = if (verbosity == 0) completions0 else completions1 + override def completions(verbosity: Int) = verbosity match { + case 0 => Nil + case _ => super.completions(verbosity) + } } // members of scala.* object scalalang extends PackageCompletion(ScalaPackage.tpe) { @@ -199,18 +247,20 @@ class Completion(val repl: Interpreter) { skipArity(name) ) - private lazy val completions0 = filtered(packageNames ++ aliasNames) - private lazy val completions1 = super.completions - override def completions = if (verbosity == 0) completions0 else completions1 + 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") - private lazy val completions0 = filtered(packageNames) - private lazy val completions1 = super.completions - override def completions = if (verbosity == 0) completions0 else completions1 + 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 @@ -252,19 +302,30 @@ class Completion(val repl: Interpreter) { lazy val jline: ArgumentCompletor = returning(new ArgumentCompletor(new JLineCompletion, new JLineDelimiter))(_ setStrict false) + /** 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 Completor { // For recording the buffer on the last tab hit - private var lastTab: (String, Int) = ("", -1) + private var lastBuf: String = "" + private var lastCursor: Int = -1 // Does this represent two consecutive tabs? - def isConsecutiveTabs(current: (String, Int)) = current == lastTab + def isConsecutiveTabs(buf: String, cursor: Int) = cursor == lastCursor && buf == lastBuf - // This is jline's entry point for completion. - override def complete(buf: String, cursor: Int, candidates: JList[String]): Int = { - verbosity = if (isConsecutiveTabs((buf, cursor))) verbosity + 1 else 0 - DBG("complete(%s, %d), verbosity: %s".format(buf, cursor, verbosity)) + // Longest common prefix + def commonPrefix(xs: List[String]) = + if (xs.isEmpty) "" + else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) - lastTab = (buf, cursor) + // This is jline's entry point for completion. + override def complete(_buf: String, cursor: Int, candidates: JList[String]): Int = { + val buf = onull(_buf) + verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 + DBG("complete(%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] = { @@ -273,19 +334,30 @@ class Completion(val repl: Interpreter) { case xs => // modify in place and return the position xs foreach (candidates add _) - // DBG("Completion candidates: " + (xs mkString " ")) - Some(p.position) + // 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(Parsed.dotted(buf, cursor), topLevelFor) - def fileCompletion = tryCompletion(Parsed.undelimited(buf, cursor), FileCompletion completionsFor _.buffer) + def regularCompletion = tryCompletion(mkDotted, topLevelFor) + def fileCompletion = tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor } diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index fe05ac70b5..cfd3b5e05f 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -21,8 +21,7 @@ trait CompletionAware { /** The complete list of unqualified Strings to which this * object will complete. */ - def completions(): List[String] - def completions(start: String): List[String] = completions filter (_ startsWith start) + def completions(verbosity: Int): List[String] /** Default filter to apply to completions. */ @@ -47,6 +46,19 @@ trait CompletionAware { */ def execute(id: String): Option[Any] = None + /** A list of useful information regarding a specific uniquely + * identified completion. This is specifically written for the + * following situation, but should be useful elsewhere too: + * + * x.y.z.methodName<tab> + * + * If "methodName" is among z's completions, and verbosity > 0 + * indicating tab has been pressed twice consecutively, then we + * call alternativesFor and show a list of overloaded method + * signatures. + */ + def alternativesFor(id: String): List[String] = Nil + /** Given string 'buf', return a list of all the strings * to which it can complete. This may involve delegating * to other CompletionAware objects. @@ -54,12 +66,16 @@ trait CompletionAware { def completionsFor(parsed: Parsed): List[String] = { import parsed._ - def cs = - if (isEmpty) completions() - else if (isUnqualified && !isLastDelimiter) completions(buffer) + val comps = completions(verbosity) filter (_ startsWith buffer) + val results = + if (isEmpty) comps + else if (isUnqualified && !isLastDelimiter) { + if (verbosity > 0 && (comps contains buffer)) alternativesFor(buffer) + else comps + } else follow(bufferHead) map (_ completionsFor bufferTail) getOrElse Nil - cs filterNot filterNotFunction map mapFunction sortWith (sortFunction _) + results filterNot filterNotFunction map mapFunction sortWith (sortFunction _) } /** TODO - unify this and completionsFor under a common traverser. @@ -67,14 +83,14 @@ trait CompletionAware { def executionFor(parsed: Parsed): Option[Any] = { import parsed._ - if (isUnqualified && !isLastDelimiter && (completions contains buffer)) execute(buffer) + if (isUnqualified && !isLastDelimiter && (completions(verbosity) contains buffer)) execute(buffer) else if (!isQualified) None else follow(bufferHead) flatMap (_ executionFor bufferTail) } } object CompletionAware { - val Empty = new CompletionAware { val completions = Nil } + val Empty = new CompletionAware { def completions(verbosity: Int) = Nil } // class Forwarder(underlying: CompletionAware) extends CompletionAware { // override def completions() = underlying.completions() @@ -101,6 +117,7 @@ object CompletionAware { def apply(terms: () => List[String], followFunction: String => Option[CompletionAware]): CompletionAware = new CompletionAware { def completions = terms() + def completions(verbosity: Int) = completions override def follow(id: String) = followFunction(id) } diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala index 810116d0cf..0b92608d88 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala @@ -20,13 +20,17 @@ class Parsed private ( def isQualified = args.size > 1 def isAtStart = cursor <= 0 + private var _verbosity = 0 + def verbosity = _verbosity + def withVerbosity(v: Int): this.type = returning[this.type](this)(_ => _verbosity = v) + def args = toArgs(buffer take cursor).toList def bufferHead = args.head def headLength = bufferHead.length + 1 - def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited) + def bufferTail = new Parsed(buffer drop headLength, cursor - headLength, delimited) withVerbosity verbosity - def prev = new Parsed(buffer, cursor - 1, delimited) - def next = new Parsed(buffer, cursor + 1, delimited) + def prev = new Parsed(buffer, cursor - 1, delimited) withVerbosity verbosity + def next = new Parsed(buffer, cursor + 1, delimited) withVerbosity verbosity def currentChar = buffer(cursor) def currentArg = args.last def position = @@ -52,7 +56,7 @@ class Parsed private ( object Parsed { def apply(s: String): Parsed = apply(onull(s), onull(s).length) - def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "(){},`; \t" contains _) + def apply(s: String, cursor: Int): Parsed = apply(onull(s), cursor, "{},`; \t" contains _) def apply(s: String, cursor: Int, delimited: Char => Boolean): Parsed = new Parsed(onull(s), cursor, delimited) diff --git a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala index 2aaa6114c2..6c066580ae 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ProductCompletion.scala @@ -8,6 +8,7 @@ package interpreter class SeqCompletion[T](elems: Seq[T]) extends CompletionAware { lazy val completions = elems.indices.toList map ("(%d)" format _) + def completions(verbosity: Int) = completions private def elemAt(name: String) = if (completions contains name) Some(elems(name drop 1 dropRight 1 toInt)) else None @@ -27,6 +28,7 @@ class ProductCompletion(root: Product) extends CompletionAware { } lazy val completions = caseNames + def completions(verbosity: Int) = completions override def execute(name: String) = fieldForName(name) override def follow(name: String) = fieldForName(name) map (x => ProductCompletion(x)) } diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala index 3d621cdbd9..f9ff894d59 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala @@ -46,15 +46,6 @@ trait ReflectionCompletion extends CompletionAware { } } -/** An instance completion which hides a few useless members. - */ -class PackageObjectCompletion(clazz: Class[_]) extends InstanceCompletion(clazz) { - override lazy val completions = memberCompletions - override def filterNotFunction(s: String) = { - super.filterNotFunction(s) || (s == "getClass") || (s == "toString") - } -} - /** A completion aware object representing a single instance of some class. * It completes to instance fields and methods, and delegates to another * InstanceCompletion object if it can determine the result type of the element. @@ -63,6 +54,7 @@ class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { protected def visibleMembers = instanceMethods ::: instanceFields def extras = List("isInstanceOf", "asInstanceOf", "toString") lazy val completions = memberCompletions ::: extras + def completions(verbosity: Int) = completions val (zeroArg, otherArg) = instanceMethods partition (_.getParameterTypes.size == 0) override def follow(id: String) = { @@ -78,6 +70,7 @@ class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { class StaticCompletion(val clazz: Class[_]) extends ReflectionCompletion { protected def visibleMembers = whichMethods ::: whichFields lazy val completions = memberCompletions + def completions(verbosity: Int) = completions private def aliasForPath(path: String) = ByteCode aliasForType path flatMap (x => classForName(x + "$")) def className = clazz.getName diff --git a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala index 67063192bd..f2af57cc36 100644 --- a/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala @@ -33,6 +33,7 @@ class XMLCompletion(root: Node) extends CompletionAware { s :: res }).sorted } + def completions(verbosity: Int) = completions override def execute(id: String) = getNode(id) override def follow(id: String) = getNode(id) map (x => new XMLCompletion(x)) |