diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2015-09-02 12:53:20 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2015-09-02 13:43:16 +1000 |
commit | eca39a40d3c0158192e7364ebb1d9bd5b8deec55 (patch) | |
tree | c9ac3a022751d08dc42255aaf9e89047388bad55 /src/interactive/scala/tools | |
parent | 06ad297a07716910814651acd60736978aa54d63 (diff) | |
download | scala-eca39a40d3c0158192e7364ebb1d9bd5b8deec55.tar.gz scala-eca39a40d3c0158192e7364ebb1d9bd5b8deec55.tar.bz2 scala-eca39a40d3c0158192e7364ebb1d9bd5b8deec55.zip |
Add a higher level completion API to the pres compiler
Conveniences added:
- The client need not determine ahead of time whether it wants
scope completions of type member completions, this is inferred
from the tree at the cursor
- Computes the delta from the cursor to point where a suggested
completion item should be written. This includes finding the
position of the name within a Select node, which is tricky.
- Includes a matcher that matches results base on prefix,
accessibility and suitability in the syntactic location.
Type members are not offered in term positions, and term members
only offered in type position if they could for a prefix for
a type. E.g. `type T = glob<TAB>` should offer `global` to help
writing `global.Tree`.
Diffstat (limited to 'src/interactive/scala/tools')
-rw-r--r-- | src/interactive/scala/tools/nsc/interactive/Global.scala | 78 |
1 files changed, 77 insertions, 1 deletions
diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 1e9e66df64..8f04735ceb 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -662,7 +662,7 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") /** Make sure unit is typechecked */ - private def typeCheck(unit: RichCompilationUnit) { + private[scala] def typeCheck(unit: RichCompilationUnit) { debugLog("type checking: "+unit) parseAndEnter(unit) unit.status = PartiallyChecked @@ -1148,6 +1148,82 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } } + sealed abstract class CompletionResult { + type M <: Member + def results: List[M] + /** The (possibly partial) detected that precedes the cursor */ + def name: Name + def positionDelta: Int + def matchingResults(matcher: (M, Name) => Boolean = CompletionResult.prefixMatcher): List[M] = { + results filter (r => matcher(r, name)) + } + def qualifierType: Type = NoType + } + object CompletionResult { + final case class ScopeMembers(positionDelta: Int, results: List[ScopeMember], name: Name) extends CompletionResult { + type M = ScopeMember + } + final case class TypeMembers(positionDelta: Int, qualifier: Tree, tree: Tree, results: List[TypeMember], name: Name) extends CompletionResult { + type M = TypeMember + override def qualifierType: Type = qualifier.tpe + } + case object NoResults extends CompletionResult { + override def results = Nil + override def name = nme.EMPTY + override def positionDelta = 0 + + } + val prefixMatcher = (member: Member, name: Name) => { + val symbol = member.sym + val prefix = if (name == nme.ERROR) nme.EMPTY else name + def isStable = member.tpe.isStable || member.sym.isStable || member.sym.getterIn(member.sym.owner).isStable + val nameMatches = !symbol.isConstructor && (prefix.isEmpty || symbol.name.startsWith(prefix) && (symbol.name.isTermName == prefix.isTermName || prefix.isTypeName && isStable)) + nameMatches && member.accessible + } + } + + final def completionsAt(pos: Position): CompletionResult = { + val focus1: Tree = typedTreeAt(pos) + def typeCompletions(tree: Tree, qual: Tree, nameStart: Int, name: Name): CompletionResult = { + val qualPos = qual.pos + val allTypeMembers = typeMembers(qualPos).toList.flatten + + val positionDelta: Int = nameStart - pos.start + val subName = name.subName(0, -positionDelta) + CompletionResult.TypeMembers(positionDelta, qual, tree, allTypeMembers, subName) + } + focus1 match { + case imp@Import(i @ Ident(name), head :: Nil) if head.name == nme.ERROR => + val allMembers = scopeMembers(pos) + val nameStart = i.pos.start + val positionDelta: Int = nameStart - pos.start + val subName = name.subName(0, pos.start - i.pos.start) + CompletionResult.ScopeMembers(positionDelta, allMembers, subName) + case imp@Import(qual, selectors) => + selectors.reverseIterator.find(_.namePos <= pos.start) match { + case None => CompletionResult.NoResults + case Some(selector) => + typeCompletions(imp, qual, selector.namePos, selector.name) + } + case sel@Select(qual, name) => + val qualPos = qual.pos + def fallback = qualPos.end + 2 + val source = pos.source + val nameStart: Int = (qualPos.end + 1 until focus1.pos.end).find(p => + source.identifier(source.position(p)).exists(_.length > 0) + ).getOrElse(fallback) + typeCompletions(sel, qual, nameStart, name) + case Ident(name) => + val allMembers = scopeMembers(pos) + val positionDelta: Int = focus1.pos.start - pos.start + val subName = name.subName(0, -positionDelta) + CompletionResult.ScopeMembers(positionDelta, allMembers, subName) + case _ => + CompletionResult.NoResults + } + } + + /** Implements CompilerControl.askLoadedTyped */ private[interactive] def waitLoadedTyped(source: SourceFile, response: Response[Tree], keepLoaded: Boolean = false, onSameThread: Boolean = true) { getUnit(source) match { |