Skip to content

Commit 748d439

Browse files
committed
Remove artificial CURSOR added for the completions
1 parent c0cfd0f commit 748d439

File tree

15 files changed

+159
-101
lines changed

15 files changed

+159
-101
lines changed

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

Lines changed: 10 additions & 1 deletion
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
*/

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,17 @@ object Completion:
121121
case _ =>
122122
""
123123

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

151-
case _ => pos.source.content.slice(fallback, pos.point).mkString
152+
case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point)
152153

153154

154155
end completionPrefix

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,15 @@ class CompletionTest {
17061706
("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
17071707
))
17081708

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

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

Lines changed: 1 addition & 1 deletion
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

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

Lines changed: 6 additions & 3 deletions
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.

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

Lines changed: 43 additions & 21 deletions
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
@@ -47,23 +50,31 @@ class CompletionProvider(
4750
val uri = params.uri().nn
4851
val text = params.text().nn
4952

50-
val code = applyCompletionCursor(params)
53+
val (wasCursorApplied, code) = applyCompletionCursor(params)
5154
val sourceFile = SourceFile.virtual(uri, code)
5255
driver.run(uri, sourceFile)
5356

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

6374
val locatedCtx = Interactive.contextOfPath(tpdPath)(using newctx)
6475
val indexedCtx = IndexedContext(locatedCtx)
6576

66-
val completionPos = CompletionPos.infer(pos, params, adjustedPath)(using locatedCtx)
77+
val completionPos = CompletionPos.infer(pos, params, adjustedPath, wasCursorApplied)(using locatedCtx)
6778

6879
val autoImportsGen = AutoImports.generator(
6980
completionPos.toSourcePosition,
@@ -114,6 +125,10 @@ class CompletionProvider(
114125
)
115126
end completions
116127

128+
val allKeywords =
129+
val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using
130+
Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString)
131+
117132
/**
118133
* In case if completion comes from empty line like:
119134
* {{{
@@ -126,23 +141,30 @@ class CompletionProvider(
126141
* Otherwise, completion poisition doesn't point at any tree
127142
* because scala parser trim end position to the last statement pos.
128143
*/
129-
private def applyCompletionCursor(params: OffsetParams): String =
144+
private def applyCompletionCursor(params: OffsetParams): (Boolean, String) =
130145
val text = params.text().nn
131146
val offset = params.offset().nn
147+
val query = Completion.naiveCompletionPrefix(text, offset)
132148

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

148170
private def completionItems(
@@ -175,7 +197,7 @@ class CompletionProvider(
175197
Select(Apply(Select(Select(_, name), _), _), _),
176198
_
177199
) :: _ =>
178-
name == StdNames.nme.StringContext
200+
name == nme.StringContext
179201
// "My name is $name"
180202
case Literal(Constant(_: String)) :: _ =>
181203
true

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,19 @@ class Completions(
6767
case _ :: (_: UnApply) :: _ => false
6868
case _ => true
6969

70-
private lazy val shouldAddSuffix = shouldAddSnippet &&
70+
private lazy val shouldAddSuffix = shouldAddSnippet &&
7171
(path match
7272
/* In case of `method@@()` we should not add snippets and the path
7373
* will contain apply as the parent of the current tree.
7474
*/
75-
case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun =>
76-
false
75+
case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun => false
7776
/* In case of `T@@[]` we should not add snippets.
7877
*/
79-
case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe =>
80-
false
81-
case _ :: (withcursor @ Select(fun, name)) :: (appl: GenericApply) :: _
82-
if appl.fun == withcursor && name.decoded == Cursor.value =>
83-
false
78+
case tpe :: (appl: AppliedTypeTree) :: _ if appl.tpt == tpe => false
79+
case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _
80+
if appl.fun == funSel && sel == fun => false
8481
case _ => true)
8582

86-
8783
private lazy val isNew: Boolean = Completion.isInNewContext(adjustedPath)
8884

8985
def includeSymbol(sym: Symbol)(using Context): Boolean =
@@ -521,14 +517,8 @@ class Completions(
521517
if tree.selectors.exists(_.renamed.sourcePos.contains(pos)) =>
522518
(List.empty, true)
523519

524-
// From Scala 3.1.3-RC3 (as far as I know), path contains
525-
// `Literal(Constant(null))` on head for an incomplete program, in this case, just ignore the head.
526-
case Literal(Constant(null)) :: tl =>
527-
advancedCompletions(tl, completionPos)
528-
529520
case _ =>
530521
val args = NamedArgCompletions.contribute(
531-
pos,
532522
path,
533523
adjustedPath,
534524
indexedContext,

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

Lines changed: 1 addition & 1 deletion
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 =

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

Lines changed: 16 additions & 15 deletions
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

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ object OverrideCompletions:
582582
)
583583
)
584584
// class Main extends Val:
585-
// he@@
585+
// he@@
586586
case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _
587587
if t.parents.nonEmpty =>
588588
Some(
@@ -595,6 +595,20 @@ object OverrideCompletions:
595595
)
596596
)
597597

598+
// class Main extends Val:
599+
// hello@ // this transforms into this.hello, thus is a Select
600+
case (sel @ Select(th: This, name)) :: (t: Template) :: (td: TypeDef) :: _
601+
if t.parents.nonEmpty && th.qual.name == td.name =>
602+
Some(
603+
(
604+
td,
605+
None,
606+
sel.sourcePos.start,
607+
false,
608+
Some(name.show),
609+
)
610+
)
611+
598612
case _ => None
599613

600614
end OverrideExtractor

0 commit comments

Comments
 (0)