Skip to content

Commit 4cc2bd4

Browse files
Backport "Remove artificial CURSOR added to code in the completions" to LTS (#22114)
Backports #20899 to the 3.3.5. PR submitted by the release tooling. [skip ci]
2 parents a83ec78 + 9f17510 commit 4cc2bd4

33 files changed

+438
-190
lines changed

compiler/src/dotty/tools/dotc/ast/NavigateAST.scala

+38-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ast
33

44
import core.Contexts.*
55
import core.Decorators.*
6+
import core.StdNames
67
import util.Spans.*
78
import Trees.{Closure, MemberDef, DefTree, WithLazyFields}
89
import dotty.tools.dotc.core.Types.AnnotatedType
@@ -74,21 +75,50 @@ object NavigateAST {
7475
def pathTo(span: Span, from: List[Positioned], skipZeroExtent: Boolean = false)(using Context): List[Positioned] = {
7576
def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = {
7677
var bestFit: List[Positioned] = path
77-
while (it.hasNext) {
78-
val path1 = it.next() match {
78+
while (it.hasNext) do
79+
val path1 = it.next() match
80+
case sel: untpd.Select if isRecoveryTree(sel) => path
81+
case sel: untpd.Ident if isPatternRecoveryTree(sel) => path
7982
case p: Positioned if !p.isInstanceOf[Closure[?]] => singlePath(p, path)
8083
case m: untpd.Modifiers => childPath(m.productIterator, path)
8184
case xs: List[?] => childPath(xs.iterator, path)
8285
case _ => path
83-
}
84-
if ((path1 ne path) &&
85-
((bestFit eq path) ||
86-
bestFit.head.span != path1.head.span &&
87-
bestFit.head.span.contains(path1.head.span)))
86+
87+
if (path1 ne path) && ((bestFit eq path) || isBetterFit(bestFit, path1)) then
8888
bestFit = path1
89-
}
89+
9090
bestFit
9191
}
92+
93+
/**
94+
* When choosing better fit we compare spans. If candidate span has starting or ending point inside (exclusive)
95+
* current best fit it is selected as new best fit. This means that same spans are failing the first predicate.
96+
*
97+
* In case when spans start and end at same offsets we prefer non synthethic one.
98+
*/
99+
def isBetterFit(currentBest: List[Positioned], candidate: List[Positioned]): Boolean =
100+
if currentBest.isEmpty && candidate.nonEmpty then true
101+
else if currentBest.nonEmpty && candidate.nonEmpty then
102+
val bestSpan = currentBest.head.span
103+
val candidateSpan = candidate.head.span
104+
105+
bestSpan != candidateSpan &&
106+
envelops(bestSpan, candidateSpan) ||
107+
bestSpan.contains(candidateSpan) && bestSpan.isSynthetic && !candidateSpan.isSynthetic
108+
else false
109+
110+
def isRecoveryTree(sel: untpd.Select): Boolean =
111+
sel.span.isSynthetic
112+
&& (sel.name == StdNames.nme.??? && sel.qualifier.symbol.name == StdNames.nme.Predef)
113+
114+
def isPatternRecoveryTree(ident: untpd.Ident): Boolean =
115+
ident.span.isSynthetic && StdNames.nme.WILDCARD == ident.name
116+
117+
def envelops(a: Span, b: Span): Boolean =
118+
!b.exists || a.exists && (
119+
(a.start < b.start && a.end >= b.end ) || (a.start <= b.start && a.end > b.end)
120+
)
121+
92122
/*
93123
* Annotations trees are located in the Type
94124
*/

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,17 @@ object Completion:
119119
case _ =>
120120
""
121121

122+
def naiveCompletionPrefix(text: String, offset: Int): String =
123+
var i = offset - 1
124+
while i >= 0 && text(i).isUnicodeIdentifierPart do i -= 1
125+
i += 1 // move to first character
126+
text.slice(i, offset)
127+
122128
/**
123129
* Inspect `path` to determine the completion prefix. Only symbols whose name start with the
124130
* returned prefix should be considered.
125131
*/
126132
def completionPrefix(path: List[untpd.Tree], pos: SourcePosition)(using Context): String =
127-
def fallback: Int =
128-
var i = pos.point - 1
129-
while i >= 0 && Character.isUnicodeIdentifierPart(pos.source.content()(i)) do i -= 1
130-
i + 1
131-
132133
path match
133134
case GenericImportSelector(sel) =>
134135
if sel.isGiven then completionPrefix(sel.bound :: Nil, pos)
@@ -146,7 +147,7 @@ object Completion:
146147
case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR =>
147148
tree.name.toString.take(pos.span.point - tree.span.point)
148149

149-
case _ => pos.source.content.slice(fallback, pos.point).mkString
150+
case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point)
150151

151152

152153
end completionPrefix

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ object Parsers {
398398
false
399399
}
400400

401-
def errorTermTree(start: Offset): Tree = atSpan(start, in.offset, in.offset) { unimplementedExpr }
401+
def errorTermTree(start: Offset): Tree = atSpan(Span(start, in.offset)) { unimplementedExpr }
402402

403403
private var inFunReturnType = false
404404
private def fromWithinReturnType[T](body: => T): T = {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ class HoverTest {
227227
@Test def enums: Unit = {
228228
code"""|package example
229229
|enum TestEnum3:
230-
| case ${m1}A${m2} // no tooltip
230+
| case ${m1}A${m2} // no tooltip
231231
|
232232
|"""
233233
.hover(m1 to m2, hoverContent("example.TestEnum3"))

presentation-compiler/src/main/dotty/tools/pc/AutoImportsProvider.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import dotty.tools.dotc.core.Symbols.*
1313
import dotty.tools.dotc.interactive.Interactive
1414
import dotty.tools.dotc.interactive.InteractiveDriver
1515
import dotty.tools.dotc.util.SourceFile
16-
import dotty.tools.pc.AutoImports.*
1716
import dotty.tools.pc.completions.CompletionPos
1817
import dotty.tools.pc.utils.InteractiveEnrichments.*
1918

@@ -67,7 +66,8 @@ final class AutoImportsProvider(
6766
val results = symbols.result.filter(isExactMatch(_, name))
6867

6968
if results.nonEmpty then
70-
val correctedPos = CompletionPos.infer(pos, params, path).toSourcePosition
69+
val correctedPos =
70+
CompletionPos.infer(pos, params, path, wasCursorApplied = false).toSourcePosition
7171
val mkEdit =
7272
path match
7373
// if we are in import section just specify full name

presentation-compiler/src/main/dotty/tools/pc/MetalsDriver.scala renamed to presentation-compiler/src/main/dotty/tools/pc/CachingDriver.scala

+7-7
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import dotty.tools.dotc.interactive.InteractiveDriver
77
import dotty.tools.dotc.reporting.Diagnostic
88
import dotty.tools.dotc.util.SourceFile
99

10+
import scala.compiletime.uninitialized
11+
1012
/**
11-
* MetalsDriver is a wrapper class that provides a compilation cache for InteractiveDriver.
12-
* MetalsDriver skips running compilation if
13+
* CachingDriver is a wrapper class that provides a compilation cache for InteractiveDriver.
14+
* CachingDriver skips running compilation if
1315
* - the target URI of `run` is the same as the previous target URI
1416
* - the content didn't change since the last compilation.
1517
*
@@ -25,11 +27,9 @@ import dotty.tools.dotc.util.SourceFile
2527
* To avoid the complexity related to currentCtx,
2628
* we decided to cache only when the target URI only if the same as the previous run.
2729
*/
28-
class MetalsDriver(
29-
override val settings: List[String]
30-
) extends InteractiveDriver(settings):
30+
class CachingDriver(override val settings: List[String]) extends InteractiveDriver(settings):
3131

32-
@volatile private var lastCompiledURI: URI = _
32+
@volatile private var lastCompiledURI: URI = uninitialized
3333

3434
private def alreadyCompiled(uri: URI, content: Array[Char]): Boolean =
3535
compilationUnits.get(uri) match
@@ -53,4 +53,4 @@ class MetalsDriver(
5353
lastCompiledURI = uri
5454
diags
5555

56-
end MetalsDriver
56+
end CachingDriver

presentation-compiler/src/main/dotty/tools/pc/PcInlayHintsProvider.scala

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import scala.meta.internal.pc.LabelPart.*
1414
import scala.meta.pc.InlayHintsParams
1515
import scala.meta.pc.SymbolSearch
1616

17-
import dotty.tools.dotc.ast.tpd
1817
import dotty.tools.dotc.ast.tpd.*
1918
import dotty.tools.dotc.core.Contexts.Context
2019
import dotty.tools.dotc.core.Flags

presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerAccess.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import scala.meta.internal.pc.CompilerAccess
88
import scala.meta.pc.PresentationCompilerConfig
99

1010
import dotty.tools.dotc.reporting.StoreReporter
11+
import dotty.tools.dotc.interactive.InteractiveDriver
1112

1213
class Scala3CompilerAccess(
1314
config: PresentationCompilerConfig,
1415
sh: Option[ScheduledExecutorService],
1516
newCompiler: () => Scala3CompilerWrapper
1617
)(using ec: ExecutionContextExecutor, rc: ReportContext)
17-
extends CompilerAccess[StoreReporter, MetalsDriver](
18+
extends CompilerAccess[StoreReporter, InteractiveDriver](
1819
config,
1920
sh,
2021
newCompiler,

presentation-compiler/src/main/dotty/tools/pc/Scala3CompilerWrapper.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import scala.meta.internal.pc.CompilerWrapper
44
import scala.meta.internal.pc.ReporterAccess
55

66
import dotty.tools.dotc.reporting.StoreReporter
7+
import dotty.tools.dotc.interactive.InteractiveDriver
78

8-
class Scala3CompilerWrapper(driver: MetalsDriver)
9-
extends CompilerWrapper[StoreReporter, MetalsDriver]:
9+
class Scala3CompilerWrapper(driver: InteractiveDriver)
10+
extends CompilerWrapper[StoreReporter, InteractiveDriver]:
1011

11-
override def compiler(): MetalsDriver = driver
12+
override def compiler(): InteractiveDriver = driver
1213

1314
override def resetReporter(): Unit =
1415
val ctx = driver.currentCtx

presentation-compiler/src/main/dotty/tools/pc/ScalaPresentationCompiler.scala

+15-19
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ import dotty.tools.pc.completions.CompletionProvider
3333
import dotty.tools.pc.InferExpectedType
3434
import dotty.tools.pc.completions.OverrideCompletions
3535
import dotty.tools.pc.buildinfo.BuildInfo
36+
import dotty.tools.pc.SymbolInformationProvider
37+
import dotty.tools.dotc.interactive.InteractiveDriver
3638

3739
import org.eclipse.lsp4j.DocumentHighlight
3840
import org.eclipse.lsp4j.TextEdit
3941
import org.eclipse.lsp4j as l
40-
import dotty.tools.pc.SymbolInformationProvider
42+
4143

4244
case class ScalaPresentationCompiler(
4345
buildTargetIdentifier: String = "",
@@ -76,14 +78,20 @@ case class ScalaPresentationCompiler(
7678
override def withReportsLoggerLevel(level: String): PresentationCompiler =
7779
copy(reportsLevel = ReportLevel.fromString(level))
7880

79-
val compilerAccess: CompilerAccess[StoreReporter, MetalsDriver] =
81+
val compilerAccess: CompilerAccess[StoreReporter, InteractiveDriver] =
8082
Scala3CompilerAccess(
8183
config,
8284
sh,
83-
() => new Scala3CompilerWrapper(newDriver)
84-
)(using
85-
ec
86-
)
85+
() => new Scala3CompilerWrapper(CachingDriver(driverSettings))
86+
)(using ec)
87+
88+
val driverSettings =
89+
val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0")
90+
val defaultFlags = List("-color:never")
91+
val filteredOptions = removeDoubleOptions(options.filterNot(forbiddenOptions))
92+
93+
filteredOptions ::: defaultFlags ::: implicitSuggestionTimeout ::: "-classpath" :: classpath
94+
.mkString(File.pathSeparator) :: Nil
8795

8896
private def removeDoubleOptions(options: List[String]): List[String] =
8997
options match
@@ -92,19 +100,6 @@ case class ScalaPresentationCompiler(
92100
case head :: tail => head :: removeDoubleOptions(tail)
93101
case Nil => options
94102

95-
def newDriver: MetalsDriver =
96-
val implicitSuggestionTimeout = List("-Ximport-suggestion-timeout", "0")
97-
val defaultFlags = List("-color:never")
98-
val filteredOptions = removeDoubleOptions(
99-
options.filterNot(forbiddenOptions)
100-
)
101-
val settings =
102-
filteredOptions ::: defaultFlags ::: implicitSuggestionTimeout ::: "-classpath" :: classpath
103-
.mkString(
104-
File.pathSeparator
105-
) :: Nil
106-
new MetalsDriver(settings)
107-
108103
override def semanticTokens(
109104
params: VirtualFileParams
110105
): CompletableFuture[ju.List[Node]] =
@@ -146,6 +141,7 @@ case class ScalaPresentationCompiler(
146141
new CompletionProvider(
147142
search,
148143
driver,
144+
() => InteractiveDriver(driverSettings),
149145
params,
150146
config,
151147
buildTargetIdentifier,

presentation-compiler/src/main/dotty/tools/pc/ScriptFirstImportPosition.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dotty.tools.pc
22

3-
import dotty.tools.dotc.ast.tpd.*
43
import dotty.tools.dotc.core.Comments.Comment
54

65
object ScriptFirstImportPosition:

presentation-compiler/src/main/dotty/tools/pc/SignatureHelpProvider.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package dotty.tools.pc
22

3-
import dotty.tools.dotc.ast.tpd.*
43
import dotty.tools.dotc.core.Contexts.*
54
import dotty.tools.dotc.core.Flags
65
import dotty.tools.dotc.core.Symbols.*

presentation-compiler/src/main/dotty/tools/pc/SymbolInformationProvider.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import scala.meta.pc.PcSymbolKind
77
import scala.meta.pc.PcSymbolProperty
88

99
import dotty.tools.dotc.core.Contexts.Context
10-
import dotty.tools.dotc.core.Denotations.Denotation
11-
import dotty.tools.dotc.core.Denotations.MultiDenotation
1210
import dotty.tools.dotc.core.Flags
1311
import dotty.tools.dotc.core.Names.*
1412
import dotty.tools.dotc.core.StdNames.nme
@@ -19,6 +17,7 @@ import dotty.tools.pc.utils.InteractiveEnrichments.allSymbols
1917
import dotty.tools.pc.utils.InteractiveEnrichments.stripBackticks
2018
import scala.meta.internal.pc.PcSymbolInformation
2119
import scala.meta.internal.pc.SymbolInfo
20+
import dotty.tools.dotc.core.Denotations.{Denotation, MultiDenotation}
2221

2322
class SymbolInformationProvider(using Context):
2423

presentation-compiler/src/main/dotty/tools/pc/completions/CompletionPos.scala

+6-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ case class CompletionPos(
2222
identEnd: Int,
2323
query: String,
2424
originalCursorPosition: SourcePosition,
25-
sourceUri: URI
25+
sourceUri: URI,
26+
withCURSOR: Boolean
2627
):
2728
def queryEnd: Int = originalCursorPosition.point
2829
def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd))
@@ -34,17 +35,19 @@ object CompletionPos:
3435
def infer(
3536
sourcePos: SourcePosition,
3637
offsetParams: OffsetParams,
37-
adjustedPath: List[Tree]
38+
adjustedPath: List[Tree],
39+
wasCursorApplied: Boolean
3840
)(using Context): CompletionPos =
3941
val identEnd = adjustedPath match
4042
case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) =>
4143
refTree.span.end - Cursor.value.length
44+
case (refTree: RefTree) :: _ => refTree.span.end
4245
case _ => sourcePos.end
4346

4447
val query = Completion.completionPrefix(adjustedPath, sourcePos)
4548
val start = sourcePos.end - query.length()
4649

47-
CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn)
50+
CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied)
4851

4952
/**
5053
* Infer the indentation by counting the number of spaces in the given line.

0 commit comments

Comments
 (0)