Skip to content

Commit 64e2d6e

Browse files
committed
Apply comments, make new completions work with fully qualified path prefix
1 parent d7d27f5 commit 64e2d6e

File tree

8 files changed

+133
-43
lines changed

8 files changed

+133
-43
lines changed
Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,95 @@
11
package dotty.tools.pc.completions
22

3+
import org.eclipse.lsp4j.TextEdit
4+
import org.eclipse.lsp4j.Position
5+
36
/**
47
* @param suffixes which we should insert
8+
* @param prefixes which we should insert
59
* @param snippet which suffix should we insert the snippet $0
610
*/
711
case class CompletionAffix(
8-
suffixes: Set[SuffixKind],
9-
prefixes: List[PrefixKind],
10-
snippet: SuffixKind,
12+
suffixes: Set[Suffix],
13+
prefixes: Set[Prefix],
14+
snippet: Suffix,
15+
currentPrefix: Option[String],
1116
):
12-
def addLabelSnippet = suffixes.contains(SuffixKind.Bracket)
13-
def hasSnippet = snippet != SuffixKind.NoSuffix
17+
def addLabelSnippet = suffixes.exists(_.kind == SuffixKind.Bracket)
18+
def hasSnippet = snippet.kind != SuffixKind.NoSuffix
1419
def chain(copyFn: CompletionAffix => CompletionAffix) = copyFn(this)
15-
def withNewSuffix(kind: SuffixKind) = this.copy(suffixes = suffixes + kind)
16-
def withNewPrefix(kind: PrefixKind) = this.copy(prefixes = prefixes :+ kind)
17-
def withNewSuffixSnippet(kind: SuffixKind) =
18-
this.copy(suffixes = suffixes + kind, snippet = kind)
20+
def withNewSuffix(kind: Suffix) = this.copy(suffixes = suffixes + kind)
21+
def withNewPrefix(kind: Prefix) = this.copy(prefixes = prefixes + kind)
22+
def withCurrentPrefix(currentPrefix: String) = this.copy(currentPrefix = Some(currentPrefix))
23+
def withNewSuffixSnippet(suffix: Suffix) =
24+
this.copy(suffixes = suffixes + suffix, snippet = suffix)
1925

2026
def nonEmpty: Boolean = suffixes.nonEmpty || prefixes.nonEmpty
2127

2228
def toSuffix: String =
2329
def loop(suffixes: List[SuffixKind]): String =
24-
def cursor = if suffixes.head == snippet then "$0" else ""
30+
def cursor = if suffixes.head == snippet.kind then "$0" else ""
2531
suffixes match
2632
case SuffixKind.Brace :: tail => s"($cursor)" + loop(tail)
2733
case SuffixKind.Bracket :: tail => s"[$cursor]" + loop(tail)
2834
case SuffixKind.Template :: tail => s" {$cursor}" + loop(tail)
2935
case _ => ""
30-
loop(suffixes.toList)
36+
loop(suffixes.toList.map(_.kind))
3137

3238
def toSuffixOpt: Option[String] =
3339
val edit = toSuffix
3440
if edit.nonEmpty then Some(edit) else None
3541

42+
43+
given Ordering[Position] = Ordering.by(elem => (elem.getLine, elem.getCharacter))
44+
45+
def toInsertRange: Option[org.eclipse.lsp4j.Range] =
46+
import scala.language.unsafeNulls
47+
48+
val ranges = prefixes.collect:
49+
case Affix(_, Some(range)) => range
50+
.toList
51+
for
52+
startPos <- ranges.map(_.getStart).minOption
53+
endPos <- ranges.map(_.getEnd).maxOption
54+
yield org.eclipse.lsp4j.Range(startPos, endPos)
55+
56+
private def loopPrefix(prefixes: List[PrefixKind]) =
57+
prefixes match
58+
case PrefixKind.New :: tail => "new "
59+
case _ => ""
60+
61+
/**
62+
* We need to insert previous prefix, but we don't want to display it in the label i.e.
63+
* ```scala
64+
* scala.util.Tr@@
65+
* ````
66+
* should return `new Try[T]: Try[T]`
67+
* but insert `new scala.util.Try`
68+
*
69+
*/
70+
def toInsertPrefix: String =
71+
loopPrefix(prefixes.toList.map(_.kind)) + currentPrefix.getOrElse("")
72+
3673
def toPrefix: String =
37-
def loop(prefixes: List[PrefixKind]) =
38-
prefixes match
39-
case PrefixKind.New :: tail => "new "
40-
case _ => ""
41-
loop(prefixes)
74+
loopPrefix(prefixes.toList.map(_.kind))
4275

4376
end CompletionAffix
4477

4578
object CompletionAffix:
4679
val empty = CompletionAffix(
4780
suffixes = Set.empty,
48-
prefixes = Nil,
49-
snippet = SuffixKind.NoSuffix,
81+
prefixes = Set.empty,
82+
snippet = Affix(SuffixKind.NoSuffix),
83+
currentPrefix = None,
5084
)
5185

5286
enum SuffixKind:
5387
case Brace, Bracket, Template, NoSuffix
5488

5589
enum PrefixKind:
5690
case New
91+
92+
type Suffix = Affix[SuffixKind]
93+
type Prefix = Affix[PrefixKind]
94+
95+
private case class Affix[+T](kind: T, insertRange: Option[org.eclipse.lsp4j.Range] = None)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ class CompletionProvider(
197197
end mkItem
198198

199199
val completionTextSuffix = completion.snippetAffix.toSuffix
200-
val completionTextPrefix = completion.snippetAffix.toPrefix
200+
val completionTextPrefix = completion.snippetAffix.toInsertPrefix
201201

202202
lazy val isInStringInterpolation =
203203
path match

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ object CompletionValue:
7979
)
8080
def importSymbol: Symbol = symbol
8181

82+
override def range: Option[Range] =
83+
snippetAffix.toInsertRange
84+
8285
def completionItemKind(using Context): CompletionItemKind =
8386
val symbol = this.symbol
8487
if symbol.is(Package) || symbol.is(Module) then

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

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import dotty.tools.pc.completions.OverrideCompletions.OverrideExtractor
3333
import dotty.tools.pc.buildinfo.BuildInfo
3434
import dotty.tools.pc.utils.MtagsEnrichments.*
3535
import dotty.tools.dotc.core.Denotations.SingleDenotation
36+
import org.eclipse.lsp4j.TextEdit
37+
import org.eclipse.lsp4j.Position
3638

3739
class Completions(
3840
text: String,
@@ -144,7 +146,7 @@ class Completions(
144146
denots: Seq[SingleDenotation]
145147
): List[CompletionValue] =
146148
denots.toList.flatMap: denot =>
147-
completionsWithSuffix(
149+
completionsWithAffix(
148150
denot,
149151
completion.show,
150152
(label, denot, suffix) => CompletionValue.Compiler(label, denot, suffix)
@@ -189,44 +191,43 @@ class Completions(
189191
CompletionAffix.empty
190192
.chain { suffix => // for [] suffix
191193
if shouldAddSnippet && symbol.info.typeParams.nonEmpty then
192-
suffix.withNewSuffixSnippet(SuffixKind.Bracket)
194+
suffix.withNewSuffixSnippet(Affix(SuffixKind.Bracket))
193195
else suffix
194196
}
195197
.chain { suffix => // for () suffix
196198
if shouldAddSnippet && symbol.is(Flags.Method) then
197199
val paramss = getParams(symbol)
198200
paramss match
199201
case Nil => suffix
200-
case List(Nil) => suffix.withNewSuffix(SuffixKind.Brace)
202+
case List(Nil) => suffix.withNewSuffix(Affix(SuffixKind.Brace))
201203
case _ if config.isCompletionSnippetsEnabled() =>
202204
val onlyParameterless = paramss.forall(_.isEmpty)
203205
lazy val onlyImplicitOrTypeParams = paramss.forall(
204206
_.exists { sym =>
205207
sym.isType || sym.is(Implicit) || sym.is(Given)
206208
}
207209
)
208-
if onlyParameterless then suffix.withNewSuffix(SuffixKind.Brace)
210+
if onlyParameterless then suffix.withNewSuffix(Affix(SuffixKind.Brace))
209211
else if onlyImplicitOrTypeParams then suffix
210-
else if suffix.hasSnippet then
211-
suffix.withNewSuffix(SuffixKind.Brace)
212-
else suffix.withNewSuffixSnippet(SuffixKind.Brace)
212+
else if suffix.hasSnippet then suffix.withNewSuffix(Affix(SuffixKind.Brace))
213+
else suffix.withNewSuffixSnippet(Affix(SuffixKind.Brace))
213214
case _ => suffix
214215
end match
215216
else suffix
216217
}
217218
.chain { suffix => // for {} suffix
218219
if shouldAddSnippet && isNew && isAbstractType(symbol) then
219-
if suffix.hasSnippet then suffix.withNewSuffix(SuffixKind.Template)
220-
else suffix.withNewSuffixSnippet(SuffixKind.Template)
220+
if suffix.hasSnippet then suffix.withNewSuffix(Affix(SuffixKind.Template))
221+
else suffix.withNewSuffixSnippet(Affix(SuffixKind.Template))
221222
else suffix
222223
}
223224

224225
end findSuffix
225226

226-
def completionsWithSuffix(
227+
def completionsWithAffix(
227228
denot: SingleDenotation,
228229
label: String,
229-
toCompletionValue: (String, SingleDenotation, CompletionAffix) => CompletionValue
230+
toCompletionValue: (String, SingleDenotation, CompletionAffix) => CompletionValue.Symbolic
230231
): List[CompletionValue] =
231232
val sym = denot.symbol
232233
val hasNonSyntheticConstructor = sym.name.isTypeName && sym.isClass
@@ -258,17 +259,30 @@ class Completions(
258259

259260
else denot :: Nil
260261

261-
val requiresInitDisambiguiation = methodDenots.exists(_.symbol.isConstructor) && methodDenots.exists(_.symbol.name == nme.apply)
262+
val existsApply = methodDenots.exists(_.symbol.name == nme.apply)
262263

263264
methodDenots.map { methodDenot =>
264265
val suffix = findSuffix(methodDenot.symbol)
265-
val affix = if methodDenot.symbol.isConstructor && requiresInitDisambiguiation then
266-
suffix.withNewPrefix(PrefixKind.New)
266+
val currentPrefix = adjustedPath match
267+
case Select(qual, _) :: _ => Some(qual.show + ".", qual.span.start)
268+
case _ => None
269+
270+
val affix = if methodDenot.symbol.isConstructor && existsApply then
271+
adjustedPath match
272+
case (select @ Select(qual, _)) :: _ =>
273+
val start = qual.span.start
274+
val insertRange = select.sourcePos.startPos.withEnd(completionPos.queryEnd).toLsp
275+
276+
suffix
277+
.withCurrentPrefix(qual.show + ".")
278+
.withNewPrefix(Affix(PrefixKind.New, insertRange = Some(insertRange)))
279+
case _ =>
280+
suffix.withNewPrefix(Affix(PrefixKind.New))
267281
else suffix
268282
val name = undoBacktick(label)
269283
toCompletionValue(name, methodDenot, affix)
270284
}
271-
end completionsWithSuffix
285+
end completionsWithAffix
272286

273287
/**
274288
* @return Tuple of completionValues and flag. If the latter boolean value is true
@@ -522,7 +536,7 @@ class Completions(
522536
)
523537
)
524538
case _ =>
525-
completionsWithSuffix(
539+
completionsWithAffix(
526540
sym,
527541
sym.decodedName,
528542
CompletionValue.Workspace(_, _, _, sym)
@@ -555,13 +569,13 @@ class Completions(
555569
&& !sym.isConstructor && !isDefaultVariableSetter
556570

557571
if isExtensionMethod then
558-
completionsWithSuffix(
572+
completionsWithAffix(
559573
sym,
560574
sym.decodedName,
561575
CompletionValue.Extension(_, _, _)
562576
).map(visit).forall(_ == true)
563577
else if isImplicitClassMember then
564-
completionsWithSuffix(
578+
completionsWithAffix(
565579
sym,
566580
sym.decodedName,
567581
CompletionValue.ImplicitClass(_, _, _, sym.maybeOwner),
@@ -602,15 +616,15 @@ class Completions(
602616
.groupBy(_.symbol.fullName) // we somehow have to ignore proxy type
603617

604618
symbolicCompletionsMap.foreach: (name, denots) =>
605-
lazy val existsTypeWithSuffix: Boolean = symbolicCompletionsMap
619+
lazy val existsTypeWithoutSuffix: Boolean = !symbolicCompletionsMap
606620
.get(name.toTypeName)
607621
.forall(_.forall(sym => sym.snippetAffix.suffixes.nonEmpty))
608622

609623
if completionMode.is(Mode.Term) && !completionMode.is(Mode.ImportOrExport) then
610624
typeResultMappings += name -> denots
611625
// show non synthetic symbols
612626
// companion test should not result TrieMap[K, V]
613-
else if name.isTermName && existsTypeWithSuffix then
627+
else if name.isTermName && !existsTypeWithoutSuffix then
614628
typeResultMappings += name -> denots
615629
else if name.isTypeName then
616630
typeResultMappings += name -> denots

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ object InterpolatorCompletions:
153153
sym.name.toString()
154154
) =>
155155
val label = sym.name.decoded
156-
completions.completionsWithSuffix(
156+
completions.completionsWithAffix(
157157
sym,
158158
label,
159159
(name, denot, suffix) =>
@@ -284,7 +284,7 @@ object InterpolatorCompletions:
284284
sym.name.decoded
285285
) && !sym.isType =>
286286
val label = sym.name.decoded
287-
completions.completionsWithSuffix(
287+
completions.completionsWithAffix(
288288
sym,
289289
label,
290290
(name, denot, suffix) =>

presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionExtraConstructorSuite.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,27 @@ class CompletionExtraConstructorSuite extends BaseCompletionSuite:
512512
includeCompletionKind = true
513513
)
514514

515+
@Test def `prepend-new` =
516+
checkSnippet(
517+
"""|object Wrapper:
518+
| Try@@
519+
|
520+
|""".stripMargin,
521+
"""|Try
522+
|Try($0)
523+
|new Try
524+
|""".stripMargin,
525+
)
526+
527+
@Test def `prepend-new-fully-qualified-path` =
528+
checkSnippet(
529+
"""|object Wrapper:
530+
| scala.util.Try@@
531+
|
532+
|""".stripMargin,
533+
"""|Try
534+
|Try($0)
535+
|new scala.util.Try
536+
|""".stripMargin,
537+
)
538+

presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionInterpolatorSuite.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,6 @@ class CompletionInterpolatorSuite extends BaseCompletionSuite:
626626
| s"this is an interesting ${java.nio.file.Paths}"
627627
|}
628628
|""".stripMargin,
629-
itemIndex = 0,
630629
assertSingleItem = false,
631630
)
632631

presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSnippetSuite.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,21 @@ class CompletionSnippetSuite extends BaseCompletionSuite:
310310
|""".stripMargin,
311311
"""|Try
312312
|Try($0)
313-
|new Try
313+
|new scala.util.Try
314314
|""".stripMargin
315315
)
316316

317+
@Test def `case-class2-edit` =
318+
checkEditLine(
319+
s"""|object Main {
320+
| ___
321+
|}
322+
|""".stripMargin,
323+
"scala.util.Tr@@",
324+
"new scala.util.Try",
325+
filter = _.contains("new Try")
326+
)
327+
317328
@Test def `case-class3` =
318329
checkSnippet(
319330
s"""|object Main {

0 commit comments

Comments
 (0)