Skip to content

Commit

Permalink
Make complications work on overr<COMPLETE> and def<COMPLETE>.
Browse files Browse the repository at this point in the history
Fixes #631. Previously, we needed to write def <COMPLETE> to autocomplete an override, now we can write a start of the 'override' word or a combination of letters in the def/val name and it will trigger the proper completion. When starting with override word it will add it to method even if overriding abstract methods.

Also fixes a bug with not autocompleting when overriding vals form traits.
  • Loading branch information
tgodzik committed Apr 9, 2019
1 parent 12e345b commit 5236e71
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,12 @@ class CompletionProvider(
if (head.sym.isClass || head.sym.isModule) {
head.sym.fullName
} else {
semanticdbSymbol(head.sym)
head match {
case o: OverrideDefMember =>
o.label
case _ =>
semanticdbSymbol(head.sym)
}
}
def isIgnoredWorkspace: Boolean =
head.isInstanceOf[WorkspaceMember] &&
Expand Down
126 changes: 72 additions & 54 deletions mtags/src/main/scala/scala/meta/internal/pc/Completions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -479,10 +479,24 @@ trait Completions { this: MetalsGlobal =>
Select(Ident(TermName("scala")), TypeName("Unit")) ::
(defdef: DefDef) ::
(t: Template) :: _ if defdef.name.endsWith(CURSOR) =>
CompletionPosition.Override(defdef.name, t, pos, text, defdef)
CompletionPosition.Override(
defdef.name,
t,
pos,
text,
defdef.pos.start,
!_.isGetter
)
case (valdef @ ValDef(_, name, _, Literal(Constant(null)))) ::
(t: Template) :: _ if name.endsWith(CURSOR) =>
CompletionPosition.Override(name, t, pos, text, valdef)
CompletionPosition.Override(
name,
t,
pos,
text,
valdef.pos.start,
_.isStable
)
case (m @ Match(_, Nil)) :: parent :: _ =>
CompletionPosition.CaseKeyword(m.selector, editRange, pos, text, parent)
case Ident(name) :: (_: CaseDef) :: (m: Match) :: parent :: _
Expand All @@ -493,6 +507,15 @@ trait Completions { this: MetalsGlobal =>
CompletionPosition.CaseKeyword(m.selector, editRange, pos, text, parent)
case (c: DefTree) :: (p: PackageDef) :: _ if c.namePos.includes(pos) =>
CompletionPosition.Filename(c, p, pos, editRange)
case (ident: Ident) :: (t: Template) :: _ =>
CompletionPosition.Override(
ident.name,
t,
pos,
text,
ident.pos.start,
_ => true
)
case _ =>
inferCompletionPosition(pos, lastEnclosing)
}
Expand Down Expand Up @@ -662,6 +685,7 @@ trait Completions { this: MetalsGlobal =>
* // after
* s"Hello ${name.length()$0}"
* }}}
*
* @param query the member query, "len" in the example above.
* @param ident the identifier from where we select a member from, "name" above.
* @param literalPart the string literal part of the interpolator trailing
Expand Down Expand Up @@ -922,29 +946,13 @@ trait Completions { this: MetalsGlobal =>
t: Template,
pos: Position,
text: String,
defn: ValOrDefDef
start: Int,
isCandidate: Symbol => Boolean
) extends CompletionPosition {
val prefix = name.toString.stripSuffix(CURSOR)
val typed = typedTreeAt(t.pos)
val isDecl = typed.tpe.decls.toSet
val keyword = defn match {
case _: DefDef => "def"
case _ => "val"
}
val OVERRIDE = " override"
val start: Int = {
val fromDef = text.lastIndexOf(s" $keyword ", pos.point)
if (fromDef > 0 && text.endsWithAt(OVERRIDE, fromDef)) {
fromDef - OVERRIDE.length()
} else {
fromDef
}
}

def isExplicitOverride = text.startsWith(OVERRIDE, start)

val editStart = start + 1
val range = pos.withStart(editStart).withEnd(pos.point).toLSP
val range = pos.withStart(start).withEnd(pos.point).toLSP
val lineStart = pos.source.lineToOffset(pos.line - 1)

// Returns all the symbols of all transitive supertypes in the enclosing scope.
Expand Down Expand Up @@ -979,32 +987,22 @@ trait Completions { this: MetalsGlobal =>
sym.isMethod &&
!isDecl(sym) &&
!isNotOverridableName(sym.name) &&
sym.name.startsWith(prefix) &&
!sym.isPrivate &&
!sym.isSynthetic &&
!sym.isArtifact &&
!sym.isEffectivelyFinal &&
!sym.isVal &&
!sym.name.endsWith(CURSOR) &&
!sym.isConstructor &&
!sym.isMutable &&
!sym.isSetter && {
defn match {
case _: ValDef =>
// Is this a `override val`?
sym.isGetter && sym.isStable
case _ =>
// It's an `override def`.
!sym.isGetter
}
}
!sym.isSetter &&
isCandidate(sym)
}

val context = doLocateContext(pos)
val re = renamedSymbols(context)
val owners = this.parentSymbols(context)
val filter = text.substring(editStart, pos.point - prefix.length)

def toOverrideMember(sym: Symbol): OverrideDefMember = {
private case class OverrideCandidate(sym: Symbol) {
val memberType = typed.tpe.memberType(sym)
val info =
if (memberType.isErroneous) sym.info
Expand All @@ -1016,6 +1014,7 @@ trait Completions { this: MetalsGlobal =>
case _ => sym.info
}
}

val history = new ShortenedNames(
lookupSymbol = { name =>
context.lookupSymbol(name, _ => true) :: Nil
Expand All @@ -1024,6 +1023,7 @@ trait Completions { this: MetalsGlobal =>
renames = re,
owners = owners
)

val printer = new SignaturePrinter(
sym,
history,
Expand All @@ -1032,32 +1032,37 @@ trait Completions { this: MetalsGlobal =>
includeDefaultParam = false,
printLongType = false
)
val label = printer.defaultMethodSignature(Identifier(sym.name))
val prefix = metalsConfig.overrideDefFormat() match {
case OverrideDefFormat.Ascii =>
if (sym.isAbstract) s"${keyword} "
else s"override ${keyword} "
case OverrideDefFormat.Unicode =>
if (sym.isAbstract) ""
else "🔼 "
}

val overrideKeyword =
if (!sym.isAbstract || isExplicitOverride) "override "
if (!sym.isAbstract || text.startsWith("o", start)) "override "
// Don't insert `override` keyword if the supermethod is abstract and the
// user did not explicitly type "override". See:
// user did not explicitly type starting with o . See:
// https://github.com/scalameta/metals/issues/565#issuecomment-472761240
else ""

val lzy =
if (sym.isLazy) "lazy "
else ""
val edit = new l.TextEdit(
range,
s"${overrideKeyword}${lzy}${keyword} $label = $${0:???}"
)
new OverrideDefMember(
prefix + label,

val keyword = if (sym.isStable) "val " else "def "

val overrideDef = metalsConfig.overrideDefFormat() match {
case OverrideDefFormat.Unicode =>
if (sym.isAbstract) s"🔼 "
else ""
case _ =>
s"${overrideKeyword}${keyword}"
}

val name = Identifier(sym.name)

val filterText = s"${overrideKeyword}${lzy}${keyword}${name}"

// if we had no val or def then filter will be empty
def toMember = new OverrideDefMember(
label,
edit,
filter + sym.name.decoded,
filterText,
sym,
history.autoImports(
pos,
Expand All @@ -1066,14 +1071,27 @@ trait Completions { this: MetalsGlobal =>
inferIndent(lineStart, text)
)
)

private def label = overrideDef + name + signature

private def signature = printer.defaultMethodSignature()

private def edit = new l.TextEdit(
range,
s"$filterText$signature = $${0:???}"
)
}

override def contribute: List[Member] = {
if (start < 0) Nil
else {
typed.tpe.members.iterator
.filter(isOverridableMethod)
.map(toOverrideMember)
.map(OverrideCandidate.apply)
.filter { canditate =>
CompletionFuzzy.matchesSubCharacters(prefix, canditate.filterText)
}
.map(_.toMember)
.toList
}
}
Expand Down
121 changes: 118 additions & 3 deletions tests/cross/src/test/scala/tests/pc/CompletionOverrideSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ object CompletionOverrideSuite extends BaseCompletionSuite {
| def iterator: Iterator[Int] = ${0:???}
| }
|}
|""".stripMargin
|""".stripMargin,
filter = _.contains("iter")
)

checkEdit(
Expand Down Expand Up @@ -90,7 +91,7 @@ object CompletionOverrideSuite extends BaseCompletionSuite {
|}
|object Main {
| new Context {
| override def ad@@
| override def add@@
| }
|}
""".stripMargin,
Expand Down Expand Up @@ -197,7 +198,8 @@ object CompletionOverrideSuite extends BaseCompletionSuite {
| def iterator: Iterator[A] = ${0:???}
| }
|}
""".stripMargin
""".stripMargin,
filter = _.contains("iter")
)

check(
Expand Down Expand Up @@ -692,4 +694,117 @@ object CompletionOverrideSuite extends BaseCompletionSuite {
"override lazy val analyzer: Object{val global: r.Main} = ${0:???}"
)

checkEditLine(
"val-trait",
"""|package s
|trait Val {
| val hello1: Int = 42
|}
|class Main extends Val {
| ___
|}
|""".stripMargin,
"val hello@@",
"override val hello1: Int = ${0:???}"
)

check(
"ident",
"""|package t
|abstract class Val {
| def hello: Int = 2
|}
|class Main extends Val {
| hello@@
|}
|""".stripMargin,
"""|hello: Int
|override def hello: Int
|""".stripMargin,
includeDetail = false,
)

check(
"override-concrete",
"""|package u
|abstract class Val {
| def overTop: Int
|}
|class Main extends Val {
| over@@
|}
|""".stripMargin,
"""|override def overTop: Int
|overTop: Int
|""".stripMargin,
includeDetail = false,
topLines = Some(2)
)

check(
"override-abstract",
"""|package w
|abstract class Val {
| def overTop: Int = 5
|}
|class Main extends Val {
| over@@
|}
|""".stripMargin,
"""|overTop: Int
|override def overTop: Int
|""".stripMargin,
includeDetail = false,
topLines = Some(2)
)

checkEdit(
"fuzzy-abstract",
"""|package v
|abstract class Val {
| def hello: Int
|}
|class Main extends Val {
| ovhello@@
|}
|""".stripMargin,
"""|package i.b
|abstract class Val {
| def hello: Int
|}
|class Main extends Val {
| override def hello: Int = ${0:???}
|}
|""".stripMargin
)

check(
"override-word",
"""|package y
|abstract class Val {
| def hello: Int = 2
|}
|class Main extends Val {
| overr@@
|}
|""".stripMargin,
"override def hello: Int",
includeDetail = false,
topLines = Some(1)
)

check(
"def-word",
"""|package z
|abstract class Val {
| def hello: Int = 2
|}
|class Main extends Val {
| def@@
|}
|""".stripMargin,
"override def hello: Int",
includeDetail = false,
topLines = Some(1)
)
}

0 comments on commit 5236e71

Please sign in to comment.