markdownToPlainText function

String markdownToPlainText(
  1. String markdown
)

Converts Markdown source to a readable plain-text representation.

This mirrors Scratch's copy-as-plain-text behavior: Markdown structure is stripped outside fenced code blocks, while code block content is preserved.

Implementation

String markdownToPlainText(String markdown) {
  var inCodeBlock = false;
  final plainLines = <String>[];

  for (final line in markdown.split(RegExp(r'\r?\n'))) {
    var text = line;

    if (RegExp(r'^\s*(```|~~~)').hasMatch(text)) {
      inCodeBlock = !inCodeBlock;
      plainLines.add('');
      continue;
    }

    if (!inCodeBlock) {
      text = text
          .replaceFirst(RegExp(r'^\s{0,3}#{1,6}\s+'), '')
          .replaceFirst(RegExp(r'^\s{0,3}>\s?'), '')
          .replaceFirst(RegExp(r'^\s*([-*+]\s+|\d+\.\s+)'), '')
          .replaceFirst(RegExp(r'^\[[ xX]\]\s+'), '')
          .replaceFirst(RegExp(r'^\s*([*-]){3,}\s*$'), '');
      text = _replaceAllMapped(text, RegExp(r'!\[(.*?)\]\([^)]*\)'), 1);
      text = _replaceAllMapped(text, RegExp(r'\[(.+?)\]\([^)]*\)'), 1);
      text = _replaceAllMapped(text, RegExp(r'`([^`]+)`'), 1);
      text = _replaceAllMapped(text, RegExp(r'\*\*(.+?)\*\*'), 1);
      text = _replaceUnderscoreDelimiterPairs(
        text,
        '__',
        (content) => content,
      );
      text = _replaceAllMapped(text, RegExp(r'\*(.+?)\*'), 1);
      text = _replaceUnderscoreDelimiterPairs(
        text,
        '_',
        (content) => content,
      );
      text = _replaceAllMapped(text, RegExp(r'~~(.+?)~~'), 1);
    }

    plainLines.add(text);
  }

  return plainLines
      .join('\n')
      .replaceAll(RegExp(r'\n{3,}'), '\n\n')
      .trimRight();
}