Skip to content

Commit 10e3380

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 11a9a78 commit 10e3380

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
@@ -775,8 +775,8 @@ object DottyLanguageServer {
775775
symbol.owner == ctx.definitions.EmptyPackageClass
776776
}
777777

778-
/** Create an lsp4j.CompletionItem from a Symbol */
779-
def completionItem(sym: Symbol)(implicit ctx: Context): lsp4j.CompletionItem = {
778+
/** Create an lsp4j.CompletionItem from a completion result */
779+
def completionItem(completion: Completion)(implicit ctx: Context): lsp4j.CompletionItem = {
780780
def completionItemKind(sym: Symbol)(implicit ctx: Context): lsp4j.CompletionItemKind = {
781781
import lsp4j.{CompletionItemKind => CIK}
782782

@@ -794,15 +794,20 @@ object DottyLanguageServer {
794794
CIK.Field
795795
}
796796

797-
val label = sym.name.show
798-
val item = new lsp4j.CompletionItem(label)
799-
val detail = if (sym.isType) sym.showFullName else sym.info.widenTermRefExpr.show
800-
item.setDetail(detail)
801-
ParsedComment.docOf(sym).foreach { doc =>
802-
item.setDocumentation(markupContent(doc.renderAsMarkdown))
797+
val item = new lsp4j.CompletionItem(completion.label)
798+
item.setDetail(completion.description)
799+
800+
val documentation = for {
801+
sym <- completion.symbols
802+
doc <- ParsedComment.docOf(sym)
803+
} yield doc
804+
805+
if (documentation.nonEmpty) {
806+
item.setDocumentation(hoverContent(None, documentation))
803807
}
804-
item.setDeprecated(sym.isDeprecated)
805-
item.setKind(completionItemKind(sym))
808+
809+
item.setDeprecated(completion.symbols.forall(_.isDeprecated))
810+
completion.symbols.headOption.foreach(s => item.setKind(completionItemKind(s)))
806811
item
807812
}
808813

0 commit comments

Comments
 (0)