diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 7a808aafb481..f6090783b55c 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -92,7 +92,14 @@ object Completion { }.getOrElse("") case (ref: untpd.RefTree) :: _ => - if (ref.name == nme.ERROR) "" + if (ref.name == nme.ERROR) { + val content = ref.source.content() + // if the error resulted from unclosed back tick + if content(ref.span.start) == '`' then + content.slice(ref.span.start, ref.span.end).mkString + else + "" + } else ref.name.toString.take(pos.span.point - ref.span.point) case _ => @@ -109,7 +116,9 @@ object Completion { private def computeCompletions(pos: SourcePosition, path: List[Tree])(using Context): (Int, List[Completion]) = { val mode = completionMode(path, pos) val prefix = completionPrefix(path, pos) - val completer = new Completer(mode, prefix, pos) + val startsWithBacktick = prefix.headOption.exists(_ == '`') + val cleanPrefix = if (startsWithBacktick) prefix.drop(1) else prefix + val completer = new Completer(mode, cleanPrefix, pos) val completions = path match { // Ignore synthetic select from `This` because in code it was `Ident` @@ -121,7 +130,7 @@ object Completion { case _ => completer.scopeCompletions } - val describedCompletions = describeCompletions(completions) + val describedCompletions = describeCompletions(completions, startsWithBacktick) val offset = completionOffset(path) interactiv.println(i"""completion with pos = $pos, @@ -136,12 +145,14 @@ object Completion { * Return the list of code completions with descriptions based on a mapping from names to the denotations they refer to. * If several denotations share the same name, each denotation will be transformed into a separate completion item. */ - def describeCompletions(completions: CompletionMap)(using Context): List[Completion] = + def describeCompletions(completions: CompletionMap, backtick: Boolean)(using Context): List[Completion] = for (name, denots) <- completions.toList denot <- denots - yield - Completion(name.show, description(denot), List(denot.symbol)) + yield { + val completionName = if (backtick) s"`${name.show}`" else name.show + Completion(completionName, description(denot), List(denot.symbol)) + } def description(denot: SingleDenotation)(using Context): String = if denot.isType then denot.symbol.showFullName diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index 807ae2bf5eec..698f57112b77 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -118,6 +118,7 @@ final class JLineTerminal extends java.io.Closeable { def currentToken: TokenData /* | Null */ = { val source = SourceFile.virtual("", input) val scanner = new Scanner(source)(using ctx.fresh.setReporter(Reporter.NoReporter)) + var lastBacktickErrorStart: Option[Int] = None while (scanner.token != EOF) { val start = scanner.offset val token = scanner.token @@ -126,7 +127,13 @@ final class JLineTerminal extends java.io.Closeable { val isCurrentToken = cursor >= start && cursor <= end if (isCurrentToken) - return TokenData(token, start, end) + return TokenData(token, lastBacktickErrorStart.getOrElse(start), end) + + // we need to enclose the last backtick, which unclosed produces ERROR token + if (token == ERROR && input(start) == '`') + lastBacktickErrorStart = Some(start) + else + lastBacktickErrorStart = None } null } diff --git a/compiler/test/dotty/tools/repl/TabcompleteTests.scala b/compiler/test/dotty/tools/repl/TabcompleteTests.scala index 540147bcd211..f18bdb47a3ff 100644 --- a/compiler/test/dotty/tools/repl/TabcompleteTests.scala +++ b/compiler/test/dotty/tools/repl/TabcompleteTests.scala @@ -138,4 +138,8 @@ class TabcompleteTests extends ReplTest { tabComplete("import quoted.* ; def fooImpl(using Quotes): Expr[Int] = { import quotes.reflect.* ; TypeRepr.of[Int].s")) } + @Test def wrongAnyMember: Unit = fromInitialState { implicit s => + assertEquals(List("`scalaUtilChainingOps`", "`synchronized`"), tabComplete("import scala.util.chaining.`s")) + assertEquals(List("`scalaUtilChainingOps`"), tabComplete("import scala.util.chaining.`sc")) + } } diff --git a/language-server/test/dotty/tools/languageserver/CompletionTest.scala b/language-server/test/dotty/tools/languageserver/CompletionTest.scala index ebeff2f44a29..521582aed53d 100644 --- a/language-server/test/dotty/tools/languageserver/CompletionTest.scala +++ b/language-server/test/dotty/tools/languageserver/CompletionTest.scala @@ -887,4 +887,38 @@ class CompletionTest { ) ) } + + @Test def wrongAnyMember: Unit = { + code"""|import scala.util.chaining.`sc${m1} + |""".withSource + .completion(m1, Set(("`scalaUtilChainingOps`",Method,"[A](a: A): scala.util.ChainingOps[A]"))) + } + + @Test def importBackticked: Unit = { + code"""|object O{ + | val `extends` = "" + |} + |import O.`extends`${m1} + |""".withSource + .completion(m1, Set(("extends",Field,"String"))) + } + + @Test def importBacktickedUnclosed: Unit = { + code"""|object O{ + | val `extends` = "" + |} + |import O.`extends${m1} + |""".withSource + .completion(m1, Set(("`extends`",Field,"String"))) + } + + + @Test def importBacktickedUnclosedSpace: Unit = { + code"""|object O{ + | val `extends ` = "" + |} + |import O.`extends ${m1} + |""".withSource + .completion(m1, Set(("`extends `",Field,"String"))) + } }