Repository: intellij-rust/intellij-rust
Issue: Move Statement Up/Down actions move entire match arm when caret is inside

<!--
Hello and thank you for the issue!
If you would like to report a bug, we have added some points below that you can fill out.
Feel free to remove all the irrelevant text to request a new feature.
-->

## Environment

* **IntelliJ Rust plugin version:** 0.3.140.3613-203-nightly
* **Rust toolchain version:** 1.48.0 (7eac88abb 2020-11-16) x86_64-apple-darwin
* **IDE name and version:** CLion 2020.3.1 (CL-203.6682.181)
* **Operating system:** macOS 10.16
* **Macro expansion engine:** new
* **Name resolution engine:** new

## Problem description
`Move Statement Up/Down` actions (`Cmd + Shift + Up/Down Arrow`) move the entire match arm when the caret is inside. Note that `Move Line Up/Down` actions (`Alt + Shift + Up/Down Arrow`) work as expected.

## Steps to reproduce
```Rust
fn main() {
     match x {
         foo => {
             1;
         }
         bar => {
             2;
             3; // <caret> Move Statement Up 
             4;
         }
         baz => {
             5;
         }
     };
}
```

Expected:
```Rust
fn main() {
     match x {
         foo => {
             1;
         }
         bar => {
             3; // <caret>
             2;
             4;
         }
         baz => {
             5;
         }
     };
}
```

Actual:
```Rust
fn main() {
     match x {
         bar => {
             2;
             3; // <caret>
             4;
         }
         foo => {
             1;
         }
         baz => {
             5;
         }
     };
}
```

Changed files:
src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt, src/main/kotlin/org/rust/ide/actions/mover/RsMatchArmUpDownMover.kt, src/test/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMoverTest.kt, src/main/kotlin/org/rust/ide/actions/mover/RsLineMover.kt

diff --git a/src/main/kotlin/org/rust/ide/actions/mover/RsLineMover.kt b/src/main/kotlin/org/rust/ide/actions/mover/RsLineMover.kt
index 789809a8c..a74efce32 100644
--- a/src/main/kotlin/org/rust/ide/actions/mover/RsLineMover.kt
+++ b/src/main/kotlin/org/rust/ide/actions/mover/RsLineMover.kt
@@ -16,6 +16,7 @@ import com.intellij.psi.PsiWhiteSpace
 import org.rust.lang.core.psi.RsElementTypes
 import org.rust.lang.core.psi.RsFile
 import org.rust.lang.core.psi.RsFunction
+import org.rust.lang.core.psi.RsMatchArm
 import org.rust.lang.core.psi.ext.elementType
 
 abstract class RsLineMover : LineMover() {
@@ -89,6 +90,9 @@ abstract class RsLineMover : LineMover() {
 
         fun isMovingOutOfFunctionBody(sibling: PsiElement, down: Boolean): Boolean =
             isMovingOutOfBraceBlock(sibling, down) && sibling.parent?.parent is RsFunction
+
+        fun isMovingOutOfMatchArmBlock(sibling: PsiElement, down: Boolean): Boolean =
+            isMovingOutOfBraceBlock(sibling, down) && sibling.parent?.parent?.parent is RsMatchArm
     }
 }
 
diff --git a/src/main/kotlin/org/rust/ide/actions/mover/RsMatchArmUpDownMover.kt b/src/main/kotlin/org/rust/ide/actions/mover/RsMatchArmUpDownMover.kt
index 639d1698c..d78e794bf 100644
--- a/src/main/kotlin/org/rust/ide/actions/mover/RsMatchArmUpDownMover.kt
+++ b/src/main/kotlin/org/rust/ide/actions/mover/RsMatchArmUpDownMover.kt
@@ -17,8 +17,10 @@ import org.rust.lang.core.psi.ext.elementType
 import org.rust.lang.core.psi.ext.getPrevNonCommentSibling
 
 class RsMatchArmUpDownMover : RsLineMover() {
-    override fun findMovableAncestor(psi: PsiElement, endpoint: RangeEndpoint): PsiElement? =
-        psi.ancestorOrSelf<RsMatchArm>()
+    override fun findMovableAncestor(psi: PsiElement, endpoint: RangeEndpoint): PsiElement? {
+        if (RsStatementUpDownMover.isMovableElement(psi)) return null
+        return psi.ancestorOrSelf<RsMatchArm>()
+    }
 
     override fun canApply(firstMovableElement: PsiElement, secondMovableElement: PsiElement): Boolean {
         val firstMatchBody = firstMovableElement.ancestorStrict<RsMatchBody>() ?: return false
diff --git a/src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt b/src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt
index 47dde8629..a1710dda2 100644
--- a/src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt
+++ b/src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt
@@ -14,24 +14,29 @@ import org.rust.lang.core.psi.ext.elementType
 import org.rust.lang.core.psi.ext.isMultiLine
 
 class RsStatementUpDownMover : RsLineMover() {
-    private val movableItems = tokenSetOf(
-        RsElementTypes.STMT,
-        RsElementTypes.EXPR_STMT,
-        RsElementTypes.EMPTY_STMT,
-        RsElementTypes.LET_DECL
-    )
+    companion object {
+        private val movableItems = tokenSetOf(
+            RsElementTypes.STMT,
+            RsElementTypes.EXPR_STMT,
+            RsElementTypes.EMPTY_STMT,
+            RsElementTypes.LET_DECL
+        )
 
-    private val PsiElement.isBlockExpr: Boolean
-        get() = this is RsExpr && parent is RsBlock
+        private val PsiElement.isBlockExpr: Boolean
+            get() = this is RsExpr && parent is RsBlock
 
-    private val PsiElement.isComment: Boolean
-        get() = elementType in RS_COMMENTS
+        private val PsiElement.isComment: Boolean
+            get() = elementType in RS_COMMENTS
+
+        fun isMovableElement(element: PsiElement): Boolean =
+            element.elementType in movableItems || element.isBlockExpr || element.isComment
+    }
 
     override fun findMovableAncestor(psi: PsiElement, endpoint: RangeEndpoint): PsiElement? =
-        psi.ancestors.find { it.elementType in movableItems || it.isBlockExpr || it.isComment }
+        psi.ancestors.find(::isMovableElement)
 
     override fun findTargetElement(sibling: PsiElement, down: Boolean): PsiElement? {
-        if (isMovingOutOfFunctionBody(sibling, down)) {
+        if (isMovingOutOfFunctionBody(sibling, down) || isMovingOutOfMatchArmBlock(sibling, down)) {
             UpDownMoverTestMarks.moveOutOfBody.hit()
             return null
         }
diff --git a/src/test/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMoverTest.kt b/src/test/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMoverTest.kt
index b446edb2d..b07e6691c 100644
--- a/src/test/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMoverTest.kt
+++ b/src/test/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMoverTest.kt
@@ -516,4 +516,108 @@ class RsStatementUpDownMoverTest : RsStatementUpDownMoverTestBase() {
             5;
         }
     """)
+
+    fun `test move up statement in match arm body`() = moveUp("""
+        fn main() {
+             match x {
+                 foo => {
+                     1;
+                 }
+                 bar => {
+                     2;
+                     /*caret*/3;
+                     4;
+                 }
+                 baz => {
+                     5;
+                 }
+             };
+        }
+    """, """
+        fn main() {
+             match x {
+                 foo => {
+                     1;
+                 }
+                 bar => {
+                     /*caret*/3;
+                     2;
+                     4;
+                 }
+                 baz => {
+                     5;
+                 }
+             };
+        }
+    """)
+
+    fun `test move down statement in match arm body`() = moveDown("""
+        fn main() {
+             match x {
+                 foo => {
+                     1;
+                 }
+                 bar => {
+                     2;
+                     3;/*caret*/
+                     4;
+                 }
+                 baz => {
+                     5;
+                 }
+             };
+        }
+    """, """
+        fn main() {
+             match x {
+                 foo => {
+                     1;
+                 }
+                 bar => {
+                     2;
+                     4;
+                     3;/*caret*/
+                 }
+                 baz => {
+                     5;
+                 }
+             };
+        }
+    """)
+
+    fun `test move up first statement in match arm body`() = moveUp("""
+        fn main() {
+             match x {
+                 foo => {
+                     1;
+                 }
+                 bar => {
+                     2;/*caret*/
+                     3;
+                     4;
+                 }
+                 baz => {
+                     5;
+                 }
+             };
+        }
+    """, testmark = UpDownMoverTestMarks.moveOutOfBody)
+
+    fun `test move down last statement in match arm body`() = moveDown("""
+        fn main() {
+             match x {
+                 foo => {
+                     1;
+                 }
+                 bar => {
+                     2;
+                     3;
+                     4;/*caret*/
+                 }
+                 baz => {
+                     5;
+                 }
+             };
+        }
+    """, testmark = UpDownMoverTestMarks.moveOutOfBody)
 }