diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 333af6a26b3b..bdd60d062814 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -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: diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala index 6d89cb663b9c..40f1ccd2e797 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala @@ -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)) @@ -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. diff --git a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala index 28519debec63..8c8dd09c8c15 100644 --- a/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala +++ b/presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala @@ -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") diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala index 5d13c60b2fd5..d41261d2d21e 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionBacktickSuite.scala @@ -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" + )