Skip to content

Commit 96b3909

Browse files
committed
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 #12514
1 parent ffd9471 commit 96b3909

File tree

5 files changed

+64
-8
lines changed

5 files changed

+64
-8
lines changed

compiler/src/dotty/tools/dotc/interactive/Completion.scala

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,14 @@ object Completion {
9292
}.getOrElse("")
9393

9494
case (ref: untpd.RefTree) :: _ =>
95-
if (ref.name == nme.ERROR) ""
95+
if (ref.name == nme.ERROR) {
96+
val content = ref.source.content()
97+
// if the error resulted from unclosed back tick
98+
if content(ref.span.start) == '`' then
99+
content.slice(ref.span.start, ref.span.end).mkString
100+
else
101+
""
102+
}
96103
else ref.name.toString.take(pos.span.point - ref.span.point)
97104

98105
case _ =>
@@ -109,7 +116,9 @@ object Completion {
109116
private def computeCompletions(pos: SourcePosition, path: List[Tree])(using Context): (Int, List[Completion]) = {
110117
val mode = completionMode(path, pos)
111118
val prefix = completionPrefix(path, pos)
112-
val completer = new Completer(mode, prefix, pos)
119+
val startsWithBacktick = prefix.headOption.exists(_ == '`')
120+
val cleanPrefix = if (startsWithBacktick) prefix.drop(1) else prefix
121+
val completer = new Completer(mode, cleanPrefix, pos)
113122

114123
val completions = path match {
115124
// Ignore synthetic select from `This` because in code it was `Ident`
@@ -121,7 +130,7 @@ object Completion {
121130
case _ => completer.scopeCompletions
122131
}
123132

124-
val describedCompletions = describeCompletions(completions)
133+
val describedCompletions = describeCompletions(completions, startsWithBacktick)
125134
val offset = completionOffset(path)
126135

127136
interactiv.println(i"""completion with pos = $pos,
@@ -136,12 +145,14 @@ object Completion {
136145
* Return the list of code completions with descriptions based on a mapping from names to the denotations they refer to.
137146
* If several denotations share the same name, each denotation will be transformed into a separate completion item.
138147
*/
139-
def describeCompletions(completions: CompletionMap)(using Context): List[Completion] =
148+
def describeCompletions(completions: CompletionMap, backtick: Boolean)(using Context): List[Completion] =
140149
for
141150
(name, denots) <- completions.toList
142151
denot <- denots
143-
yield
144-
Completion(name.show, description(denot), List(denot.symbol))
152+
yield {
153+
val completionName = if (backtick) s"`${name.show}`" else name.show
154+
Completion(completionName, description(denot), List(denot.symbol))
155+
}
145156

146157
def description(denot: SingleDenotation)(using Context): String =
147158
if denot.isType then denot.symbol.showFullName

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ object Scanners {
107107

108108
/** Generate an error at the given offset */
109109
def error(msg: String, off: Offset = offset): Unit = {
110-
errorButContinue(msg, off)
110+
errorButContinue(msg)
111111
token = ERROR
112112
errOffset = off
113113
}

compiler/src/dotty/tools/repl/JLineTerminal.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ final class JLineTerminal extends java.io.Closeable {
118118
def currentToken: TokenData /* | Null */ = {
119119
val source = SourceFile.virtual("<completions>", input)
120120
val scanner = new Scanner(source)(using ctx.fresh.setReporter(Reporter.NoReporter))
121+
var lastBacktickErrorStart: Option[Int] = None
121122
while (scanner.token != EOF) {
122123
val start = scanner.offset
123124
val token = scanner.token
@@ -126,7 +127,13 @@ final class JLineTerminal extends java.io.Closeable {
126127

127128
val isCurrentToken = cursor >= start && cursor <= end
128129
if (isCurrentToken)
129-
return TokenData(token, start, end)
130+
return TokenData(token, lastBacktickErrorStart.getOrElse(start), end)
131+
132+
// we need to enclose the last backtick, which unclosed produces ERROR token
133+
if (token == ERROR && input(start) == '`')
134+
lastBacktickErrorStart = Some(start)
135+
else
136+
lastBacktickErrorStart = None
130137
}
131138
null
132139
}

compiler/test/dotty/tools/repl/TabcompleteTests.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,8 @@ class TabcompleteTests extends ReplTest {
138138
tabComplete("import quoted.* ; def fooImpl(using Quotes): Expr[Int] = { import quotes.reflect.* ; TypeRepr.of[Int].s"))
139139
}
140140

141+
@Test def wrongAnyMember: Unit = fromInitialState { implicit s =>
142+
assertEquals(List("`scalaUtilChainingOps`", "`synchronized`"), tabComplete("import scala.util.chaining.`s"))
143+
assertEquals(List("`scalaUtilChainingOps`"), tabComplete("import scala.util.chaining.`sc"))
144+
}
141145
}

language-server/test/dotty/tools/languageserver/CompletionTest.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,4 +887,38 @@ class CompletionTest {
887887
)
888888
)
889889
}
890+
891+
@Test def wrongAnyMember: Unit = {
892+
code"""|import scala.util.chaining.`sc${m1}
893+
|""".withSource
894+
.completion(m1, Set(("`scalaUtilChainingOps`",Method,"[A](a: A): scala.util.ChainingOps[A]")))
895+
}
896+
897+
@Test def importBackticked: Unit = {
898+
code"""|object O{
899+
| val `extends` = ""
900+
|}
901+
|import O.`extends`${m1}
902+
|""".withSource
903+
.completion(m1, Set(("extends",Field,"String")))
904+
}
905+
906+
@Test def importBacktickedUnclosed: Unit = {
907+
code"""|object O{
908+
| val `extends` = ""
909+
|}
910+
|import O.`extends${m1}
911+
|""".withSource
912+
.completion(m1, Set(("`extends`",Field,"String")))
913+
}
914+
915+
916+
@Test def importBacktickedUnclosedSpace: Unit = {
917+
code"""|object O{
918+
| val `extends ` = ""
919+
|}
920+
|import O.`extends ${m1}
921+
|""".withSource
922+
.completion(m1, Set(("`extends `",Field,"String")))
923+
}
890924
}

0 commit comments

Comments
 (0)