Skip to content

Commit 9155812

Browse files
committed
Fix #5508: Add Completion structure
One `Completion` represents one completion results that should be included in the list of completion options. It can represent zero symbols (for instance, a wildcard import), or more than one (a class and its companion object). Each `Completion` also has a `description` which should be displayed in the tool that shows the completion candidates. Fixes #5508
1 parent 6eddf68 commit 9155812

File tree

3 files changed

+50
-22
lines changed

3 files changed

+50
-22
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ import dotty.tools.dotc.util.{NoSourcePosition, SourcePosition}
2020

2121
import scala.collection.mutable
2222

23+
/**
24+
* One of the results of a completion query.
25+
*
26+
* @param label The label of this completion result, or the text that this completion result
27+
* should insert in the scope where the completion request happened.
28+
* @param description The description of this completion result: the fully qualified name for
29+
* types, or the type for terms.
30+
* @param symbols The symbols that are matched by this completion result.
31+
*/
32+
case class Completion(label: String, description: String, symbols: List[Symbol])
33+
2334
object Completion {
2435

2536
import dotty.tools.dotc.ast.tpd._
@@ -28,7 +39,7 @@ object Completion {
2839
*
2940
* @return offset and list of symbols for possible completions
3041
*/
31-
def completions(pos: SourcePosition)(implicit ctx: Context): (Int, List[Symbol]) = {
42+
def completions(pos: SourcePosition)(implicit ctx: Context): (Int, List[Completion]) = {
3243
val path = Interactive.pathTo(ctx.compilationUnit.tpdTree, pos.pos)
3344
computeCompletions(pos, path)(Interactive.contextOfPath(path))
3445
}
@@ -100,7 +111,7 @@ object Completion {
100111
new CompletionBuffer(mode, prefix, pos)
101112
}
102113

103-
private def computeCompletions(pos: SourcePosition, path: List[Tree])(implicit ctx: Context): (Int, List[Symbol]) = {
114+
private def computeCompletions(pos: SourcePosition, path: List[Tree])(implicit ctx: Context): (Int, List[Completion]) = {
104115

105116
val offset = completionOffset(path)
106117
val buffer = completionBuffer(path, pos)
@@ -131,15 +142,27 @@ object Completion {
131142
/**
132143
* Return the list of symbols that shoudl be included in completion results.
133144
*
134-
* If the mode is `Import` and several symbols share the same name, the type symbols are
135-
* preferred over term symbols.
145+
* If several symbols share the same name, the type symbols appear before term symbols inside
146+
* the same `Completion`.
147+
*/
148+
def getCompletions(implicit ctx: Context): List[Completion] = {
149+
val groupedSymbols = completions.toList.groupBy(_.name.stripModuleClassSuffix.toSimpleName).toList
150+
groupedSymbols.map { case (name, symbols) =>
151+
val typesFirst = symbols.sortWith((s, _) => s.isType)
152+
// Use distinct to remove duplicates with class, module class, etc.
153+
val descriptions = typesFirst.map(description).distinct.mkString(", ")
154+
Completion(name.toString, descriptions, typesFirst)
155+
}
156+
}
157+
158+
/**
159+
* A description for `sym`.
160+
*
161+
* For types, show the symbol's full name, or its type for term symbols.
136162
*/
137-
def getCompletions(implicit ctx: Context): List[Symbol] = {
138-
// Show only the type symbols when there are multiple options with the same name
139-
completions.toList.groupBy(_.name.stripModuleClassSuffix.toSimpleName).mapValues {
140-
case sym :: Nil => sym :: Nil
141-
case syms => syms.filter(_.isType)
142-
}.values.flatten.toList
163+
private def description(sym: Symbol)(implicit ctx: Context): String = {
164+
if (sym.isType) sym.showFullName
165+
else sym.info.widenTermRefExpr.show
143166
}
144167

145168
/**

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ class ReplDriver(settings: Array[String],
149149

150150
/** Extract possible completions at the index of `cursor` in `expr` */
151151
protected[this] final def completions(cursor: Int, expr: String, state0: State): List[Candidate] = {
152-
def makeCandidate(completion: Symbol)(implicit ctx: Context) = {
153-
val displ = completion.name.toString
152+
def makeCandidate(completion: Completion)(implicit ctx: Context) = {
153+
val displ = completion.label
154154
new Candidate(
155155
/* value = */ displ,
156156
/* displ = */ displ, // displayed value

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -780,8 +780,8 @@ object DottyLanguageServer {
780780
symbol.owner == ctx.definitions.EmptyPackageClass
781781
}
782782

783-
/** Create an lsp4j.CompletionItem from a Symbol */
784-
def completionItem(sym: Symbol)(implicit ctx: Context): lsp4j.CompletionItem = {
783+
/** Create an lsp4j.CompletionItem from a completion result */
784+
def completionItem(completion: Completion)(implicit ctx: Context): lsp4j.CompletionItem = {
785785
def completionItemKind(sym: Symbol)(implicit ctx: Context): lsp4j.CompletionItemKind = {
786786
import lsp4j.{CompletionItemKind => CIK}
787787

@@ -799,15 +799,20 @@ object DottyLanguageServer {
799799
CIK.Field
800800
}
801801

802-
val label = sym.name.show
803-
val item = new lsp4j.CompletionItem(label)
804-
val detail = if (sym.isType) sym.showFullName else sym.info.widenTermRefExpr.show
805-
item.setDetail(detail)
806-
ParsedComment.docOf(sym).foreach { doc =>
807-
item.setDocumentation(markupContent(doc.renderAsMarkdown))
802+
val item = new lsp4j.CompletionItem(completion.label)
803+
item.setDetail(completion.description)
804+
805+
val documentation = for {
806+
sym <- completion.symbols
807+
doc <- ParsedComment.docOf(sym)
808+
} yield doc
809+
810+
if (documentation.nonEmpty) {
811+
item.setDocumentation(hoverContent(None, documentation))
808812
}
809-
item.setDeprecated(sym.isDeprecated)
810-
item.setKind(completionItemKind(sym))
813+
814+
item.setDeprecated(completion.symbols.forall(_.isDeprecated))
815+
completion.symbols.headOption.foreach(s => item.setKind(completionItemKind(s)))
811816
item
812817
}
813818

0 commit comments

Comments
 (0)