Skip to content

Commit

Permalink
Remove artificial CURSOR added for the completions
Browse files Browse the repository at this point in the history
  • Loading branch information
rochala committed Jun 30, 2024
1 parent 8f73af2 commit 941bc76
Show file tree
Hide file tree
Showing 17 changed files with 160 additions and 99 deletions.
11 changes: 10 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/NavigateAST.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ast

import core.Contexts.*
import core.Decorators.*
import core.StdNames
import util.Spans.*
import Trees.{Closure, MemberDef, DefTree, WithLazyFields}
import dotty.tools.dotc.core.Types.AnnotatedType
Expand Down Expand Up @@ -76,6 +77,8 @@ object NavigateAST {
var bestFit: List[Positioned] = path
while (it.hasNext) {
val path1 = it.next() match {
// FIXME this has to be changed to deterministicaly find recoveed tree
case untpd.Select(qual, name) if name == StdNames.nme.??? => path
case p: Positioned if !p.isInstanceOf[Closure[?]] => singlePath(p, path)
case m: untpd.Modifiers => childPath(m.productIterator, path)
case xs: List[?] => childPath(xs.iterator, path)
Expand All @@ -84,11 +87,17 @@ object NavigateAST {
if ((path1 ne path) &&
((bestFit eq path) ||
bestFit.head.span != path1.head.span &&
bestFit.head.span.contains(path1.head.span)))
envelops(bestFit.head.span, path1.head.span)))
bestFit = path1
}
bestFit
}

def envelops(a: Span, b: Span): Boolean =
!b.exists || a.exists && (
(a.start < b.start && a.end >= b.end ) || (a.start <= b.start && a.end > b.end)
)

/*
* Annotations trees are located in the Type
*/
Expand Down
13 changes: 7 additions & 6 deletions compiler/src/dotty/tools/dotc/interactive/Completion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,17 @@ object Completion:
case _ =>
""

def naiveCompletionPrefix(text: String, offset: Int): String =
var i = offset - 1
while i >= 0 && text(i).isUnicodeIdentifierPart do i -= 1
i += 1 // move to first character
text.slice(i, offset)

/**
* Inspect `path` to determine the completion prefix. Only symbols whose name start with the
* returned prefix should be considered.
*/
def completionPrefix(path: List[untpd.Tree], pos: SourcePosition)(using Context): String =
def fallback: Int =
var i = pos.point - 1
while i >= 0 && Character.isUnicodeIdentifierPart(pos.source.content()(i)) do i -= 1
i + 1

path match
case GenericImportSelector(sel) =>
if sel.isGiven then completionPrefix(sel.bound :: Nil, pos)
Expand All @@ -146,7 +147,7 @@ object Completion:
case (tree: untpd.RefTree) :: _ if tree.name != nme.ERROR =>
tree.name.toString.take(pos.span.point - tree.span.point)

case _ => pos.source.content.slice(fallback, pos.point).mkString
case _ => naiveCompletionPrefix(pos.source.content().mkString, pos.point)


end completionPrefix
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1725,7 +1725,7 @@ object Parsers {
case arg =>
arg
val args1 = args.mapConserve(sanitize)

if in.isArrow || isPureArrow || erasedArgs.contains(true) then
functionRest(args)
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1705,6 +1705,15 @@ class CompletionTest {
("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
))

@Test def testtest: Unit =
code"""|object M {
| def sel$m1
|}
|"""
.completion(m1, Set(
("getOrElse", Method, "[V1 >: String](key: Int, default: => V1): V1"),
))

@Test def noEnumCompletionInNewContext: Unit =
code"""|enum TestEnum:
| case TestCase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ final class AutoImportsProvider(
val results = symbols.result.filter(isExactMatch(_, name))

if results.nonEmpty then
val correctedPos = CompletionPos.infer(pos, params, path).toSourcePosition
val correctedPos = CompletionPos.infer(pos, params, path, false).toSourcePosition
val mkEdit =
path match
// if we are in import section just specify full name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ case class CompletionPos(
identEnd: Int,
query: String,
originalCursorPosition: SourcePosition,
sourceUri: URI
sourceUri: URI,
withCURSOR: Boolean
):
def queryEnd: Int = originalCursorPosition.point
def stripSuffixEditRange: l.Range = new l.Range(originalCursorPosition.offsetToPos(queryStart), originalCursorPosition.offsetToPos(identEnd))
Expand All @@ -34,17 +35,19 @@ object CompletionPos:
def infer(
sourcePos: SourcePosition,
offsetParams: OffsetParams,
adjustedPath: List[Tree]
adjustedPath: List[Tree],
wasCursorApplied: Boolean
)(using Context): CompletionPos =
val identEnd = adjustedPath match
case (refTree: RefTree) :: _ if refTree.name.toString.contains(Cursor.value) =>
refTree.span.end - Cursor.value.length
case (refTree: RefTree) :: _ => refTree.span.end
case _ => sourcePos.end

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

CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn)
CompletionPos(start, identEnd, query.nn, sourcePos, offsetParams.uri.nn, wasCursorApplied)

/**
* Infer the indentation by counting the number of spaces in the given line.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import dotty.tools.dotc.ast.tpd.*
import dotty.tools.dotc.core.Constants.Constant
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Phases
import dotty.tools.dotc.core.StdNames
import dotty.tools.dotc.core.StdNames.nme
import dotty.tools.dotc.core.Flags
import dotty.tools.dotc.interactive.Interactive
import dotty.tools.dotc.interactive.Completion
import dotty.tools.dotc.interactive.InteractiveDriver
import dotty.tools.dotc.parsing.Tokens
import dotty.tools.dotc.util.SourceFile
import dotty.tools.pc.AutoImports.AutoImportEdits
import dotty.tools.pc.AutoImports.AutoImportsGenerator
Expand Down Expand Up @@ -45,23 +48,31 @@ class CompletionProvider(
val uri = params.uri().nn
val text = params.text().nn

val code = applyCompletionCursor(params)
val (wasCursorApplied, code) = applyCompletionCursor(params)
val sourceFile = SourceFile.virtual(uri, code)
driver.run(uri, sourceFile)

val ctx = driver.currentCtx
given ctx: Context = driver.currentCtx
val pos = driver.sourcePosition(params)
val (items, isIncomplete) = driver.compilationUnits.get(uri) match
case Some(unit) =>

val newctx = ctx.fresh.setCompilationUnit(unit).withPhase(Phases.typerPhase(using ctx))
val tpdPath = Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using newctx)
val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath, pos)(using newctx)
val tpdPath0 = Interactive.pathTo(unit.tpdTree, pos.span)(using newctx)
val adjustedPath = Interactive.resolveTypedOrUntypedPath(tpdPath0, pos)(using newctx)

val tpdPath = tpdPath0 match
// $1$ // FIXME add check for a $1$ name to make sure we only do the below in lifting case
case Select(qual, name) :: tail if qual.symbol.is(Flags.Synthetic) =>
qual.symbol.defTree match
case valdef: ValDef => Select(valdef.rhs, name) :: tail
case _ => tpdPath0
case _ => tpdPath0


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

val completionPos = CompletionPos.infer(pos, params, adjustedPath)(using locatedCtx)
val completionPos = CompletionPos.infer(pos, params, adjustedPath, wasCursorApplied)(using locatedCtx)

val autoImportsGen = AutoImports.generator(
completionPos.toSourcePosition,
Expand Down Expand Up @@ -111,6 +122,10 @@ class CompletionProvider(
)
end completions

val allKeywords =
val softKeywords = Tokens.softModifierNames + nme.as + nme.derives + nme.extension + nme.throws + nme.using
Tokens.keywords.toList.map(Tokens.tokenString) ++ softKeywords.map(_.toString)

/**
* In case if completion comes from empty line like:
* {{{
Expand All @@ -123,23 +138,30 @@ class CompletionProvider(
* Otherwise, completion poisition doesn't point at any tree
* because scala parser trim end position to the last statement pos.
*/
private def applyCompletionCursor(params: OffsetParams): String =
private def applyCompletionCursor(params: OffsetParams): (Boolean, String) =
val text = params.text().nn
val offset = params.offset().nn
val query = Completion.naiveCompletionPrefix(text, offset)

val isStartMultilineComment =
val i = params.offset()
i >= 3 && (text.charAt(i - 1) match
case '*' =>
text.charAt(i - 2) == '*' &&
text.charAt(i - 3) == '/'
case _ => false
)
if isStartMultilineComment then
// Insert potentially missing `*/` to avoid comment out all codes after the "/**".
text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset)
if offset > 0 && text.charAt(offset - 1).isUnicodeIdentifierPart && !allKeywords.contains(query) then
false -> text
else
text.substring(0, offset).nn + Cursor.value + text.substring(offset)
val isStartMultilineComment =

val i = params.offset()
i >= 3 && (text.charAt(i - 1) match
case '*' =>
text.charAt(i - 2) == '*' &&
text.charAt(i - 3) == '/'
case _ => false
)
true -> (
if isStartMultilineComment then
// Insert potentially missing `*/` to avoid comment out all codes after the "/**".
text.substring(0, offset).nn + Cursor.value + "*/" + text.substring(offset)
else
text.substring(0, offset).nn + Cursor.value + text.substring(offset)
)
end applyCompletionCursor

private def completionItems(
Expand Down Expand Up @@ -172,7 +194,7 @@ class CompletionProvider(
Select(Apply(Select(Select(_, name), _), _), _),
_
) :: _ =>
name == StdNames.nme.StringContext
name == nme.StringContext
// "My name is $name"
case Literal(Constant(_: String)) :: _ =>
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,9 @@ class Completions(
/* In case of `method@@()` we should not add snippets and the path
* will contain apply as the parent of the current tree.
*/
case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun =>
false
case _ :: (withcursor @ Select(fun, name)) :: (appl: GenericApply) :: _
if appl.fun == withcursor && name.decoded == Cursor.value =>
false
case (fun) :: (appl: GenericApply) :: _ if appl.fun == fun => false
case sel :: (funSel @ Select(fun, name)) :: (appl: GenericApply) :: _
if appl.fun == funSel && sel == fun => false
case (_: (Import | Export)) :: _ => false
case _ :: (_: (Import | Export)) :: _ => false
case _ => true
Expand Down Expand Up @@ -479,14 +477,8 @@ class Completions(
if tree.selectors.exists(_.renamed.sourcePos.contains(pos)) =>
(List.empty, true)

// From Scala 3.1.3-RC3 (as far as I know), path contains
// `Literal(Constant(null))` on head for an incomplete program, in this case, just ignore the head.
case Literal(Constant(null)) :: tl =>
advancedCompletions(tl, completionPos)

case _ =>
val args = NamedArgCompletions.contribute(
pos,
path,
adjustedPath,
indexedContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ object InterpolatorCompletions:
buildTargetIdentifier: String
)(using ctx: Context, reportsContext: ReportContext): List[CompletionValue] =
val litStartPos = lit.span.start
val litEndPos = lit.span.end - Cursor.value.length()
val litEndPos = lit.span.end - (if completionPos.withCURSOR then Cursor.value.length else 0)
val position = completionPos.originalCursorPosition
val span = position.span
val nameStart =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ import scala.annotation.tailrec
object NamedArgCompletions:

def contribute(
pos: SourcePosition,
path: List[Tree],
untypedPath: => List[untpd.Tree],
untypedPath: List[untpd.Tree],
indexedContext: IndexedContext,
clientSupportsSnippets: Boolean,
)(using ctx: Context): List[CompletionValue] =
Expand All @@ -64,12 +63,13 @@ object NamedArgCompletions:
for
app <- getApplyForContextFunctionParam(rest)
if !app.fun.isInfix
yield contribute(
Some(ident),
app,
indexedContext,
clientSupportsSnippets,
)
yield
contribute(
Some(ident),
app,
indexedContext,
clientSupportsSnippets,
)
contribution.getOrElse(Nil)
case (app: Apply) :: _ =>
/**
Expand Down Expand Up @@ -156,10 +156,11 @@ object NamedArgCompletions:
case _ => None
val matchingMethods =
for
(name, indxContext) <- maybeNameAndIndexedContext(method)
potentialMatches <- indxContext.findSymbol(name)
yield potentialMatches.collect {
case m
(name, indexedContext) <- maybeNameAndIndexedContext(method)
potentialMatches <- indexedContext.findSymbol(name)
yield
potentialMatches.collect {
case m
if m.is(Flags.Method) &&
m.vparamss.length >= argss.length &&
Try(m.isAccessibleFrom(apply.symbol.info)).toOption
Expand All @@ -179,8 +180,7 @@ object NamedArgCompletions:
end fallbackFindMatchingMethods

val matchingMethods: List[Symbols.Symbol] =
if method.symbol.paramSymss.nonEmpty
then
if method.symbol.paramSymss.nonEmpty then
val allArgsAreSupplied =
val vparamss = method.symbol.vparamss
vparamss.length == argss.length && vparamss
Expand Down Expand Up @@ -295,6 +295,7 @@ object NamedArgCompletions:
)
}

// FIXME pass query here
val prefix = ident
.map(_.name.toString)
.getOrElse("")
Expand Down Expand Up @@ -391,7 +392,7 @@ class FuzzyArgMatcher(tparams: List[Symbols.Symbol])(using Context):
(expectedArgs.length == actualArgs.length ||
(!allArgsProvided && expectedArgs.length >= actualArgs.length)) &&
actualArgs.zipWithIndex.forall {
case (Ident(name), _) if name.endsWith(Cursor.value) => true
case (Ident(name), _) => true
case (NamedArg(name, arg), _) =>
expectedArgs.exists { expected =>
expected.name == name && (!arg.hasType || arg.typeOpt.unfold
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ object OverrideCompletions:
)
)
// class Main extends Val:
// he@@
// he@@
case (id: Ident) :: (t: Template) :: (td: TypeDef) :: _
if t.parents.nonEmpty =>
Some(
Expand All @@ -586,6 +586,20 @@ object OverrideCompletions:
)
)

// class Main extends Val:
// hello@ // this transforms into this.hello, thus is a Select
case (sel @ Select(th: This, name)) :: (t: Template) :: (td: TypeDef) :: _
if t.parents.nonEmpty && th.qual.name == td.name =>
Some(
(
td,
None,
sel.sourcePos.start,
false,
Some(name.show),
)
)

case _ => None

end OverrideExtractor
Expand Down
Loading

0 comments on commit 941bc76

Please sign in to comment.