Skip to content

Commit 1cd249e

Browse files
timaliberdovbuilduser
authored and
builduser
committed
[backspace handler] delete matching braces for empty template bodies
(cherry picked from commit 754fb40)
1 parent d68a2ef commit 1cd249e

File tree

7 files changed

+168
-29
lines changed

7 files changed

+168
-29
lines changed

scala/scala-impl/src/org/jetbrains/plugins/scala/editor/backspaceHandler/ScalaBackspaceHandler.scala

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.jetbrains.plugins.scala.extensions._
2020
import org.jetbrains.plugins.scala.lang.lexer.{ScalaTokenTypes, ScalaXmlTokenTypes}
2121
import org.jetbrains.plugins.scala.lang.psi.api.expr._
2222
import org.jetbrains.plugins.scala.lang.psi.api.expr.xml.ScXmlStartTag
23+
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.templates.ScTemplateBody
2324
import org.jetbrains.plugins.scala.lang.psi.api.{ScFile, ScalaFile}
2425
import org.jetbrains.plugins.scala.lang.scaladoc.lexer.ScalaDocTokenType
2526
import org.jetbrains.plugins.scala.lang.scaladoc.lexer.docsyntax.ScalaDocSyntaxElementType
@@ -101,7 +102,7 @@ class ScalaBackspaceHandler extends BackspaceHandlerDelegate {
101102
highlighterIterator.tokenLength == QuotesLength && offset == highlighterIterator.getStart && {
102103
highlighterIterator.retreat()
103104
try highlighterIterator.getTokenType == tINTERPOLATED_MULTILINE_STRING && highlighterIterator.tokenLength == QuotesLength
104-
finally highlighterIterator.advance() // pretending we are side-affect-free =/
105+
finally highlighterIterator.advance() // pretending we are side-effect-free =/
105106
}
106107
case _ =>
107108
false
@@ -134,19 +135,26 @@ class ScalaBackspaceHandler extends BackspaceHandlerDelegate {
134135
}
135136

136137
private def handleLeftBrace(element: PsiElement, file: PsiFile, editor: Editor): Unit = {
137-
for {
138-
BraceWrapInfo(element, _, parent, _) <- ScalaTypedHandler.findElementToWrap(element)
139-
if element.is[ScBlockExpr]
140-
block = element.asInstanceOf[ScBlockExpr]
141-
lBrace <- block.getLBrace
142-
rBrace <- block.getRBrace
143-
if canDeleteClosingBrace(block, parent, lBrace, rBrace, file)
144-
} {
138+
def deleteRBrace(rBrace: PsiElement): Unit = {
145139
val document = editor.getDocument
146140
deleteBrace(rBrace, document)
147141
val project = file.getProject
148142
document.commit(project)
149143
}
144+
145+
element match {
146+
case ElementType(ScalaTokenTypes.tLBRACE) & Parent(tb: ScTemplateBody) if tb.isEmpty =>
147+
tb.getRBrace.foreach(deleteRBrace)
148+
case _ =>
149+
for {
150+
BraceWrapInfo(element, _, parent, _) <- ScalaTypedHandler.findElementToWrap(element)
151+
if element.is[ScBlockExpr]
152+
block = element.asInstanceOf[ScBlockExpr]
153+
lBrace <- block.getLBrace
154+
rBrace <- block.getRBrace
155+
if canDeleteClosingBrace(block, parent, lBrace, rBrace, file)
156+
} deleteRBrace(rBrace)
157+
}
150158
}
151159

152160
private def canDeleteClosingBrace(block: ScBlockExpr, parent: PsiElement, lBrace: PsiElement, rBrace: PsiElement, file: PsiFile): Boolean = {
@@ -173,9 +181,9 @@ class ScalaBackspaceHandler extends BackspaceHandlerDelegate {
173181
if (correctClosingBraceSelected(statements, parent, rBrace, tabSize))
174182
statements.isEmpty ||
175183
hasCorrectIndentationInside(statements, parent, tabSize) &&
176-
hasCorrectIndentationOutside(statements, parent, rBrace, tabSize) &&
177-
firstStatementDoesNotBreakIndentation(statements, lBrace, rBrace) &&
178-
lastElementDoesNotBreakIndentation(rBrace)
184+
hasCorrectIndentationOutside(statements, parent, rBrace, tabSize) &&
185+
firstStatementDoesNotBreakIndentation(statements, lBrace, rBrace) &&
186+
lastElementDoesNotBreakIndentation(rBrace)
179187
else
180188
false
181189

@@ -326,10 +334,10 @@ class ScalaBackspaceHandler extends BackspaceHandlerDelegate {
326334
}
327335

328336
/*
329-
In some cases with nested braces (like '{()}' ) IDEA can't properly handle backspace action due to
330-
bag in BraceMatchingUtil (it looks for every lbrace/rbrace token regardless of they are a pair or not)
331-
So we have to fix it in our handler
332-
*/
337+
* In some cases with nested braces (like '{()}') IDEA can't properly handle backspace action due to
338+
* bug in BraceMatchingUtil (it looks for every lbrace/rbrace token regardless of they are a pair or not).
339+
* So we have to fix it in our handler
340+
*/
333341
override def charDeleted(deletedChar: Char, file: PsiFile, editor: Editor): Boolean = {
334342
val scalaFile = file match {
335343
case f: ScFile => f
@@ -365,8 +373,6 @@ class ScalaBackspaceHandler extends BackspaceHandlerDelegate {
365373

366374
val fileType = file.getFileType
367375
val matcher = BraceMatchingUtil.getBraceMatcher(fileType, tpe)
368-
if (matcher == null)
369-
return None
370376

371377
val stack = scala.collection.mutable.Stack[IElementType]()
372378

@@ -410,6 +416,7 @@ class ScalaBackspaceHandler extends BackspaceHandlerDelegate {
410416
document.commit(file.getProject)
411417

412418
val element = file.findElementAt(offset)
419+
if (element == null) return
413420

414421
element.parents.find(_.is[ScBlockExpr]) match {
415422
case Some(block: ScBlockExpr) if canAutoDeleteBraces(block) && AutoBraceUtils.isIndentationContext(block) =>

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/api/base/ScOptionalBracesOwner.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package org.jetbrains.plugins.scala.lang.psi.api.base
22

33
import com.intellij.psi.PsiElement
4+
import com.intellij.psi.tree.TokenSet
45
import org.jetbrains.plugins.scala.extensions.PsiElementExt
5-
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.{LBRACE_OR_COLON_TOKEN_SET, tCOLON, tLBRACE}
6+
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.{LBRACE_OR_COLON_TOKEN_SET, tCOLON, tLBRACE, tRBRACE}
67
import org.jetbrains.plugins.scala.lang.psi.api.ScalaPsiElement
8+
import org.jetbrains.plugins.scala.lang.psi.api.base.ScOptionalBracesOwner.rBraceTokenSet
79

810
/** Scala 3 introduced optional braces, now template bodies, argument blocks, etc.
911
* may be written as
@@ -30,6 +32,12 @@ trait ScOptionalBracesOwner extends ScalaPsiElement {
3032
@inline def getLBrace: Option[PsiElement] =
3133
getEnclosingStartElement.filter(_.elementType == tLBRACE)
3234

35+
def getRBrace: Option[PsiElement] =
36+
getNode.getChildren(rBraceTokenSet) match {
37+
case Array(node) => Option(node.getPsi)
38+
case _ => None
39+
}
40+
3341
@inline def getColon: Option[PsiElement] =
3442
getEnclosingStartElement.filter(_.elementType == tCOLON)
3543

@@ -42,4 +50,6 @@ object ScOptionalBracesOwner {
4250
object withColon {
4351
def unapply(elem: ScOptionalBracesOwner): Option[PsiElement] = elem.getColon
4452
}
53+
54+
private val rBraceTokenSet = TokenSet.create(tRBRACE)
4555
}

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/api/expr/ScBlock.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package org.jetbrains.plugins.scala.lang.psi.api.expr
22

33
import com.intellij.psi.scope.PsiScopeProcessor
44
import com.intellij.psi.search.GlobalSearchScope
5-
import com.intellij.psi.tree.TokenSet
65
import com.intellij.psi.{PsiElement, ResolveState}
76
import org.jetbrains.plugins.scala.ScalaBundle
87
import org.jetbrains.plugins.scala.extensions.{ObjectExt, PsiElementExt}
9-
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes
108
import org.jetbrains.plugins.scala.lang.psi.api.ScalaFile
119
import org.jetbrains.plugins.scala.lang.psi.api.base.ScOptionalBracesOwner
1210
import org.jetbrains.plugins.scala.lang.psi.api.base.patterns.{ScCaseClause, ScCaseClauses}
@@ -89,12 +87,6 @@ trait ScBlock extends ScExpression
8987
def hasRBrace: Boolean = getRBrace.isDefined
9088
def hasLBrace: Boolean = getLBrace.isDefined
9189

92-
def getRBrace: Option[PsiElement] =
93-
getNode.getChildren(TokenSet.create(ScalaTokenTypes.tRBRACE)) match {
94-
case Array(node) => Option(node.getPsi)
95-
case _ => None
96-
}
97-
9890
def resultExpression: Option[ScExpression] = lastStatement.flatMap(_.asOptionOf[ScExpression])
9991
def lastStatement: Option[ScBlockStatement] = findLastChild[ScBlockStatement]
10092

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/api/toplevel/templates/ScTemplateBody.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,6 @@ trait ScTemplateBody extends ScalaPsiElement
3434
def selfTypeElement: Option[ScSelfTypeElement]
3535

3636
def extensions: Seq[ScExtension]
37+
38+
def isEmpty: Boolean
3739
}

scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/impl/toplevel/templates/ScTemplateBodyImpl.scala

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package templates
33

44
import com.intellij.lang.ASTNode
55
import com.intellij.psi.scope.PsiScopeProcessor
6+
import com.intellij.psi.tree.TokenSet
67
import com.intellij.psi.util.PsiTreeUtil
78
import com.intellij.psi.{PsiElement, ResolveState}
89
import org.jetbrains.plugins.scala.JavaArrayFactoryUtil._
910
import org.jetbrains.plugins.scala.caches.{ModTracker, cached}
1011
import org.jetbrains.plugins.scala.lang.TokenSets._
12+
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes
1113
import org.jetbrains.plugins.scala.lang.parser.ScalaElementType.{EXTENSION, EnumCases, SELF_TYPE, TEMPLATE_BODY}
1214
import org.jetbrains.plugins.scala.lang.psi.api.ScalaPsiElement
1315
import org.jetbrains.plugins.scala.lang.psi.api.base.types.ScSelfTypeElement
@@ -19,7 +21,7 @@ import org.jetbrains.plugins.scala.lang.psi.impl.ScalaStubBasedElementImpl
1921
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.typedef.ScTemplateDefinitionImpl
2022
import org.jetbrains.plugins.scala.lang.psi.stubs.ScTemplateBodyStub
2123

22-
class ScTemplateBodyImpl private (stub: ScTemplateBodyStub, node: ASTNode)
24+
class ScTemplateBodyImpl private(stub: ScTemplateBodyStub, node: ASTNode)
2325
extends ScalaStubBasedElementImpl(stub, TEMPLATE_BODY, node)
2426
with ScTemplateBody {
2527

@@ -86,4 +88,18 @@ class ScTemplateBodyImpl private (stub: ScTemplateBodyStub, node: ASTNode)
8688
override protected def childBeforeFirstImport: Option[PsiElement] = {
8789
selfTypeElement.orElse(super.childBeforeFirstImport)
8890
}
91+
92+
override def isEmpty: Boolean = _isEmpty()
93+
94+
private val _isEmpty = cached("isEmpty", ModTracker.anyScalaPsiChange, () => {
95+
getStubOrPsiChildren(TokenSet.ANY, PsiElementFactory)
96+
.forall { child =>
97+
val node = child.getNode
98+
node != null && ScTemplateBodyImpl.WHITESPACE_OR_BRACE.contains(node.getElementType)
99+
}
100+
})
101+
}
102+
103+
object ScTemplateBodyImpl {
104+
private val WHITESPACE_OR_BRACE = TokenSet.orSet(TokenSet.WHITE_SPACE, ScalaTokenTypes.BRACES_TOKEN_SET)
89105
}

scala/scala-impl/test/org/jetbrains/plugins/scala/lang/actions/editor/backspace/ClosingBraceRemoveTest.scala

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.jetbrains.plugins.scala.lang.actions.editor.backspace
22

33
import org.jetbrains.plugins.scala.settings.{ScalaApplicationSettings, ScalaCompileServerSettings}
44

5-
/** @see [[org.jetbrains.plugins.scala.lang.actions.editor.ClosingBraceInsertTest]]*/
5+
/** @see [[org.jetbrains.plugins.scala.lang.actions.editor.ClosingBraceInsertTest]] */
66
class ClosingBraceRemoveTest extends ScalaBackspaceHandlerBaseTest {
77

88
// copied from Scala3BracelessSyntaxEnterExhaustiveTest
@@ -14,6 +14,95 @@ class ClosingBraceRemoveTest extends ScalaBackspaceHandlerBaseTest {
1414

1515
private def empty = ""
1616

17+
def testRemove_TemplateBody_EmptyTrait(): Unit = doTest(
18+
before =
19+
s"""trait Foo {${|}}
20+
|""".stripMargin,
21+
after =
22+
s"""trait Foo ${|}
23+
|""".stripMargin
24+
)
25+
26+
def testRemove_TemplateBody_EmptyTrait2(): Unit = doTest(
27+
before =
28+
s"""trait Foo {${|}
29+
|}
30+
|""".stripMargin,
31+
after =
32+
s"""trait Foo ${|}
33+
|""".stripMargin
34+
)
35+
36+
def testRemove_TemplateBody_EmptyTrait3(): Unit = doTest(
37+
before =
38+
s"""trait Foo {${|}
39+
| $empty
40+
|}
41+
|""".stripMargin,
42+
after =
43+
s"""trait Foo ${|}
44+
|
45+
|""".stripMargin
46+
)
47+
48+
def testRemove_TemplateBody_EmptyClass(): Unit = doTest(
49+
s"""class Foo {${|}
50+
|}
51+
|""".stripMargin,
52+
after =
53+
s"""class Foo ${|}
54+
|""".stripMargin
55+
)
56+
57+
def testRemove_TemplateBody_EmptyObject(): Unit = doTest(
58+
before =
59+
s"""object Foo {${|}
60+
|}
61+
|""".stripMargin,
62+
after =
63+
s"""object Foo ${|}
64+
|""".stripMargin
65+
)
66+
67+
def testNotRemove_TemplateBody_Expression(): Unit = doTest(
68+
before =
69+
s"""trait Foo {${|}
70+
| 2
71+
|}
72+
|""".stripMargin,
73+
after =
74+
s"""trait Foo ${|}
75+
| 2
76+
|}
77+
|""".stripMargin
78+
)
79+
80+
def testNotRemove_TemplateBody_Block(): Unit = doTest(
81+
before =
82+
s"""trait Foo {${|}
83+
| {}
84+
|}
85+
|""".stripMargin,
86+
after =
87+
s"""trait Foo ${|}
88+
| {}
89+
|}
90+
|""".stripMargin
91+
)
92+
93+
def testNotRemove_TemplateBody_Method(): Unit = doTest(
94+
before =
95+
s"""trait Foo {${|}
96+
| def bar: Int
97+
|}
98+
|""".stripMargin,
99+
after =
100+
s"""trait Foo ${|}
101+
| def bar: Int
102+
|}
103+
|""".stripMargin
104+
)
105+
17106
def testRemove_FunctionBody_SingleExpression(): Unit = {
18107
val before =
19108
s"""def foo() = {${|}

scala/scala-impl/test/org/jetbrains/plugins/scala/lang/actions/editor/backspace/Scala3IndentationBasedSyntaxClosingBraceRemoveTest.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ class Scala3IndentationBasedSyntaxClosingBraceRemoveTest extends ScalaBackspaceH
4040
}
4141
}
4242

43+
def testRemove_TemplateBody_EmptyEnum(): Unit = doTest(
44+
before =
45+
s"""enum Foo {${|}
46+
|}
47+
|""".stripMargin,
48+
after =
49+
s"""enum Foo ${|}
50+
|""".stripMargin
51+
)
52+
53+
def testNotRemove_TemplateBody_EnumWithCases(): Unit = doTest(
54+
before =
55+
s"""enum Foo {${|}
56+
| case Bar
57+
|}
58+
|""".stripMargin,
59+
after =
60+
s"""enum Foo ${|}
61+
| case Bar
62+
|}
63+
|""".stripMargin
64+
)
65+
4366
def testRemove_FunctionBody_SingleExpression(): Unit = {
4467
val before =
4568
s"""def foo() = {${|}

0 commit comments

Comments
 (0)