Skip to content

Commit 941bc76

Browse files
committed
Remove artificial CURSOR added for the completions
1 parent 8f73af2 commit 941bc76

File tree

17 files changed

+160
-99
lines changed

17 files changed

+160
-99
lines changed

Diff for: compiler/src/dotty/tools/dotc/ast/NavigateAST.scala

+10-1
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
@@ -76,6 +77,8 @@ object NavigateAST {
7677
var bestFit: List[Positioned] = path
7778
while (it.hasNext) {
7879
val path1 = it.next() match {
80+
// FIXME this has to be changed to deterministicaly find recoveed tree
81+
case untpd.Select(qual, name) if name == StdNames.nme.??? => 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)
@@ -84,11 +87,17 @@ object NavigateAST {
8487
if ((path1 ne path) &&
8588
((bestFit eq path) ||
8689
bestFit.head.span != path1.head.span &&
87-
bestFit.head.span.contains(path1.head.span)))
90+
envelops(bestFit.head.span, path1.head.span)))
8891
bestFit = path1
8992
}
9093
bestFit
9194
}
95+
96+
def envelops(a: Span, b: Span): Boolean =
97+
!b.exists || a.exists && (
98+
(a.start < b.start && a.end >= b.end ) || (a.start <= b.start && a.end > b.end)
99+
)
100+
92101
/*
93102
* Annotations trees are located in the Type
94103
*/

Diff for: 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

Diff for: compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1725,7 +1725,7 @@ object Parsers {
17251725
case arg =>
17261726
arg
17271727
val args1 = args.mapConserve(sanitize)
1728-
1728+
17291729
if in.isArrow || isPureArrow || erasedArgs.contains(true) then
17301730
functionRest(args)
17311731
else

Diff for: language-server/test/dotty/tools/languageserver/CompletionTest.scala

+9
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,15 @@ class CompletionTest {
17051705
("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
17061706
))
17071707

1708+
@Test def testtest: Unit =
1709+
code"""|object M {
1710+
| def sel$m1
1711+
|}
1712+
|"""
1713+
.completion(m1, Set(
1714+
("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
1715+
))
1716+
17081717
@Test def noEnumCompletionInNewContext: Unit =
17091718
code"""|enum TestEnum:
17101719
| case TestCase

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ final class AutoImportsProvider(
6767
val results = symbols.result.filter(isExactMatch(_, name))
6868

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

Diff for: 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.

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/CompletionProvider.scala

+43-21
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import dotty.tools.dotc.ast.tpd.*
1414
import dotty.tools.dotc.core.Constants.Constant
1515
import dotty.tools.dotc.core.Contexts.Context
1616
import dotty.tools.dotc.core.Phases
17-
import dotty.tools.dotc.core.StdNames
17+
import dotty.tools.dotc.core.StdNames.nme
18+
import dotty.tools.dotc.core.Flags
1819
import dotty.tools.dotc.interactive.Interactive
20+
import dotty.tools.dotc.interactive.Completion
1921
import dotty.tools.dotc.interactive.InteractiveDriver
22+
import dotty.tools.dotc.parsing.Tokens
2023
import dotty.tools.dotc.util.SourceFile
2124
import dotty.tools.pc.AutoImports.AutoImportEdits
2225
import dotty.tools.pc.AutoImports.AutoImportsGenerator
@@ -45,23 +48,31 @@ class CompletionProvider(
4548
val uri = params.uri().nn
4649
val text = params.text().nn
4750

48-
val code = applyCompletionCursor(params)
51+
val (wasCursorApplied, code) = applyCompletionCursor(params)
4952
val sourceFile = SourceFile.virtual(uri, code)
5053
driver.run(uri, sourceFile)
5154

52-
val ctx = driver.currentCtx
55+
given ctx: Context = driver.currentCtx
5356
val pos = driver.sourcePosition(params)
5457
val (items, isIncomplete) = driver.compilationUnits.get(uri) match
5558
case Some(unit) =>
56-
5759
val newctx = ctx.fresh.setCompilationUnit(unit).withPhase(Phases.typerPhase(using ctx))
58-
val tpdPath = Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)
59-
val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)(using newctx)
60+
val tpdPath0 = Interactive.pathTo(unit.tpdTree, pos.span)(using newctx)
61+
val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath0, pos)(using newctx)
62+
63+
val tpdPath = tpdPath0 match
64+
// $1$ // FIXME add check for a $1$ name to make sure we only do the below in lifting case
65+
case Select(qual, name) :: tail if qual.symbol.is(Flags.Synthetic) =>
66+
qual.symbol.defTree match
67+
case valdef: ValDef => Select(valdef.rhs, name) :: tail
68+
case _ => tpdPath0
69+
case _ => tpdPath0
70+
6071

6172
val locatedCtx = Interactive.contextOfPath(tpdPath)(using newctx)
6273
val indexedCtx = IndexedContext(locatedCtx)
6374

64-
val completionPos = CompletionPos.infer(pos, params, adjustedPath)(using locatedCtx)
75+
val completionPos = CompletionPos.infer(pos, params, adjustedPath, wasCursorApplied)(using locatedCtx)
6576

6677
val autoImportsGen = AutoImports.generator(
6778
completionPos.toSourcePosition,
@@ -111,6 +122,10 @@ class CompletionProvider(
111122
)
112123
end completions
113124

125+
val allKeywords =
126+
val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using
127+
Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString)
128+
114129
/**
115130
* In case if completion comes from empty line like:
116131
* {{{
@@ -123,23 +138,30 @@ class CompletionProvider(
123138
* Otherwise, completion poisition doesn't point at any tree
124139
* because scala parser trim end position to the last statement pos.
125140
*/
126-
private def applyCompletionCursor(params: OffsetParams): String =
141+
private def applyCompletionCursor(params: OffsetParams): (Boolean, String) =
127142
val text = params.text().nn
128143
val offset = params.offset().nn
144+
val query = Completion.naiveCompletionPrefix(text, offset)
129145

130-
val isStartMultilineComment =
131-
val i = params.offset()
132-
i >= 3 && (text.charAt(i - 1) match
133-
case '*' =>
134-
text.charAt(i - 2) == '*' &&
135-
text.charAt(i - 3) == '/'
136-
case _ => false
137-
)
138-
if isStartMultilineComment then
139-
// Insert potentially missing `*/` to avoid comment out all codes after the "/**".
140-
text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset)
146+
if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart && !allKeywords.contains(query) then
147+
false -> text
141148
else
142-
text.substring(0, offset).nn + Cursor.value + text.substring(offset)
149+
val isStartMultilineComment =
150+
151+
val i = params.offset()
152+
i >= 3 && (text.charAt(i - 1) match
153+
case '*' =>
154+
text.charAt(i - 2) == '*' &&
155+
text.charAt(i - 3) == '/'
156+
case _ => false
157+
)
158+
true -> (
159+
if isStartMultilineComment then
160+
// Insert potentially missing `*/` to avoid comment out all codes after the "/**".
161+
text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset)
162+
else
163+
text.substring(0, offset).nn + Cursor.value + text.substring(offset)
164+
)
143165
end applyCompletionCursor
144166

145167
private def completionItems(
@@ -172,7 +194,7 @@ class CompletionProvider(
172194
Select(Apply(Select(Select(_, name), _), _), _),
173195
_
174196
) :: _ =>
175-
name == StdNames.nme.StringContext
197+
name == nme.StringContext
176198
// "My name is $name"
177199
case Literal(Constant(_: String)) :: _ =>
178200
true

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/Completions.scala

+3-11
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,9 @@ class Completions(
6363
/* In case of `method@@()` we should not add snippets and the path
6464
* will contain apply as the parent of the current tree.
6565
*/
66-
case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun =>
67-
false
68-
case _ :: (withcursor @ Select(fun, name)) :: (appl: GenericApply) :: _
69-
if appl.fun == withcursor && name.decoded == Cursor.value =>
70-
false
66+
case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun => false
67+
case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _
68+
if appl.fun == funSel && sel == fun => false
7169
case (_: (Import | Export)) :: _ => false
7270
case _ :: (_: (Import | Export)) :: _ => false
7371
case _ => true
@@ -479,14 +477,8 @@ class Completions(
479477
if tree.selectors.exists(_.renamed.sourcePos.contains(pos)) =>
480478
(List.empty, true)
481479

482-
// From Scala 3.1.3-RC3 (as far as I know), path contains
483-
// `Literal(Constant(null))` on head for an incomplete program, in this case, just ignore the head.
484-
case Literal(Constant(null)) :: tl =>
485-
advancedCompletions(tl, completionPos)
486-
487480
case _ =>
488481
val args = NamedArgCompletions.contribute(
489-
pos,
490482
path,
491483
adjustedPath,
492484
indexedContext,

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/InterpolatorCompletions.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ object InterpolatorCompletions:
224224
buildTargetIdentifier: String
225225
)(using ctx: Context, reportsContext: ReportContext): List[CompletionValue] =
226226
val litStartPos = lit.span.start
227-
val litEndPos = lit.span.end - Cursor.value.length()
227+
val litEndPos = lit.span.end - (if completionPos.withCURSOR then Cursor.value.length else 0)
228228
val position = completionPos.originalCursorPosition
229229
val span = position.span
230230
val nameStart =

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/NamedArgCompletions.scala

+16-15
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ import scala.annotation.tailrec
3535
object NamedArgCompletions:
3636

3737
def contribute(
38-
pos: SourcePosition,
3938
path: List[Tree],
40-
untypedPath: => List[untpd.Tree],
39+
untypedPath: List[untpd.Tree],
4140
indexedContext: IndexedContext,
4241
clientSupportsSnippets: Boolean,
4342
)(using ctx: Context): List[CompletionValue] =
@@ -64,12 +63,13 @@ object NamedArgCompletions:
6463
for
6564
app <- getApplyForContextFunctionParam(rest)
6665
if !app.fun.isInfix
67-
yield contribute(
68-
Some(ident),
69-
app,
70-
indexedContext,
71-
clientSupportsSnippets,
72-
)
66+
yield
67+
contribute(
68+
Some(ident),
69+
app,
70+
indexedContext,
71+
clientSupportsSnippets,
72+
)
7373
contribution.getOrElse(Nil)
7474
case (app: Apply) :: _ =>
7575
/**
@@ -156,10 +156,11 @@ object NamedArgCompletions:
156156
case _ => None
157157
val matchingMethods =
158158
for
159-
(name, indxContext) <- maybeNameAndIndexedContext(method)
160-
potentialMatches <- indxContext.findSymbol(name)
161-
yield potentialMatches.collect {
162-
case m
159+
(name, indexedContext) <- maybeNameAndIndexedContext(method)
160+
potentialMatches <- indexedContext.findSymbol(name)
161+
yield
162+
potentialMatches.collect {
163+
case m
163164
if m.is(Flags.Method) &&
164165
m.vparamss.length >= argss.length &&
165166
Try(m.isAccessibleFrom(apply.symbol.info)).toOption
@@ -179,8 +180,7 @@ object NamedArgCompletions:
179180
end fallbackFindMatchingMethods
180181

181182
val matchingMethods: List[Symbols.Symbol] =
182-
if method.symbol.paramSymss.nonEmpty
183-
then
183+
if method.symbol.paramSymss.nonEmpty then
184184
val allArgsAreSupplied =
185185
val vparamss = method.symbol.vparamss
186186
vparamss.length == argss.length && vparamss
@@ -295,6 +295,7 @@ object NamedArgCompletions:
295295
)
296296
}
297297

298+
// FIXME pass query here
298299
val prefix = ident
299300
.map(_.name.toString)
300301
.getOrElse("")
@@ -391,7 +392,7 @@ class FuzzyArgMatcher(tparams: List[Symbols.Symbol])(using Context):
391392
(expectedArgs.length == actualArgs.length ||
392393
(!allArgsProvided && expectedArgs.length >= actualArgs.length)) &&
393394
actualArgs.zipWithIndex.forall {
394-
case (Ident(name), _) if name.endsWith(Cursor.value) => true
395+
case (Ident(name), _) => true
395396
case (NamedArg(name, arg), _) =>
396397
expectedArgs.exists { expected =>
397398
expected.name == name && (!arg.hasType || arg.typeOpt.unfold

Diff for: presentation-compiler/src/main/dotty/tools/pc/completions/OverrideCompletions.scala

+15-1
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ object OverrideCompletions:
573573
)
574574
)
575575
// class Main extends Val:
576-
// he@@
576+
// he@@
577577
case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _
578578
if t.parents.nonEmpty =>
579579
Some(
@@ -586,6 +586,20 @@ object OverrideCompletions:
586586
)
587587
)
588588

589+
// class Main extends Val:
590+
// hello@ // this transforms into this.hello, thus is a Select
591+
case (sel @ Select(th: This, name)) :: (t: Template) :: (td: TypeDef) :: _
592+
if t.parents.nonEmpty && th.qual.name == td.name =>
593+
Some(
594+
(
595+
td,
596+
None,
597+
sel.sourcePos.start,
598+
false,
599+
Some(name.show),
600+
)
601+
)
602+
589603
case _ => None
590604

591605
end OverrideExtractor

0 commit comments

Comments
 (0)