Skip to content

Commit 936f7ad

Browse files
committed
Better LSP completions inside of backticks
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
1 parent aa9db1f commit 936f7ad

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)