maskInlineDirectives function

String maskInlineDirectives(
  1. String source,
  2. List<InlineDirective> directives, {
  3. MarkdownBlockRegistry? blockRegistry,
})

Replaces every complete directives occurrence in source with an inert sentinel carrying its payload — except inside code, which stays as written.

Returns source unchanged when there is nothing to do, so a document without directives — which is every document unless the host configured one — costs one indexOf per directive and no allocation.

Implementation

String maskInlineDirectives(
  String source,
  List<InlineDirective> directives, {
  MarkdownBlockRegistry? blockRegistry,
}) {
  if (blockRegistry != null && directives.isNotEmpty) {
    return _outsideCustomBlocks(
      source,
      blockRegistry,
      (text) => maskInlineDirectives(text, directives),
    );
  }
  if (directives.isEmpty) {
    return source;
  }
  // Most replies hold no directive at all: one `indexOf` per directive, and no
  // allocation, decides that.
  if (!directives.any((d) => source.contains(d.open))) {
    return source;
  }

  // One pass, left to right, where whatever starts first wins.
  //
  // A directive inside code is code: `` `genui{x}` `` is the reader's text,
  // and masking it showed them the sentinel and its Base64 payload instead,
  // because code is never expanded back. Fences — in a block quote too — and
  // code spans are copied through untouched.
  //
  // And a directive's payload is opaque the other way round: a backtick in a
  // JSON payload is not a code span. Finding code first and masking second
  // would let that backtick pair with one in a later payload and hide a real
  // directive, so the two are decided in the same scan.
  final out = StringBuffer();
  final n = source.length;
  var i = 0;
  var lineStart = true;
  var paragraphEndsAt = -1;
  FenceOpen? fence;
  var fenceDepth = 0;
  while (i < n) {
    if (lineStart) {
      final lineEnd = source.indexOf('\n', i);
      final end = lineEnd == -1 ? n : lineEnd;
      final line = source.substring(i, end);
      if (fence != null && quoteDepth(line) < fenceDepth) {
        fence = null;
      }
      final open = fence;
      final opens = open == null ? fenceOpen(unquoted(line)) : null;
      if (open != null || opens != null) {
        if (open != null && isFenceClose(unquoted(line), open)) {
          fence = null;
        } else if (opens != null) {
          fence = opens;
          fenceDepth = quoteDepth(line);
        }
        out.write(line);
        if (lineEnd != -1) {
          out.write('\n');
        }
        i = lineEnd == -1 ? n : lineEnd + 1;
        continue;
      }
      lineStart = false;
    }

    // A directive starting here, earlier-declared directives first.
    var matched = false;
    for (var index = 0; index < directives.length; index++) {
      final directive = directives[index];
      if (!source.startsWith(directive.open, i)) {
        continue;
      }
      final from = i + directive.open.length;
      final close = source.indexOf(directive.close, from);
      if (close == -1) {
        // Still arriving. Left as written so it reads as the literal text it
        // currently is, and masked on a later build once the closer lands.
        // Everything after it is its payload so far.
        out.write(source.substring(i));
        return out.toString();
      }
      out
        ..write(_maskOpen)
        ..write(index)
        ..write(':')
        ..write(base64Encode(utf8.encode(source.substring(from, close))))
        ..write(_maskClose);
      i = close + directive.close.length;
      matched = true;
      break;
    }
    if (matched) {
      continue;
    }

    final c = source.codeUnitAt(i);
    if (c == 0x0A) {
      out.writeCharCode(c);
      i += 1;
      lineStart = true;
      continue;
    }
    if (c == 0x5C /* \ */ && i + 1 < n) {
      // An escaped backtick opens no code span.
      out.write(source.substring(i, i + 2));
      lineStart = source.codeUnitAt(i + 1) == 0x0A;
      i += 2;
      continue;
    }
    if (c == 0x60 /* ` */ ) {
      if (i > paragraphEndsAt) {
        paragraphEndsAt = paragraphEnd(source, i);
      }
      final span = codeSpanAt(source, i, end: paragraphEndsAt);
      final end = span != null
          ? span.close + span.run
          : i + backtickRunAt(source, i);
      out.write(source.substring(i, end));
      i = end;
      continue;
    }
    out.writeCharCode(c);
    i += 1;
  }
  return out.toString();
}