unindent method

void unindent()

Remove indentation from the current selection or the current line.

If a range is selected, the method attempts to remove up to three leading spaces from each line in the selection (or removes the leading contiguous spaces if fewer than three). The selection is adjusted to reflect the removed characters. If there is no selection, the current line is unindented and the caret is moved appropriately.

Uses replaceRange and setSelectionSilently to update the document and selection without causing external selection side effects.

Implementation

void unindent() {
  if (selection.baseOffset != selection.extentOffset) {
    final selStart = selection.start;
    final selEnd = selection.end;

    final lineStart = text.lastIndexOf('\n', selStart - 1) + 1;
    int lineEnd = text.indexOf('\n', selEnd);
    if (lineEnd == -1) lineEnd = text.length;

    final selectedBlock = text.substring(lineStart, lineEnd);
    final unindentedBlock = selectedBlock
        .split('\n')
        .map(
          (line) => line.startsWith('   ')
              ? line.substring(3)
              : line.replaceFirst(RegExp(r'^ +'), ''),
        )
        .join('\n');

    final lines = selectedBlock.split('\n');
    int removedChars = 0;
    for (final line in lines) {
      if (line.startsWith('   ')) {
        removedChars += 3;
      } else {
        removedChars += RegExp(r'^ +').stringMatch(line)?.length ?? 0;
      }
    }

    final newSelection = TextSelection(
      baseOffset:
          selection.baseOffset -
          (lines.first.startsWith('   ')
              ? 3
              : (RegExp(r'^ +').stringMatch(lines.first)?.length ?? 0)),
      extentOffset: selection.extentOffset - removedChars,
    );

    replaceRange(lineStart, lineEnd, unindentedBlock);
    setSelectionSilently(newSelection);
  } else {
    final caret = selection.start;
    final prevNewline = text.lastIndexOf('\n', caret - 1);
    final lineStart = prevNewline == -1 ? 0 : prevNewline + 1;
    final nextNewline = text.indexOf('\n', caret);
    final lineEnd = nextNewline == -1 ? text.length : nextNewline;
    final line = text.substring(lineStart, lineEnd);

    int removeCount = 0;
    if (line.startsWith('   ')) {
      removeCount = 3;
    } else {
      removeCount = RegExp(r'^ +').stringMatch(line)?.length ?? 0;
    }

    final newLine = line.substring(removeCount);
    final newOffset = caret - removeCount > lineStart
        ? caret - removeCount
        : lineStart;

    replaceRange(lineStart, lineEnd, newLine);
    setSelectionSilently(TextSelection.collapsed(offset: newOffset));
  }
}