From ffc202da0dd6384bf1959fa6f8ac137b6c468cb5 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 24 Aug 2021 14:50:08 +0200 Subject: [PATCH] Try to extract name when backtick is missing Previously, completion prefix would show up as an empty string if we had an unclosed backtick. Now, we try to extract the prefix from the file contents in that case. An alternative approach would be to tranfer somehow the name string in `nme.Error`, but I didn't see anything like that done before, so not sure how to properly address that. Fixes https://github.com/lampepfl/dotty/issues/12514 --- .../tools/dotc/interactive/Completion.scala | 23 +++++++++---- .../src/dotty/tools/repl/JLineTerminal.scala | 9 ++++- .../dotty/tools/repl/TabcompleteTests.scala | 4 +++ .../tools/languageserver/CompletionTest.scala | 34 +++++++++++++++++++ 4 files changed, 63 insertions(+), 7 deletions(-) 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"))) + } }