Skip to content

Commit 86a09f3

Browse files
authored
Respect client capabilities for completion item snippets (#1057)
Respect client capabilities for completion item snippets
2 parents b3ea857 + 03c3f68 commit 86a09f3

File tree

9 files changed

+149
-20
lines changed

9 files changed

+149
-20
lines changed

metals/src/main/scala/scala/meta/internal/metals/Compilers.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ch.epfl.scala.bsp4j.ScalaBuildTarget
66
import ch.epfl.scala.bsp4j.ScalacOptionsItem
77
import java.util.Collections
88
import java.util.concurrent.ScheduledExecutorService
9+
import org.eclipse.lsp4j.InitializeParams
910
import org.eclipse.lsp4j.CompletionItem
1011
import org.eclipse.lsp4j.CompletionList
1112
import org.eclipse.lsp4j.CompletionParams
@@ -40,7 +41,8 @@ class Compilers(
4041
search: SymbolSearch,
4142
embedded: Embedded,
4243
statusBar: StatusBar,
43-
sh: ScheduledExecutorService
44+
sh: ScheduledExecutorService,
45+
initializeParams: Option[InitializeParams]
4446
)(implicit ec: ExecutionContextExecutorService)
4547
extends Cancelable {
4648
val plugins = new CompilerPlugins()
@@ -247,7 +249,11 @@ class Compilers(
247249
.withExecutorService(ec)
248250
.withScheduledExecutorService(sh)
249251
.withConfiguration(
250-
config.compilers.copy(_symbolPrefixes = userConfig().symbolPrefixes)
252+
config.compilers.copy(
253+
_symbolPrefixes = userConfig().symbolPrefixes,
254+
isCompletionSnippetsEnabled =
255+
initializeParams.supportsCompletionSnippets
256+
)
251257
)
252258

253259
def newCompiler(

metals/src/main/scala/scala/meta/internal/metals/MetalsEnrichments.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,16 @@ object MetalsEnrichments
520520
)
521521
} yield hierarchicalDocumentSymbolSupport.booleanValue).getOrElse(false)
522522

523+
def supportsCompletionSnippets: Boolean =
524+
(for {
525+
params <- initializeParams
526+
capabilities <- Option(params.getCapabilities)
527+
textDocument <- Option(capabilities.getTextDocument)
528+
completion <- Option(textDocument.getCompletion)
529+
completionItem <- Option(completion.getCompletionItem)
530+
snippetSupport <- Option(completionItem.getSnippetSupport())
531+
} yield snippetSupport.booleanValue).getOrElse(false)
532+
523533
}
524534

525535
implicit class XtensionPromise[T](promise: Promise[T]) {

metals/src/main/scala/scala/meta/internal/metals/MetalsLanguageServer.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ class MetalsLanguageServer(
394394
),
395395
embedded,
396396
statusBar,
397-
sh
397+
sh,
398+
Option(params)
398399
)
399400
)
400401
doctor = new Doctor(

mtags-interfaces/src/main/java/scala/meta/pc/PresentationCompilerConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ enum OverrideDefFormat {
7070
*/
7171
boolean isSignatureHelpDocumentationEnabled();
7272

73+
/**
74+
* Returns true if completions can contain snippets.
75+
*/
76+
boolean isCompletionSnippetsEnabled();
77+
7378
/**
7479
* The maximum delay for requests to respond.
7580
*

mtags/src/main/scala/scala/meta/internal/pc/CompletionProvider.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class CompletionProvider(
4444
)
4545
val pos = unit.position(params.offset)
4646
val isSnippet = isSnippetEnabled(pos, params.text())
47+
val clientSupportsSnippets =
48+
compiler.metalsConfig.isCompletionSnippetsEnabled()
4749
val (i, completion, editRange, query) = safeCompletionsAt(pos)
4850
val start = inferIdentStart(pos, params.text())
4951
val end = inferIdentEnd(pos, params.text())
@@ -88,13 +90,13 @@ class CompletionProvider(
8890
item.setDetail(detail)
8991
}
9092
val templateSuffix =
91-
if (!isSnippet) ""
93+
if (!isSnippet || !clientSupportsSnippets) ""
9294
else if (completion.isNew &&
9395
r.sym.dealiased.requiresTemplateCurlyBraces) " {}"
9496
else ""
9597

9698
val typeSuffix =
97-
if (!isSnippet) ""
99+
if (!isSnippet || !clientSupportsSnippets) ""
98100
else if (completion.isType && r.sym.hasTypeParams) "[$0]"
99101
else if (completion.isNew && r.sym.hasTypeParams) "[$0]"
100102
else ""
@@ -111,7 +113,11 @@ class CompletionProvider(
111113
item.setFilterText(symbolName)
112114
}
113115

114-
item.setInsertTextFormat(InsertTextFormat.Snippet)
116+
if (clientSupportsSnippets) {
117+
item.setInsertTextFormat(InsertTextFormat.Snippet)
118+
} else {
119+
item.setInsertTextFormat(InsertTextFormat.PlainText)
120+
}
115121

116122
r match {
117123
case i: TextEditMember =>
@@ -151,7 +157,9 @@ class CompletionProvider(
151157
case head :: Nil if head.forall(_.isImplicit) =>
152158
() // Don't set ($0) snippet for implicit-only params.
153159
case _ =>
154-
item.setTextEdit(textEdit(baseLabel + "($0)"))
160+
if (clientSupportsSnippets) {
161+
item.setTextEdit(textEdit(baseLabel + "($0)"))
162+
}
155163
metalsConfig
156164
.parameterHintsCommand()
157165
.asScala

mtags/src/main/scala/scala/meta/internal/pc/Completions.scala

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import scala.collection.immutable.Nil
2222
*/
2323
trait Completions { this: MetalsGlobal =>
2424

25+
val clientSupportsSnippets: Boolean =
26+
metalsConfig.isCompletionSnippetsEnabled()
27+
2528
/**
2629
* A member for symbols on the classpath that are not in scope, produced via workspace/symbol.
2730
*/
@@ -768,11 +771,11 @@ trait Completions { this: MetalsGlobal =>
768771
if (text.charAt(lit.pos.start - 1) != 's')
769772
List(new l.TextEdit(lit.pos.withEnd(lit.pos.start).toLSP, "s"))
770773
else Nil
771-
val dolarEdits = for {
774+
val dollarEdits = for {
772775
i <- lit.pos.start to (lit.pos.end - CURSOR.length())
773776
if text.charAt(i) == '$' && i != interpolator.dollar
774777
} yield new l.TextEdit(pos.source.position(i).withEnd(i).toLSP, "$")
775-
interpolatorEdit ++ dolarEdits
778+
interpolatorEdit ++ dollarEdits
776779
}
777780

778781
def newText(sym: Symbol): String = {
@@ -796,6 +799,7 @@ trait Completions { this: MetalsGlobal =>
796799

797800
val filter: String =
798801
text.substring(lit.pos.start, pos.point - interpolator.name.length)
802+
799803
override def contribute: List[Member] = {
800804
metalsScopeMembers(pos).collect {
801805
case s: ScopeMember
@@ -988,7 +992,7 @@ trait Completions { this: MetalsGlobal =>
988992
allParams.exists(param => param.name.startsWith(prefix))
989993
val isExplicitlyCalled = suffix.startsWith(prefix)
990994
val hasParamsToFill = allParams.count(!_.hasDefault) > 1
991-
if ((shouldShow || isExplicitlyCalled) && hasParamsToFill) {
995+
if ((shouldShow || isExplicitlyCalled) && hasParamsToFill && clientSupportsSnippets) {
992996
val editText = allParams.zipWithIndex
993997
.collect {
994998
case (param, index) if !param.hasDefault =>
@@ -1206,7 +1210,11 @@ trait Completions { this: MetalsGlobal =>
12061210
private def signature = printer.defaultMethodSignature()
12071211
private def edit = new l.TextEdit(
12081212
range,
1209-
s"$filterText$signature = $${0:???}"
1213+
if (clientSupportsSnippets) {
1214+
s"$filterText$signature = $${0:???}"
1215+
} else {
1216+
s"$filterText$signature = ???"
1217+
}
12101218
)
12111219
}
12121220

@@ -1311,7 +1319,11 @@ trait Completions { this: MetalsGlobal =>
13111319
"match",
13121320
new l.TextEdit(
13131321
editRange,
1314-
"match {\n\tcase$0\n}"
1322+
if (clientSupportsSnippets) {
1323+
"match {\n\tcase$0\n}"
1324+
} else {
1325+
"match"
1326+
}
13151327
),
13161328
completionsSymbol("match"),
13171329
label = Some("match"),
@@ -1325,7 +1337,11 @@ trait Completions { this: MetalsGlobal =>
13251337
tail
13261338
.map(_.edit.getNewText())
13271339
.mkString(
1328-
s"match {\n\t${head.edit.getNewText} $$0\n\t",
1340+
if (clientSupportsSnippets) {
1341+
s"match {\n\t${head.edit.getNewText} $$0\n\t"
1342+
} else {
1343+
s"match {\n\t${head.edit.getNewText}\n\t"
1344+
},
13291345
"\n\t",
13301346
"\n}"
13311347
)
@@ -1460,7 +1476,10 @@ trait Completions { this: MetalsGlobal =>
14601476
if (definitions.isTupleType(parents.selector)) {
14611477
result += new TextEditMember(
14621478
"case () =>",
1463-
new l.TextEdit(editRange, "case ($0) =>"),
1479+
new l.TextEdit(
1480+
editRange,
1481+
if (clientSupportsSnippets) "case ($0) =>" else "case () =>"
1482+
),
14641483
parents.selector.typeSymbol,
14651484
label = Some(s"case ${parents.selector} =>"),
14661485
command = metalsConfig.parameterHintsCommand().asScala
@@ -1518,8 +1537,10 @@ trait Completions { this: MetalsGlobal =>
15181537
val label = s"case $pattern =>"
15191538
new TextEditMember(
15201539
filterText = label,
1521-
edit =
1522-
new l.TextEdit(editRange, label + (if (isSnippet) " $0" else "")),
1540+
edit = new l.TextEdit(
1541+
editRange,
1542+
label + (if (isSnippet && clientSupportsSnippets) " $0" else "")
1543+
),
15231544
sym = sym,
15241545
label = Some(label),
15251546
additionalTextEdits = autoImports
@@ -1534,7 +1555,8 @@ trait Completions { this: MetalsGlobal =>
15341555
s"case _: $name",
15351556
new l.TextEdit(
15361557
editRange,
1537-
if (isSnippet) s"case $${0:_}: $name$suffix => "
1558+
if (isSnippet && clientSupportsSnippets)
1559+
s"case $${0:_}: $name$suffix => "
15381560
else s"case _: $name$suffix =>"
15391561
),
15401562
sym,

mtags/src/main/scala/scala/meta/internal/pc/MetalsGlobal.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -573,11 +573,11 @@ class MetalsGlobal(
573573

574574
def snippetCursor: String = sym.paramss match {
575575
case Nil =>
576-
"$0"
576+
if (clientSupportsSnippets) "$0" else ""
577577
case Nil :: Nil =>
578-
"()$0"
578+
if (clientSupportsSnippets) "()$0" else "()"
579579
case _ =>
580-
"($0)"
580+
if (clientSupportsSnippets) "($0)" else ""
581581
}
582582

583583
def isDefined: Boolean =

mtags/src/main/scala/scala/meta/internal/pc/PresentationCompilerConfigImpl.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ case class PresentationCompilerConfigImpl(
1919
isHoverDocumentationEnabled: Boolean = true,
2020
snippetAutoIndent: Boolean = true,
2121
isSignatureHelpDocumentationEnabled: Boolean = true,
22+
isCompletionSnippetsEnabled: Boolean = true,
2223
isCompletionItemResolve: Boolean = true,
2324
timeoutDelay: Long = 20,
2425
timeoutUnit: TimeUnit = TimeUnit.SECONDS
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package tests.pc
2+
3+
import tests.BaseCompletionSuite
4+
import scala.meta.pc.PresentationCompilerConfig
5+
import scala.meta.internal.pc.PresentationCompilerConfigImpl
6+
7+
object CompletionSnippetNegSuite extends BaseCompletionSuite {
8+
9+
override def config: PresentationCompilerConfig =
10+
PresentationCompilerConfigImpl(
11+
isCompletionSnippetsEnabled = false
12+
)
13+
14+
checkSnippet(
15+
"member",
16+
"""
17+
|object Main {
18+
| List.appl@@
19+
|}
20+
|""".stripMargin,
21+
"""|apply
22+
|unapplySeq
23+
|""".stripMargin,
24+
compat = Map(
25+
"2.13" ->
26+
// the second apply is from scala/collection/BuildFrom#apply(), introduced in 2.13
27+
"""|apply
28+
|unapplySeq
29+
|apply
30+
|""".stripMargin
31+
)
32+
)
33+
34+
checkSnippet(
35+
"scope",
36+
"""
37+
|object Main {
38+
| printl@@
39+
|
40+
|}
41+
|""".stripMargin,
42+
"""|println()
43+
|println
44+
|""".stripMargin
45+
)
46+
47+
checkSnippet(
48+
"java-nullary",
49+
"""
50+
|class Foo {
51+
| override def toString = "Foo"
52+
|}
53+
|object Main {
54+
| new Foo().toStrin@@
55+
|
56+
|}
57+
|""".stripMargin,
58+
// even if `Foo.toString` is nullary, it overrides `Object.toString()`
59+
// which is a Java non-nullary method with an empty parameter list.
60+
"""|toString()
61+
|""".stripMargin
62+
)
63+
64+
checkSnippet(
65+
"type",
66+
s"""|object Main {
67+
| val x: scala.IndexedSe@@
68+
|}
69+
|""".stripMargin,
70+
// It's expected to have two separate results, one for `object IndexedSeq` and one for `type IndexedSeq[T]`.
71+
"""|IndexedSeq
72+
|IndexedSeq
73+
|""".stripMargin
74+
)
75+
76+
}

0 commit comments

Comments
 (0)