inlineSafeLength function

int inlineSafeLength(
  1. String source, {
  2. bool holdMathDollars = false,
})

How much of source can be shown without its styling changing later.

Returns source's length when nothing is pending, so the common case costs one scan and no allocation.

Deliberately a scan for unbalanced delimiters rather than a second parse: it runs on the tail segment for every chunk that arrives, and it only has to be conservative. Holding a character that would not have changed costs a few milliseconds of latency; showing one that does change is the artefact this exists to remove.

Implementation

int inlineSafeLength(String source, {bool holdMathDollars = false}) {
  var limit = source.length;

  // Each delimiter carries its own patience. Holding is only ever worth it
  // while the closer is plausibly still coming.
  void holdAt(int index, int cap) {
    if (index < 0 || index >= limit) {
      return;
    }
    if (source.length - index > cap) {
      return;
    }
    limit = index;
  }

  // A code span runs to its closing backtick and swallows any other delimiter
  // on the way, so it is resolved first and the rest is checked outside it.
  var i = 0;
  var lastOpenTick = -1;
  final outside = StringBuffer();
  while (i < source.length) {
    if (source.codeUnitAt(i) == 0x60 /* ` */ ) {
      final close = source.indexOf('`', i + 1);
      if (close == -1) {
        lastOpenTick = i;
        break;
      }
      // Keep the offsets aligned without copying the span's contents.
      outside.write(' ' * (close + 1 - i));
      i = close + 1;
      continue;
    }
    outside.writeCharCode(source.codeUnitAt(i));
    i += 1;
  }
  holdAt(lastOpenTick, proseDelimiterHold);

  // A `*` that begins a line and is followed by whitespace is a list bullet,
  // and a line made only of `*`/`-`/`_` is a thematic break — neither is an
  // emphasis delimiter, and counting them as one made every streamed `*`
  // bullet invisible for the length of the leash. They are blanked out
  // before the emphasis scans, offsets preserved.
  final rest = _maskLineMarkers(outside.toString());
  // Paired delimiters: an odd count means the last one is still open.
  for (final token in const ['**', '~~', r'$$']) {
    holdAt(_lastUnpaired(rest, token), proseDelimiterHold);
  }
  // Single `*` for italic, but only the ones that are not part of `**`.
  holdAt(_lastUnpairedStar(rest), proseDelimiterHold);

  // Delimiters with distinct open and close forms.
  for (final pair in const [(r'\(', r'\)'), (r'\[', r'\]'), ('<u>', '</u>')]) {
    final open = rest.lastIndexOf(pair.$1);
    if (open != -1 && rest.indexOf(pair.$2, open + pair.$1.length) == -1) {
      holdAt(open, markupDelimiterHold);
    }
  }

  // A link or image: `[label](href)` is prose until its closing paren.
  final bracket = rest.lastIndexOf('[');
  if (bracket != -1) {
    final closeBracket = rest.indexOf(']', bracket + 1);
    if (closeBracket == -1) {
      holdAt(bracket, markupDelimiterHold);
    } else if (closeBracket + 1 < rest.length &&
        rest.codeUnitAt(closeBracket + 1) == 0x28 /* ( */ &&
        rest.indexOf(')', closeBracket + 1) == -1) {
      holdAt(bracket, markupDelimiterHold);
    }
  }

  // A table row is atomic: a line beginning with `|` is held until its
  // newline arrives, so the parser only ever sees complete rows. And while
  // the delimiter row is still arriving, the header is held with it — a
  // partial delimiter is invalid, so without this the table formed on one
  // cut (`|-`), fell apart on the next (`|:`), and re-formed, several times
  // per row.
  final lastNewline = source.lastIndexOf('\n');
  final lastLine = source.substring(lastNewline + 1);
  if (lastLine.trimLeft().startsWith('|')) {
    var holdIndex = lastNewline + 1;
    if (lastNewline >= 0 && _partialTableSeparator.hasMatch(lastLine.trim())) {
      final prevNewline = source.lastIndexOf('\n', lastNewline - 1);
      final prevLine = source.substring(prevNewline + 1, lastNewline);
      if (prevLine.trimLeft().startsWith('|')) {
        holdIndex = prevNewline + 1;
      }
    }
    holdAt(holdIndex, markupDelimiterHold);
  }

  // Single-`$` maths, only when the caller treats `$…$` as maths at all —
  // to everyone else a dollar is a dollar.
  if (holdMathDollars) {
    holdAt(_lastUnpairedDollar(rest), markupDelimiterHold);
  }

  // A trailing character that is only the first half of an opener: `\` may
  // become `\(` and `<` may become `<u>`. Un-held it is shown, revealed, and
  // then vanishes when the second half lands — the one leak the rules above
  // cannot see. The very next character decides, so this holds a couple of
  // characters at most and cannot stall.
  final partial = _partialTrailingOpener.firstMatch(source);
  if (partial != null) {
    holdAt(partial.start, proseDelimiterHold);
  }

  return limit;
}