diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 02386c04d708..c97cd065699f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -78,6 +78,22 @@ object Completion { Mode.None } + /** When dealing with in varios palces we check to see if they are + * due to incomplete backticks. If so, we ensure we get the full prefix + * including the backtick. + * + * @param content The source content that we'll check the positions for the prefix + * @param start The start position we'll start to look for the prefix at + * @param end The end position we'll look for the prefix at + * @return Either the full prefix including the ` or an empty string + */ + private def checkBacktickPrefix(content: Array[Char], start: Int, end: Int): String = + content.lift(start) match + case Some(char) if char == '`' => + content.slice(start, end).mkString + case _ => + "" + /** * Inspect `path` to determine the completion prefix. Only symbols whose name start with the * returned prefix should be considered. @@ -92,15 +108,14 @@ object Completion { completionPrefix(selector :: Nil, pos) }.getOrElse("") - // We special case Select here because we want to determine if the name - // is an error due to an unclosed backtick. - case (select: untpd.Select) :: _ if (select.name == nme.ERROR) => - val content = select.source.content() - content.lift(select.nameSpan.start) match - case Some(char) if char == '`' => - content.slice(select.nameSpan.start, select.span.end).mkString - case _ => - "" + // Foo.`se will result in Select(Ident(Foo), ) + case (select: untpd.Select) :: _ if select.name == nme.ERROR => + checkBacktickPrefix(select.source.content(), select.nameSpan.start, select.span.end) + + // import scala.util.chaining.`s will result in a Ident() + case (ident: untpd.Ident) :: _ if ident.name == nme.ERROR => + checkBacktickPrefix(ident.source.content(), ident.span.start, ident.span.end) + case (ref: untpd.RefTree) :: _ => if (ref.name == nme.ERROR) "" else ref.name.toString.take(pos.span.point - ref.span.point) diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index ca38d0725141..133341e1642f 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -195,6 +195,15 @@ class TabcompleteTests extends ReplTest { |Foo.`bac"""stripMargin)) } + @Test def backtickedImport = initially { + assertEquals( + List( + "`scalaUtilChainingOps`", + "`synchronized`" + ), + tabComplete("import scala.util.chaining.`s")) + } + @Test def commands = initially { assertEquals( List( diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index 683432ae2a1c..5b8f306b4e68 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -1121,4 +1121,13 @@ class CompletionTest { | val x = Bar.`fo${m1}""" .withSource.completion(m1, expected) } + + @Test def backticksImported: Unit = { + val expected = Set( + ("`scalaUtilChainingOps`", Method, "[A](a: A): scala.util.ChainingOps[A]"), + ("`synchronized`", Method, "[X0](x$0: X0): X0") + ) + code"""import scala.util.chaining.`s${m1}""" + .withSource.completion(m1, expected) + } }