completeOpenMath function
The formula left open at the end of source, closed so that it parses.
Returns where its opener starts and the text to render in place of
source.substring(start), or null when every formula in source is
closed. Returns null too when a code span is still open, since its
backticks swallow any \( inside.
\( and \[ are always maths; $$ and $ only when dollarsAreMath.
A $ pairs as the final render pairs it (DollarMathCloser); one still
waiting for its partner is drawn open unless it is a price ($5, 5$),
as in the streaming hold. Single-dollar maths does not cross a line
break: an opener whose line has ended without a closer is prose.
Implementation
({int start, String completed})? completeOpenMath(
String source, {
required bool dollarsAreMath,
}) {
final n = source.length;
final dollars = DollarMathCloser(source);
var i = 0;
while (i < n) {
final c = source.codeUnitAt(i);
if (c == 0x60 /* ` */ ) {
final span = codeSpanAt(source, i);
if (span == null) return null;
i = span.close + span.run;
continue;
}
if (c == 0x5C /* \ */ && i + 1 < n) {
final next = source.codeUnitAt(i + 1);
if (next == 0x28 /* ( */ || next == 0x5B /* [ */ ) {
final closer = next == 0x28 ? r'\)' : r'\]';
final end = source.indexOf(closer, i + 2);
if (end == -1) {
return _open(source, i, i + 2, source.substring(i, i + 2), closer);
}
i = end + 2;
continue;
}
// Any other escape, `\$` included, is one unit.
i += 2;
continue;
}
if (dollarsAreMath && c == 0x24 /* $ */ ) {
if (i + 1 < n && source.codeUnitAt(i + 1) == 0x24) {
final end = source.indexOf(r'$$', i + 2);
if (end == -1) return _open(source, i, i + 2, r'\[', r'\]');
i = end + 2;
continue;
}
final end = dollars.find(i);
if (end == -1) {
// Still open only while it waits for a partner on the last line,
// and only if it is not a price: `$5` and `5$` show as text until a
// closer makes them maths. A `$` before a space is held rather than
// drawn, so `pay $ 5` never flashes as a formula.
final next = i + 1 < n ? source.codeUnitAt(i + 1) : -1;
if (!source.contains('\n', i) &&
next != -1 &&
!isUnicodeWhitespace(next) &&
!source.contains(r'$', i + 1) &&
!leadsPrice(source, i) &&
!followsPrice(source, i)) {
return _open(source, i, i + 1, r'\(', r'\)');
}
i += 1;
continue;
}
i = end + 1;
continue;
}
i += 1;
}
return null;
}