Skip to content

Commit a6a486e

Browse files
authored
Better LSP completions inside of backticks (#22555)
This improves the presentation compiler name completions inside of backticks. The existing gaps which motivate doing this are outlined in [this Metals feature request][0]. [0]: scalameta/metals-feature-requests#418
2 parents 9066923 + 936f7ad commit a6a486e

File tree

4 files changed

+98
-12
lines changed

4 files changed

+98
-12
lines changed

Diff for: compiler/src/dotty/tools/dotc/interactive/Completion.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,12 @@ object Completion:
147147
checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end)
148148

149149
case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR =>
150-
tree.name.toString.take(pos.span.point - tree.span.point)
151-
152-
case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point)
153-
150+
val nameStart = tree.span.point
151+
val start = if pos.source.content().lift(nameStart).contains('`') then nameStart + 1 else nameStart
152+
tree.name.toString.take(pos.span.point - start)
154153

154+
case _ =>
155+
naiveCompletionPrefix(pos.source.content().mkString, pos.point)
155156
end completionPrefix
156157

157158
private object GenericImportSelector:

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala

+27-6
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ case class CompletionPos(
2323
query: String,
2424
originalCursorPosition: SourcePosition,
2525
sourceUri: URI,
26-
withCURSOR: Boolean
26+
withCURSOR: Boolean,
27+
hasLeadingBacktick: Boolean,
28+
hasTrailingBacktick: Boolean
2729
):
2830
def queryEnd: Int = originalCursorPosition.point
2931
def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd))
@@ -38,16 +40,35 @@ object CompletionPos:
3840
adjustedPath: List[Tree],
3941
wasCursorApplied: Boolean
4042
)(using Context): CompletionPos =
41-
val identEnd = adjustedPath match
43+
def hasBacktickAt(offset: Int): Boolean =
44+
sourcePos.source.content().lift(offset).contains('`')
45+
46+
val (identEnd, hasTrailingBacktick) = adjustedPath match
4247
case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) =>
43-
refTree.span.end - Cursor.value.length
44-
case (refTree: RefTree) :: _ => refTree.span.end
45-
case _ => sourcePos.end
48+
val refTreeEnd = refTree.span.end
49+
val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1)
50+
val identEnd = refTreeEnd - Cursor.value.length
51+
(if hasTrailingBacktick then identEnd - 1 else identEnd, hasTrailingBacktick)
52+
case (refTree: RefTree) :: _ =>
53+
val refTreeEnd = refTree.span.end
54+
val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1)
55+
(if hasTrailingBacktick then refTreeEnd - 1 else refTreeEnd, hasTrailingBacktick)
56+
case _ => (sourcePos.end, false)
4657

4758
val query = Completion.completionPrefix(adjustedPath, sourcePos)
4859
val start = sourcePos.end - query.length()
60+
val hasLeadingBacktick = hasBacktickAt(start - 1)
4961

50-
CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied)
62+
CompletionPos(
63+
start,
64+
identEnd,
65+
query.nn,
66+
sourcePos,
67+
offsetParams.uri.nn,
68+
wasCursorApplied,
69+
hasLeadingBacktick,
70+
hasTrailingBacktick
71+
)
5172

5273
/**
5374
* Infer the indentation by counting the number of spaces in the given line.

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala

+9-2
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,17 @@ class CompletionProvider(
248248
range: Option[LspRange] = None
249249
): CompletionItem =
250250
val oldText = params.text().nn.substring(completionPos.queryStart, completionPos.identEnd)
251-
val editRange = if newText.startsWith(oldText) then completionPos.stripSuffixEditRange
251+
val trimmedNewText = {
252+
var nt = newText
253+
if (completionPos.hasLeadingBacktick) nt = nt.stripPrefix("`")
254+
if (completionPos.hasTrailingBacktick) nt = nt.stripSuffix("`")
255+
nt
256+
}
257+
258+
val editRange = if trimmedNewText.startsWith(oldText) then completionPos.stripSuffixEditRange
252259
else completionPos.toEditRange
253260

254-
val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(newText))
261+
val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(trimmedNewText))
255262

256263
val item = new CompletionItem(label)
257264
item.setSortText(f"${idx}%05d")

Diff for: presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala

+57
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,60 @@ class CompletionBacktickSuite extends BaseCompletionSuite:
179179
|}
180180
|""".stripMargin
181181
)
182+
183+
@Test def `add-backticks-around-identifier` =
184+
checkEdit(
185+
"""|object Main {
186+
| def `Foo Bar` = 123
187+
| Foo@@
188+
|}
189+
|""".stripMargin,
190+
"""|object Main {
191+
| def `Foo Bar` = 123
192+
| `Foo Bar`
193+
|}
194+
|""".stripMargin
195+
)
196+
197+
@Test def `complete-inside-backticks` =
198+
checkEdit(
199+
"""|object Main {
200+
| def `Foo Bar` = 123
201+
| `Foo@@`
202+
|}
203+
|""".stripMargin,
204+
"""|object Main {
205+
| def `Foo Bar` = 123
206+
| `Foo Bar`
207+
|}
208+
|""".stripMargin
209+
)
210+
211+
@Test def `complete-inside-backticks-after-space` =
212+
checkEdit(
213+
"""|object Main {
214+
| def `Foo Bar` = 123
215+
| `Foo B@@a`
216+
|}
217+
|""".stripMargin,
218+
"""|object Main {
219+
| def `Foo Bar` = 123
220+
| `Foo Bar`
221+
|}
222+
|""".stripMargin
223+
)
224+
225+
@Test def `complete-inside-empty-backticks` =
226+
checkEdit(
227+
"""|object Main {
228+
| def `Foo Bar` = 123
229+
| `@@`
230+
|}
231+
|""".stripMargin,
232+
"""|object Main {
233+
| def `Foo Bar` = 123
234+
| `Foo Bar`
235+
|}
236+
|""".stripMargin,
237+
filter = _ == "Foo Bar: Int"
238+
)

0 commit comments

Comments
 (0)