Skip to content

Better LSP completions inside of backticks #22555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
)
Loading