Format list pattern and rest pattern. (#1363)

* Format list pattern.

* Add documentation for PatternExtensions.canBlockSplit.

* Fix comment in if statement.

* Fix comment nits.
diff --git a/lib/src/ast_extensions.dart b/lib/src/ast_extensions.dart
index 566cf1a..92434e6 100644
--- a/lib/src/ast_extensions.dart
+++ b/lib/src/ast_extensions.dart
@@ -313,3 +313,15 @@
     return true;
   }
 }
+
+extension PatternExtensions on DartPattern {
+  /// Whether this expression is a non-empty delimited container for inner
+  /// expressions that allows "block-like" formatting in some contexts.
+  ///
+  /// See [ExpressionExtensions.canBlockSplit].
+  bool get canBlockSplit => switch (this) {
+        ListPattern(:var elements, :var rightBracket) =>
+          elements.canSplit(rightBracket),
+        _ => false,
+      };
+}
diff --git a/lib/src/front_end/ast_node_visitor.dart b/lib/src/front_end/ast_node_visitor.dart
index 070f0a7..00c863c 100644
--- a/lib/src/front_end/ast_node_visitor.dart
+++ b/lib/src/front_end/ast_node_visitor.dart
@@ -964,9 +964,22 @@
         var expressionPiece = nodePiece(ifStatement.expression);
         if (ifStatement.caseClause case var caseClause?) {
           var caseClausePiece = nodePiece(caseClause);
+          // If the case clause can have block formatting, then a newline in
+          // it doesn't force the if-case to split before the `case` keyword,
+          // like:
+          //
+          //     if (obj case [
+          //       first,
+          //       second,
+          //       third,
+          //     ]) {
+          //       ;
+          //     }
+          var allowInnerSplit = caseClause.guardedPattern.pattern.canBlockSplit;
           b.add(AssignPiece(
             expressionPiece,
             caseClausePiece,
+            allowInnerSplit: allowInnerSplit,
             indentInValue: true,
           ));
         } else {
@@ -1138,7 +1151,7 @@
   @override
   Piece visitListLiteral(ListLiteral node) {
     return createCollection(
-      node.constKeyword,
+      constKeyword: node.constKeyword,
       typeArguments: node.typeArguments,
       node.leftBracket,
       node.elements,
@@ -1148,7 +1161,12 @@
 
   @override
   Piece visitListPattern(ListPattern node) {
-    throw UnimplementedError();
+    return createCollection(
+      typeArguments: node.typeArguments,
+      node.leftBracket,
+      node.elements,
+      node.rightBracket,
+    );
   }
 
   @override
@@ -1405,7 +1423,7 @@
     }
 
     return createCollection(
-      node.constKeyword,
+      constKeyword: node.constKeyword,
       node.leftParenthesis,
       node.fields,
       node.rightParenthesis,
@@ -1487,7 +1505,10 @@
 
   @override
   Piece visitRestPatternElement(RestPatternElement node) {
-    throw UnimplementedError();
+    return buildPiece((b) {
+      b.token(node.operator);
+      b.visit(node.pattern);
+    });
   }
 
   @override
@@ -1509,7 +1530,7 @@
   @override
   Piece visitSetOrMapLiteral(SetOrMapLiteral node) {
     return createCollection(
-      node.constKeyword,
+      constKeyword: node.constKeyword,
       typeArguments: node.typeArguments,
       node.leftBracket,
       node.elements,
diff --git a/lib/src/front_end/delimited_list_builder.dart b/lib/src/front_end/delimited_list_builder.dart
index b3d63f1..8612c96 100644
--- a/lib/src/front_end/delimited_list_builder.dart
+++ b/lib/src/front_end/delimited_list_builder.dart
@@ -157,6 +157,7 @@
     var format = switch (element) {
       FunctionExpression() when element.canBlockSplit => BlockFormat.function,
       Expression() when element.canBlockSplit => BlockFormat.block,
+      DartPattern() when element.canBlockSplit => BlockFormat.block,
       _ => BlockFormat.none,
     };
 
diff --git a/lib/src/front_end/piece_factory.dart b/lib/src/front_end/piece_factory.dart
index f24f0e8..f8ab71f 100644
--- a/lib/src/front_end/piece_factory.dart
+++ b/lib/src/front_end/piece_factory.dart
@@ -118,10 +118,15 @@
     });
   }
 
-  /// Creates a [ListPiece] for a collection literal.
-  Piece createCollection(Token? constKeyword, Token leftBracket,
-      List<AstNode> elements, Token rightBracket,
-      {TypeArgumentList? typeArguments, ListStyle style = const ListStyle()}) {
+  /// Creates a [ListPiece] for a collection literal or pattern.
+  Piece createCollection(
+    Token leftBracket,
+    List<AstNode> elements,
+    Token rightBracket, {
+    Token? constKeyword,
+    TypeArgumentList? typeArguments,
+    ListStyle style = const ListStyle(),
+  }) {
     return buildPiece((b) {
       b.modifier(constKeyword);
       b.visit(typeArguments);
diff --git a/lib/src/piece/assign.dart b/lib/src/piece/assign.dart
index 2169eba..358a581 100644
--- a/lib/src/piece/assign.dart
+++ b/lib/src/piece/assign.dart
@@ -114,7 +114,9 @@
 
     writer.format(target);
     writer.splitIf(state == _atOperator);
-    if (_indentInValue) {
+
+    // We need extra indentation when there's no inner splitting of the value.
+    if (!_allowInnerSplit && _indentInValue) {
       writer.setIndent(Indent.expression * 2);
     }
     writer.format(value);
diff --git a/test/pattern/list.stmt b/test/pattern/list.stmt
new file mode 100644
index 0000000..354eb0a
--- /dev/null
+++ b/test/pattern/list.stmt
@@ -0,0 +1,91 @@
+40 columns                              |
+>>> Basic list patterns. 
+switch (obj) {
+case  [  ]  :
+case  <  int  >  [  ]  :
+case  [  2  ]  :
+case  [  2  ,  ]  :
+case  [  2  ,  3  ]  :
+  ok;
+}
+<<<
+switch (obj) {
+  case []:
+  case <int>[]:
+  case [2]:
+  case [2]:
+  case [2, 3]:
+    ok;
+}
+>>> Unsplit list.
+if (obj case [1, ...var x, 3]) {;}
+<<<
+if (obj case [1, ...var x, 3]) {
+  ;
+}
+>>> If it splits anywhere in the list, it splits at every element.
+if (obj case [first,second,third,fourth]) {;}
+<<<
+if (obj case [
+  first,
+  second,
+  third,
+  fourth,
+]) {
+  ;
+}
+>>> Unsplit short list even with a comma.
+if (obj case [1,]) {;}
+<<<
+if (obj case [1]) {
+  ;
+}
+>>> Nested list patterns don't force outer to split
+if (obj case [[1, 2], [[3]]]) {;}
+<<<
+if (obj case [[1, 2], [[3]]]) {
+  ;
+}
+>>> Split all elements and keep line comment on newline.
+if (obj case [
+  // yeah
+  a,b,c,
+  d,e,f,
+]) {;}
+<<<
+if (obj case [
+  // yeah
+  a,
+  b,
+  c,
+  d,
+  e,
+  f,
+]) {
+  ;
+}
+>>> Split in type argument, but not in the body.
+if (obj case <Map<VeryLongTypeArgument, VeryLongTypeArgument>>[e]) {;}
+<<<
+if (obj case <
+  Map<
+    VeryLongTypeArgument,
+    VeryLongTypeArgument
+  >
+>[e]) {
+  ;
+}
+>>> Split in type argument and body.
+if (obj case <Map<VeryLongTypeArgument, VeryLongTypeArgument>>[element,VeryLongElementElementElement]) {;}
+<<<
+if (obj case <
+  Map<
+    VeryLongTypeArgument,
+    VeryLongTypeArgument
+  >
+>[
+  element,
+  VeryLongElementElementElement,
+]) {
+  ;
+}