Allow disabling formatting for a region of code. (#1522)

Allow disabling formatting for a region of code.

There are two special line comments:

```
// dart format off
// dart format on
```

Any code between those keeps its original formatting completely,
including indentation. It just splats the original unformatted code into
the output.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12998dc..211bba4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,10 @@
 * Allow passing a language version to `DartFomatter()`. Formatted code will be
   parsed at that version. If omitted, defaults to the latest version. In a
   future release, this parameter will become required.
+* Allow opting out of formatting for a region of code using `// dart format off`
+  and `// dart format on` comments. Note: This only works using the new tall
+  style and requires passing the `--enable-experiment=tall-style` experiment
+  flag (#361).
 * Preserve type parameters on old-style function-typed formals that also use
   `this.` or `super.` (#1321).
 * Remove temporary work around for analyzer 6.2.0 from dart_style 2.3.6.
diff --git a/benchmark/run.dart b/benchmark/run.dart
index 8d8f646..bf8da68 100644
--- a/benchmark/run.dart
+++ b/benchmark/run.dart
@@ -160,7 +160,7 @@
       result = visitor.run(parseResult.unit).text;
     } else {
       var visitor = AstNodeVisitor(formatter, parseResult.lineInfo, source);
-      result = visitor.run(parseResult.unit).text;
+      result = visitor.run(source, parseResult.unit).text;
     }
   }
 
diff --git a/example/format.dart b/example/format.dart
index ba66c08..d6d0603 100644
--- a/example/format.dart
+++ b/example/format.dart
@@ -25,7 +25,7 @@
   class C {}
   ''');
 
-  _runTest('selection/selection.stmt', 2);
+  _runTest('other/selection.stmt', 2);
 }
 
 void _formatStmt(String source, {bool tall = true, int pageWidth = 40}) {
@@ -68,7 +68,7 @@
 /// directory.
 Future<void> _runTest(String path, int line,
     {int pageWidth = 40, bool tall = true}) async {
-  var testFile = await TestFile.read(path);
+  var testFile = await TestFile.read('${tall ? 'tall' : 'short'}/$path');
   var formatTest = testFile.tests.firstWhere((test) => test.line == line);
 
   var formatter = DartFormatter(
diff --git a/lib/src/back_end/code.dart b/lib/src/back_end/code.dart
index 47c06eb..52416a1 100644
--- a/lib/src/back_end/code.dart
+++ b/lib/src/back_end/code.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import '../source_code.dart';
+
 /// Base class for an object that represents fully formatted code.
 ///
 /// We use this instead of immediately generating a string for the resulting
@@ -13,14 +15,59 @@
 /// to be fast. Appending strings to a [StringBuffer] is fairly fast, but not
 /// as fast simply appending a single [GroupCode] to the parent solution's
 /// [GroupCode].
-sealed class Code {}
+sealed class Code {
+  /// Traverse the [Code] tree and generate a string for debugging purposes.
+  String toDebugString() {
+    var buffer = StringBuffer();
+    var prefix = '';
+
+    void write(String text) {
+      if (buffer.isNotEmpty) buffer.write(prefix);
+      buffer.writeln(text);
+    }
+
+    void trace(Code code) {
+      switch (code) {
+        case _NewlineCode():
+          write('Newline(blank: ${code._blank}, indent: ${code._indent})');
+
+        case _TextCode():
+          write('`${code._text}`');
+
+        case GroupCode():
+          write('Group(indent: ${code._indent}):');
+          prefix += '| ';
+          for (var child in code._children) {
+            trace(child);
+          }
+          prefix = prefix.substring(2);
+
+        case _MarkerCode():
+          write('Marker(${code._marker}, offset: ${code._offset})');
+
+        case _EnableFormattingCode():
+          write('EnableFormattingCode(enabled: ${code._enabled}, '
+              'offset: ${code._sourceOffset})');
+      }
+    }
+
+    trace(this);
+
+    return buffer.toString();
+  }
+}
 
 /// A [Code] object which can be written to and contain other child [Code]
 /// objects.
 final class GroupCode extends Code {
+  /// How many spaces the first text inside this group should be indented.
+  final int _indent;
+
   /// The child [Code] objects contained in this group.
   final List<Code> _children = [];
 
+  GroupCode(this._indent);
+
   /// Appends [text] to this code.
   void write(String text) {
     _children.add(_TextCode(text));
@@ -32,7 +79,10 @@
   /// single newline is written. The [indent] parameter is the number of spaces
   /// of leading indentation on the next line after the newline.
   void newline({required bool blank, required int indent}) {
-    _children.add(_NewlineCode(blank: blank, indent: indent));
+    // Don't insert a redundant newline at the top of a group.
+    if (_children.isNotEmpty) {
+      _children.add(_NewlineCode(blank: blank, indent: indent));
+    }
   }
 
   /// Adds an entire existing code [group] as a child of this one.
@@ -52,65 +102,32 @@
     _children.add(_MarkerCode(_Marker.end, offset));
   }
 
+  /// Disables or re-enables formatting in a region of code.
+  void setFormattingEnabled(bool enabled, int sourceOffset) {
+    _children.add(_EnableFormattingCode(enabled, sourceOffset));
+  }
+
   /// Traverse the [Code] tree and build the final formatted string.
   ///
   /// Whenever a newline is written, writes [lineEnding]. If omitted, defaults
   /// to '\n'.
   ///
   /// Returns the formatted string and the selection markers if there are any.
-  ({String code, int? selectionStart, int? selectionEnd}) build(
-      [String? lineEnding]) {
+  SourceCode build(SourceCode source, [String? lineEnding]) {
     lineEnding ??= '\n';
 
-    var buffer = StringBuffer();
-    int? selectionStart;
-    int? selectionEnd;
-
-    _build(buffer, lineEnding, (marker, offset) {
-      if (marker == _Marker.start) {
-        selectionStart = offset;
-      } else {
-        selectionEnd = offset;
-      }
-    });
-
-    return (
-      code: buffer.toString(),
-      selectionStart: selectionStart,
-      selectionEnd: selectionEnd
-    );
-  }
-
-  void _build(StringBuffer buffer, String lineEnding,
-      void Function(_Marker marker, int offset) markSelection) {
-    for (var i = 0; i < _children.length; i++) {
-      var child = _children[i];
-      switch (child) {
-        case _NewlineCode():
-          // Don't write any leading newlines at the top of the buffer.
-          if (i > 0) {
-            buffer.write(lineEnding);
-            if (child._blank) buffer.write(lineEnding);
-          }
-
-          buffer.write(_indents[child._indent] ?? (' ' * child._indent));
-
-        case _TextCode():
-          buffer.write(child._text);
-
-        case GroupCode():
-          child._build(buffer, lineEnding, markSelection);
-
-        case _MarkerCode():
-          markSelection(child._marker, buffer.length + child._offset);
-      }
-    }
+    var builder = _StringBuilder(source, lineEnding);
+    builder.traverse(this);
+    return builder.finish();
   }
 }
 
 /// A [Code] object for a newline followed by any leading indentation.
 final class _NewlineCode extends Code {
+  /// True if a blank line (two newlines) should be written.
   final bool _blank;
+
+  /// The number of spaces of indentation after this newline.
   final int _indent;
 
   _NewlineCode({required bool blank, required int indent})
@@ -132,49 +149,230 @@
   /// What kind of selection endpoint is being marked.
   final _Marker _marker;
 
-  /// The number of characters past this object where the marker should appear
-  /// in the resulting code.
+  /// The number of characters into the next [Code] object where the marker
+  /// should appear in the resulting output.
   final int _offset;
 
   _MarkerCode(this._marker, this._offset);
 }
 
+final class _EnableFormattingCode extends Code {
+  /// Whether this comment disables formatting (`format off`) or re-enables it
+  /// (`format on`).
+  final bool _enabled;
+
+  /// The number of code points from the beginning of the unformatted source
+  /// where the unformatted code should begin or end.
+  ///
+  /// If this piece is for `// dart format off`, then the offset is just past
+  /// the `off`. If this piece is for `// dart format on`, it points to just
+  /// before `//`.
+  final int _sourceOffset;
+
+  _EnableFormattingCode(this._enabled, this._sourceOffset);
+}
+
 /// Which selection marker is pointed to by a [_MarkerCode].
 enum _Marker { start, end }
 
-/// Pre-calculated whitespace strings for various common levels of indentation.
-///
-/// Generating these ahead of time is faster than concatenating multiple spaces
-/// at runtime.
-const _indents = {
-  2: '  ',
-  4: '    ',
-  6: '      ',
-  8: '        ',
-  10: '          ',
-  12: '            ',
-  14: '              ',
-  16: '                ',
-  18: '                  ',
-  20: '                    ',
-  22: '                      ',
-  24: '                        ',
-  26: '                          ',
-  28: '                            ',
-  30: '                              ',
-  32: '                                ',
-  34: '                                  ',
-  36: '                                    ',
-  38: '                                      ',
-  40: '                                        ',
-  42: '                                          ',
-  44: '                                            ',
-  46: '                                              ',
-  48: '                                                ',
-  50: '                                                  ',
-  52: '                                                    ',
-  54: '                                                      ',
-  56: '                                                        ',
-  58: '                                                          ',
-  60: '                                                            ',
-};
+/// Traverses a [Code] tree and produces the final string of output code and
+/// the selection markers, if any.
+class _StringBuilder {
+  /// Pre-calculated whitespace strings for various common levels of
+  /// indentation.
+  ///
+  /// Generating these ahead of time is faster than concatenating multiple
+  /// spaces at runtime.
+  static const _indents = {
+    2: '  ',
+    4: '    ',
+    6: '      ',
+    8: '        ',
+    10: '          ',
+    12: '            ',
+    14: '              ',
+    16: '                ',
+    18: '                  ',
+    20: '                    ',
+    22: '                      ',
+    24: '                        ',
+    26: '                          ',
+    28: '                            ',
+    30: '                              ',
+    32: '                                ',
+    34: '                                  ',
+    36: '                                    ',
+    38: '                                      ',
+    40: '                                        ',
+    42: '                                          ',
+    44: '                                            ',
+    46: '                                              ',
+    48: '                                                ',
+    50: '                                                  ',
+    52: '                                                    ',
+    54: '                                                      ',
+    56: '                                                        ',
+    58: '                                                          ',
+    60: '                                                            ',
+  };
+
+  final SourceCode _source;
+  final String _lineEnding;
+  final StringBuffer _buffer = StringBuffer();
+
+  /// The offset from the beginning of the source to where the selection start
+  /// marker is, if there is one.
+  int? _selectionStart;
+
+  /// The offset from the beginning of the source to where the selection end
+  /// marker is, if there is one.
+  int? _selectionEnd;
+
+  /// How many spaces of indentation should be written before the next text.
+  int _indent = 0;
+
+  /// If formatting has been disabled, then this is the offset from the
+  /// beginning of the source, to where the disabled formatting begins.
+  ///
+  /// Otherwise, -1 to indicate that formatting is enabled.
+  int _disableFormattingStart = -1;
+
+  _StringBuilder(this._source, this._lineEnding);
+
+  void traverse(Code code) {
+    switch (code) {
+      case _NewlineCode():
+        // If formatting has been disabled, then don't write the formatted
+        // output. The unformatted output will be written when formatting is
+        // re-enabled.
+        if (_disableFormattingStart == -1) {
+          _buffer.write(_lineEnding);
+          if (code._blank) _buffer.write(_lineEnding);
+          _indent = code._indent;
+        }
+
+      case _TextCode():
+        // If formatting has been disabled, then don't write the formatted
+        // output. The unformatted output will be written when formatting is
+        // re-enabled.
+        if (_disableFormattingStart == -1) {
+          // Write any pending indentation.
+          _buffer.write(_indents[_indent] ?? (' ' * _indent));
+          _indent = 0;
+
+          _buffer.write(code._text);
+        }
+
+      case GroupCode():
+        _indent = code._indent;
+        for (var i = 0; i < code._children.length; i++) {
+          var child = code._children[i];
+          traverse(child);
+        }
+
+      case _MarkerCode():
+        if (_disableFormattingStart == -1) {
+          // Calculate the absolute offset from the beginning of the formatted
+          // output where the selection marker will appear based on how much
+          // formatted output we've written, pending indentation, and then the
+          // relative offset of the marker into the subsequent [Code] we will
+          // write.
+          var absolutePosition = _buffer.length + _indent + code._offset;
+          switch (code._marker) {
+            case _Marker.start:
+              _selectionStart = absolutePosition;
+            case _Marker.end:
+              _selectionEnd = absolutePosition;
+          }
+        } else {
+          // The marker appears inside a region where formatting is disabled.
+          // In that case, calculating where the marker will end up in the
+          // final formatted output is more complicated because we haven't
+          // actually written any of the code between the `// dart format off`
+          // comment and this marker to [_buffer] yet. However, we do know the
+          // *absolute* position of the selection markers in the original
+          // source.
+          //
+          // Let's say the source file looks like:
+          //
+          //               1         2         3
+          //     0123456789012345678901234567890123456789
+          //     bef  +  ore off code | inside on more
+          //
+          // Here, `bef  +  ore` is some amount of code appearing before
+          // formatting is disabled, `off` is the `// dart format off` comment,
+          // `code` is some code inside the unformatted region, `|` is the
+          // selection marker, `inside` is more code in the unformatted region,
+          // `on` turns formatting back on, and `more` is formatted code at the
+          // end.
+          //
+          // We know the beginning of the unformatted region is at offset 15
+          // (just after the comment) in the original source. We know the
+          // selection marker is at offset 21 in the original source. From that,
+          // we know the selection marker should end up 6 code points after the
+          // beginning of the unformatted region in the resulting output.
+          switch (code._marker) {
+            case _Marker.start:
+              // Calculate how far into the unformatted code where the marker
+              // should appear.
+              var markerOffsetInUnformatted =
+                  _source.selectionStart! - _disableFormattingStart;
+              _selectionStart = _buffer.length + markerOffsetInUnformatted;
+
+            case _Marker.end:
+              var end = _source.selectionStart! + _source.selectionLength!;
+
+              // Calculate how far into the unformatted code where the marker
+              // should appear.
+              var markerOffsetInUnformatted = end - _disableFormattingStart;
+              _selectionEnd = _buffer.length + markerOffsetInUnformatted;
+          }
+        }
+
+      case _EnableFormattingCode(_enabled: false):
+        // Region markers don't nest. If we've already turned off formatting,
+        // then ignore any subsequent `// dart format off` comments until it's
+        // been turned back on.
+        if (_disableFormattingStart == -1) {
+          _disableFormattingStart = code._sourceOffset;
+        }
+
+      case _EnableFormattingCode(_enabled: true):
+        // If we didn't disable formatting, then enabling it does nothing.
+        if (_disableFormattingStart != -1) {
+          // Write all of the unformatted text from the `// dart format off`
+          // comment to the end of the `// dart format on` comment.
+          _buffer.write(_source.text
+              .substring(_disableFormattingStart, code._sourceOffset));
+          _disableFormattingStart = -1;
+        }
+    }
+  }
+
+  SourceCode finish() {
+    if (_disableFormattingStart != -1) {
+      // Formatting was disabled and never re-enabled, so write the rest of the
+      // source file as unformatted text.
+      _buffer.write(_source.text.substring(_disableFormattingStart));
+    } else if (_source.isCompilationUnit) {
+      // Be a good citizen, end with a newline.
+      _buffer.write(_lineEnding);
+    }
+
+    var selectionStart = _selectionStart;
+    int? selectionLength;
+    if (_source.selectionStart != null) {
+      // If we haven't hit the beginning and/or end of the selection yet, they
+      // must be at the very end of the code.
+      selectionStart ??= _buffer.length;
+      var selectionEnd = _selectionEnd ?? _buffer.length;
+      selectionLength = selectionEnd - selectionStart;
+    }
+
+    return SourceCode(_buffer.toString(),
+        uri: _source.uri,
+        isCompilationUnit: _source.isCompilationUnit,
+        selectionStart: selectionStart,
+        selectionLength: selectionLength);
+  }
+}
diff --git a/lib/src/back_end/code_writer.dart b/lib/src/back_end/code_writer.dart
index e60f593..c266dc1 100644
--- a/lib/src/back_end/code_writer.dart
+++ b/lib/src/back_end/code_writer.dart
@@ -30,7 +30,7 @@
   final Solution _solution;
 
   /// The code being written.
-  final GroupCode _code = GroupCode();
+  final GroupCode _code;
 
   /// What whitespace should be written before the next non-whitespace text.
   ///
@@ -41,7 +41,7 @@
   ///
   /// Initially [Whitespace.newline] so that we write the leading indentation
   /// before the first token.
-  Whitespace _pendingWhitespace = Whitespace.newline;
+  Whitespace _pendingWhitespace = Whitespace.none;
 
   /// The number of spaces of indentation that should be begin the next line
   /// when [_pendingWhitespace] is [Whitespace.newline] or
@@ -100,11 +100,13 @@
   /// [leadingIndent] is the number of spaces of leading indentation at the
   /// beginning of each line independent of indentation created by pieces being
   /// written.
-  CodeWriter(this._pageWidth, int leadingIndent, this._cache, this._solution) {
+  CodeWriter(this._pageWidth, int leadingIndent, this._cache, this._solution)
+      : _code = GroupCode(leadingIndent) {
     _indentStack.add(_Indent(leadingIndent, 0));
 
-    // Write the leading indent before the first line.
+    // Track the leading indent before the first line.
     _pendingIndent = leadingIndent;
+    _column = _pendingIndent;
   }
 
   /// Returns the final formatted code and the next pieces that can be expanded
@@ -331,6 +333,11 @@
     _code.endSelection(end);
   }
 
+  /// Disables or re-enables formatting in a region of code.
+  void setFormattingEnabled(bool enabled, int sourceOffset) {
+    _code.setFormattingEnabled(enabled, sourceOffset);
+  }
+
   /// Write any pending whitespace.
   ///
   /// This is called before non-whitespace text is about to be written, or
diff --git a/lib/src/back_end/solver.dart b/lib/src/back_end/solver.dart
index 948ec6b..df087f8 100644
--- a/lib/src/back_end/solver.dart
+++ b/lib/src/back_end/solver.dart
@@ -103,7 +103,7 @@
 
       if (debug.traceSolver) {
         debug.log(debug.bold('Try #$attempts $solution'));
-        debug.log(solution.code.build().code);
+        debug.log(solution.code.toDebugString());
         debug.log('');
       }
 
@@ -133,8 +133,8 @@
     // If we didn't find a solution without overflow, pick the least bad one.
     if (debug.traceSolver) {
       debug.unindent();
-      debug.log(debug.bold('Solved $root to $best:'));
-      debug.log(solution.code.build().code);
+      debug.log(debug.bold('Solved $root to $best'));
+      debug.log(solution.code.toDebugString());
       debug.log('');
     }
 
diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart
index d814dde..67a97b1 100644
--- a/lib/src/dart_formatter.dart
+++ b/lib/src/dart_formatter.dart
@@ -219,7 +219,7 @@
     SourceCode output;
     if (experimentFlags.contains(tallStyleExperimentFlag)) {
       var visitor = AstNodeVisitor(this, lineInfo, unitSourceCode);
-      output = visitor.run(node);
+      output = visitor.run(unitSourceCode, node);
     } else {
       var visitor = SourceVisitor(this, lineInfo, unitSourceCode);
       output = visitor.run(node);
diff --git a/lib/src/front_end/ast_node_visitor.dart b/lib/src/front_end/ast_node_visitor.dart
index 36d5f4e..b3cdf9a 100644
--- a/lib/src/front_end/ast_node_visitor.dart
+++ b/lib/src/front_end/ast_node_visitor.dart
@@ -66,7 +66,7 @@
   ///
   /// This is the only method that should be called externally. Everything else
   /// is effectively private.
-  SourceCode run(AstNode node) {
+  SourceCode run(SourceCode source, AstNode node) {
     Profile.begin('AstNodeVisitor.run()');
 
     Profile.begin('AstNodeVisitor build Piece tree');
@@ -123,7 +123,7 @@
     Profile.end('AstNodeVisitor build Piece tree');
 
     // Finish writing and return the complete result.
-    var result = pieces.finish(unitPiece);
+    var result = pieces.finish(source, unitPiece);
 
     Profile.end('AstNodeVisitor.run()');
 
diff --git a/lib/src/front_end/piece_writer.dart b/lib/src/front_end/piece_writer.dart
index 90e7595..e2e1f0b 100644
--- a/lib/src/front_end/piece_writer.dart
+++ b/lib/src/front_end/piece_writer.dart
@@ -277,7 +277,18 @@
   /// Creates a new [Piece] for [comment] and returns it.
   Piece commentPiece(SourceComment comment,
       [Whitespace trailingWhitespace = Whitespace.none]) {
-    var piece = CommentPiece(trailingWhitespace);
+    var piece = switch (comment.text) {
+      '// dart format off' => EnableFormattingCommentPiece(
+          enable: false,
+          comment.offset + comment.text.length,
+          trailingWhitespace),
+      '// dart format on' => EnableFormattingCommentPiece(
+          enable: true,
+          comment.offset + comment.text.length,
+          trailingWhitespace),
+      _ => CommentPiece(trailingWhitespace),
+    };
+
     _write(piece, comment.text, comment.offset,
         multiline: comment.type.mayBeMultiline);
     return piece;
@@ -379,7 +390,7 @@
 
   /// Finishes writing and returns a [SourceCode] containing the final output
   /// and updated selection, if any.
-  SourceCode finish(Piece rootPiece) {
+  SourceCode finish(SourceCode source, Piece rootPiece) {
     if (debug.tracePieceBuilder) {
       debug.log(debug.pieceTree(rootPiece));
     }
@@ -390,28 +401,11 @@
     var solver = Solver(cache,
         pageWidth: _formatter.pageWidth, leadingIndent: _formatter.indent);
     var solution = solver.format(rootPiece);
-    var (:code, :selectionStart, :selectionEnd) =
-        solution.code.build(_formatter.lineEnding);
+    var output = solution.code.build(source, _formatter.lineEnding);
 
     Profile.end('PieceWriter.finish() format piece tree');
 
-    // Be a good citizen, end with a newline.
-    if (_source.isCompilationUnit) code += _formatter.lineEnding!;
-
-    int? selectionLength;
-    if (_source.selectionStart != null) {
-      // If we haven't hit the beginning and/or end of the selection yet, they
-      // must be at the very end of the code.
-      selectionStart ??= code.length;
-      selectionEnd ??= code.length;
-      selectionLength = selectionEnd - selectionStart;
-    }
-
-    return SourceCode(code,
-        uri: _source.uri,
-        isCompilationUnit: _source.isCompilationUnit,
-        selectionStart: selectionStart,
-        selectionLength: selectionLength);
+    return output;
   }
 
   /// Returns the number of characters past [position] in the source where the
diff --git a/lib/src/piece/text.dart b/lib/src/piece/text.dart
index 274516c..b5c159f 100644
--- a/lib/src/piece/text.dart
+++ b/lib/src/piece/text.dart
@@ -59,18 +59,6 @@
     }
   }
 
-  /// Sets [selectionStart] to be [start] code units after the end of the
-  /// current text in this piece.
-  void startSelection(int start) {
-    _selectionStart = _adjustSelection(start);
-  }
-
-  /// Sets [selectionEnd] to be [end] code units after the end of the
-  /// current text in this piece.
-  void endSelection(int end) {
-    _selectionEnd = _adjustSelection(end);
-  }
-
   /// Adjust [offset] by the current length of this [TextPiece].
   int _adjustSelection(int offset) {
     for (var line in _lines) {
@@ -163,7 +151,7 @@
   /// Whitespace at the end of the comment.
   final Whitespace _trailingWhitespace;
 
-  CommentPiece([this._trailingWhitespace = Whitespace.none]);
+  CommentPiece(this._trailingWhitespace);
 
   @override
   void format(CodeWriter writer, State state) {
@@ -180,6 +168,32 @@
   void forEachChild(void Function(Piece piece) callback) {}
 }
 
+/// A piece for the special `// dart format off` and `// dart format on`
+/// comments that are used to opt a region of code out of being formatted.
+class EnableFormattingCommentPiece extends CommentPiece {
+  /// Whether this comment disables formatting (`format off`) or re-enables it
+  /// (`format on`).
+  final bool _enabled;
+
+  /// The number of code points from the beginning of the unformatted source
+  /// where the unformatted code should begin or end.
+  ///
+  /// If this piece is for `// dart format off`, then the offset is just past
+  /// the `off`. If this piece is for `// dart format on`, it points to just
+  /// before `//`.
+  final int _sourceOffset;
+
+  EnableFormattingCommentPiece(this._sourceOffset, super._trailingWhitespace,
+      {required bool enable})
+      : _enabled = enable;
+
+  @override
+  void format(CodeWriter writer, State state) {
+    super.format(writer, state);
+    writer.setFormattingEnabled(_enabled, _sourceOffset);
+  }
+}
+
 /// A piece that writes a single space.
 class SpacePiece extends Piece {
   @override
diff --git a/test/tall/other/format_off.unit b/test/tall/other/format_off.unit
new file mode 100644
index 0000000..fc96f28
--- /dev/null
+++ b/test/tall/other/format_off.unit
@@ -0,0 +1,196 @@
+40 columns                              |
+### Tests for disabling formatting on regions of code.
+>>> Simple unformatted region.
+main() {
+  here   +   gets   +   formatted    ;
+  // dart format off
+  here   +   does    +    not   ;
+  // dart format on
+  but   +   here   +    does   ;
+}
+<<<
+main() {
+  here + gets + formatted;
+  // dart format off
+  here   +   does    +    not   ;
+  // dart format on
+  but + here + does;
+}
+>>> Multiple unformatted regions.
+main() {
+  here   +   gets   +   formatted    ;
+  // dart format off
+  here   +   does    +    not   ;
+  // dart format on
+  but   +   here   +    does   ;
+
+  {
+    more  +  formatted  ;
+    // dart format off
+    more   +   unformatted   ;
+    multiple   +   lines   ;
+    // dart format on
+  }
+}
+<<<
+main() {
+  here + gets + formatted;
+  // dart format off
+  here   +   does    +    not   ;
+  // dart format on
+  but + here + does;
+
+  {
+    more + formatted;
+    // dart format off
+    more   +   unformatted   ;
+    multiple   +   lines   ;
+    // dart format on
+  }
+}
+>>> Begin outside block and end inside.
+main() {
+  before  ;
+  // dart format off
+  inside  +  region  +  outside  +  block  ;
+  {
+    inside  +  block  +  and  +  region  ;
+    // dart format on
+    inside  +  block  +  outside  +  region  ;
+  }
+  outside  +  block  ;
+}
+<<<
+main() {
+  before;
+  // dart format off
+  inside  +  region  +  outside  +  block  ;
+  {
+    inside  +  block  +  and  +  region  ;
+    // dart format on
+    inside + block + outside + region;
+  }
+  outside + block;
+}
+>>> Begin inside block and end outside.
+main() {
+  before  ;
+  {
+    inside  +  block  +  outside  +  region  ;
+    // dart format off
+    inside  +  block  +  and  +  region  ;
+  }
+  outside  +  block  +  inside  +  region  ;
+  // dart format on
+  outside  +  block  ;
+}
+<<<
+main() {
+  before;
+  {
+    inside + block + outside + region;
+    // dart format off
+    inside  +  block  +  and  +  region  ;
+  }
+  outside  +  block  +  inside  +  region  ;
+  // dart format on
+  outside + block;
+}
+>>> Preserve indentation in region but not region marker comments.
+main() {
+      too  +  much  ;
+      // dart format off
+      still  +  too  +  much  ;
+      // dart format on
+      again  +  too  +  much  ;
+}
+<<<
+main() {
+  too + much;
+  // dart format off
+      still  +  too  +  much  ;
+      // dart format on
+  again + too + much;
+}
+>>> On without off does nothing.
+main() {
+  before  +  marker  ;
+  // dart format on
+  after  +  marker  ;
+}
+<<<
+main() {
+  before + marker;
+  // dart format on
+  after + marker;
+}
+>>> Off without on leaves rest of file unformatted.
+main() {
+  before  +  marker  ;
+  // dart format off
+  after  +  marker  ;
+}
+<<<
+main() {
+  before + marker;
+  // dart format off
+  after  +  marker  ;
+}
+>>> Markers do not nest.
+main() {
+  one  ;
+  // dart format off
+  two  ;
+  // dart format off
+  three  ;
+  // dart format on
+  four  ;
+  // dart format on
+  five  ;
+}
+<<<
+main() {
+  one;
+  // dart format off
+  two  ;
+  // dart format off
+  three  ;
+  // dart format on
+  four;
+  // dart format on
+  five;
+}
+>>> Formatted code on same line before format off comment.
+main() {
+  long  +  code  +  before  +  comment  +  // dart format off
+  unformatted  +  code  +
+  // dart format on
+  after  +  region;
+}
+<<<
+main() {
+  long +
+      code +
+      before +
+      comment + // dart format off
+  unformatted  +  code  +
+  // dart format on
+      after +
+      region;
+}
+>>> Unformatted code on same line before format on comment.
+main() {
+  before  +  region  +
+  // dart format off
+  unformatted  +  code  +  // dart format on
+  after  +  region;
+}
+<<<
+main() {
+  before +
+      region +
+      // dart format off
+  unformatted  +  code  +  // dart format on
+      after +
+      region;
+}
\ No newline at end of file
diff --git a/test/tall/other/format_off_selection.unit b/test/tall/other/format_off_selection.unit
new file mode 100644
index 0000000..e0ff161
--- /dev/null
+++ b/test/tall/other/format_off_selection.unit
@@ -0,0 +1,65 @@
+40 columns                              |
+>>> Selection start inside disabled formatting.
+main() {
+  here   +   gets   +   format‹ted    ;
+  // dart format off
+  here   +   does  ›  +    not   ;
+  // dart format on
+  but   +   here   +    does   ;
+}
+<<<
+main() {
+  here + gets + format‹ted;
+  // dart format off
+  here   +   does  ›  +    not   ;
+  // dart format on
+  but + here + does;
+}
+>>> Selection end inside disabled formatting.
+main() {
+  here   +   gets   +   formatted    ;
+  // dart format off
+  here   +   does  ‹  +    not   ;
+  // dart format on
+  but   +   here   +    do›es   ;
+}
+<<<
+main() {
+  here + gets + formatted;
+  // dart format off
+  here   +   does  ‹  +    not   ;
+  // dart format on
+  but + here + do›es;
+}
+>>> Selection inside disabled formatting.
+main() {
+  here   +   gets   +   formatted    ;
+  // dart format off
+  here  ‹ +   does    + ›   not   ;
+  // dart format on
+  but   +   here   +    does   ;
+}
+<<<
+main() {
+  here + gets + formatted;
+  // dart format off
+  here  ‹ +   does    + ›   not   ;
+  // dart format on
+  but + here + does;
+}
+>>> Selection around disabled formatting.
+main() {
+  here   +   gets   +   format‹ted    ;
+  // dart format off
+  here   +   does    +    not   ;
+  // dart format on
+  but   +   here   +    do›es   ;
+}
+<<<
+main() {
+  here + gets + format‹ted;
+  // dart format off
+  here   +   does    +    not   ;
+  // dart format on
+  but + here + do›es;
+}
\ No newline at end of file
diff --git a/test/tall/selection/selection.stmt b/test/tall/other/selection.stmt
similarity index 100%
rename from test/tall/selection/selection.stmt
rename to test/tall/other/selection.stmt
diff --git a/test/tall/selection/selection.unit b/test/tall/other/selection.unit
similarity index 100%
rename from test/tall/selection/selection.unit
rename to test/tall/other/selection.unit
diff --git a/test/tall/selection/comma.stmt b/test/tall/other/selection_comma.stmt
similarity index 100%
rename from test/tall/selection/comma.stmt
rename to test/tall/other/selection_comma.stmt
diff --git a/test/tall/selection/comment.stmt b/test/tall/other/selection_comment.stmt
similarity index 100%
rename from test/tall/selection/comment.stmt
rename to test/tall/other/selection_comment.stmt
diff --git a/test/tall/top_level/trailing_whitespace.unit b/test/tall/other/trailing_whitespace.unit
similarity index 100%
rename from test/tall/top_level/trailing_whitespace.unit
rename to test/tall/other/trailing_whitespace.unit
diff --git a/test/tall/top_level/unicode.unit b/test/tall/other/unicode.unit
similarity index 100%
rename from test/tall/top_level/unicode.unit
rename to test/tall/other/unicode.unit
diff --git a/test/tall_format_test.dart b/test/tall_format_test.dart
index d55f4a3..35404ba 100644
--- a/test/tall_format_test.dart
+++ b/test/tall_format_test.dart
@@ -14,8 +14,8 @@
   await testDirectory('tall/expression');
   await testDirectory('tall/function');
   await testDirectory('tall/invocation');
+  await testDirectory('tall/other');
   await testDirectory('tall/pattern');
-  await testDirectory('tall/selection');
   await testDirectory('tall/statement');
   await testDirectory('tall/top_level');
   await testDirectory('tall/type');