Skip to content

Commit

Permalink
Better LSP completions inside of backticks (#22555)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tgodzik authored Feb 9, 2025
2 parents 9066923 + 936f7ad commit a6a486e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 12 deletions.
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,12 @@ object Completion:
checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end)

case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR =>
tree.name.toString.take(pos.span.point - tree.span.point)

case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point)

val nameStart = tree.span.point
val start = if pos.source.content().lift(nameStart).contains('`') then nameStart + 1 else nameStart
tree.name.toString.take(pos.span.point - start)

case _ =>
naiveCompletionPrefix(pos.source.content().mkString, pos.point)
end completionPrefix

private object GenericImportSelector:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ case class CompletionPos(
query: String,
originalCursorPosition: SourcePosition,
sourceUri: URI,
withCURSOR: Boolean
withCURSOR: Boolean,
hasLeadingBacktick: Boolean,
hasTrailingBacktick: Boolean
):
def queryEnd: Int = originalCursorPosition.point
def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd))
Expand All @@ -38,16 +40,35 @@ object CompletionPos:
adjustedPath: List[Tree],
wasCursorApplied: Boolean
)(using Context): CompletionPos =
val identEnd = adjustedPath match
def hasBacktickAt(offset: Int): Boolean =
sourcePos.source.content().lift(offset).contains('`')

val (identEnd, hasTrailingBacktick) = adjustedPath match
case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) =>
refTree.span.end - Cursor.value.length
case (refTree: RefTree) :: _ => refTree.span.end
case _ => sourcePos.end
val refTreeEnd = refTree.span.end
val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1)
val identEnd = refTreeEnd - Cursor.value.length
(if hasTrailingBacktick then identEnd - 1 else identEnd, hasTrailingBacktick)
case (refTree: RefTree) :: _ =>
val refTreeEnd = refTree.span.end
val hasTrailingBacktick = hasBacktickAt(refTreeEnd - 1)
(if hasTrailingBacktick then refTreeEnd - 1 else refTreeEnd, hasTrailingBacktick)
case _ => (sourcePos.end, false)

val query = Completion.completionPrefix(adjustedPath, sourcePos)
val start = sourcePos.end - query.length()
val hasLeadingBacktick = hasBacktickAt(start - 1)

CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied)
CompletionPos(
start,
identEnd,
query.nn,
sourcePos,
offsetParams.uri.nn,
wasCursorApplied,
hasLeadingBacktick,
hasTrailingBacktick
)

/**
* Infer the indentation by counting the number of spaces in the given line.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,17 @@ class CompletionProvider(
range: Option[LspRange] = None
): CompletionItem =
val oldText = params.text().nn.substring(completionPos.queryStart, completionPos.identEnd)
val editRange = if newText.startsWith(oldText) then completionPos.stripSuffixEditRange
val trimmedNewText = {
var nt = newText
if (completionPos.hasLeadingBacktick) nt = nt.stripPrefix("`")
if (completionPos.hasTrailingBacktick) nt = nt.stripSuffix("`")
nt
}

val editRange = if trimmedNewText.startsWith(oldText) then completionPos.stripSuffixEditRange
else completionPos.toEditRange

val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(newText))
val textEdit = new TextEdit(range.getOrElse(editRange), wrapInBracketsIfRequired(trimmedNewText))

val item = new CompletionItem(label)
item.setSortText(f"${idx}%05d")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,60 @@ class CompletionBacktickSuite extends BaseCompletionSuite:
|}
|""".stripMargin
)

@Test def `add-backticks-around-identifier` =
checkEdit(
"""|object Main {
| def `Foo Bar` = 123
| Foo@@
|}
|""".stripMargin,
"""|object Main {
| def `Foo Bar` = 123
| `Foo Bar`
|}
|""".stripMargin
)

@Test def `complete-inside-backticks` =
checkEdit(
"""|object Main {
| def `Foo Bar` = 123
| `Foo@@`
|}
|""".stripMargin,
"""|object Main {
| def `Foo Bar` = 123
| `Foo Bar`
|}
|""".stripMargin
)

@Test def `complete-inside-backticks-after-space` =
checkEdit(
"""|object Main {
| def `Foo Bar` = 123
| `Foo B@@a`
|}
|""".stripMargin,
"""|object Main {
| def `Foo Bar` = 123
| `Foo Bar`
|}
|""".stripMargin
)

@Test def `complete-inside-empty-backticks` =
checkEdit(
"""|object Main {
| def `Foo Bar` = 123
| `@@`
|}
|""".stripMargin,
"""|object Main {
| def `Foo Bar` = 123
| `Foo Bar`
|}
|""".stripMargin,
filter = _ == "Foo Bar: Int"
)

0 comments on commit a6a486e

Please sign in to comment.