renderRanges method
List<String>
renderRanges(
- String source,
- Iterable<
TreeSitterHtmlHighlight> highlights, { - String? carriageReturnScope = 'carriage-return',
Renders Flutter-friendly UTF-16 highlight ranges as Tree-sitter HTML.
This adapter creates the same streaming events consumed by render, then returns the rendered lines. It does not change the native renderer API.
Implementation
List<String> renderRanges(
String source,
Iterable<TreeSitterHtmlHighlight> highlights, {
String? carriageReturnScope = 'carriage-return',
}) {
reset();
final exact = <(int, int), TreeSitterHtmlHighlight>{};
for (final highlight in highlights) {
if (highlight.startUtf16 < 0 ||
highlight.endUtf16 < highlight.startUtf16 ||
highlight.endUtf16 > source.length) {
throw ArgumentError.value(
highlight,
'highlights',
'Highlight ranges must be in bounds.',
);
}
final key = (highlight.startUtf16, highlight.endUtf16);
final previous = exact[key];
if (previous == null ||
highlight.priority > previous.priority ||
highlight.priority == previous.priority &&
highlight.order >= previous.order) {
exact[key] = highlight;
}
}
final sorted = exact.values.toList()
..sort((left, right) {
final start = left.startUtf16.compareTo(right.startUtf16);
if (start != 0) return start;
return right.endUtf16.compareTo(left.endUtf16);
});
final nesting = <TreeSitterHtmlHighlight>[];
for (final highlight in sorted) {
while (nesting.isNotEmpty &&
highlight.startUtf16 >= nesting.last.endUtf16) {
nesting.removeLast();
}
if (nesting.isNotEmpty && highlight.endUtf16 > nesting.last.endUtf16) {
throw ArgumentError.value(
highlight,
'highlights',
'Highlight ranges must not cross.',
);
}
nesting.add(highlight);
}
final scopes = <String>[];
final scopeIndices = <String, int>{};
for (final highlight in sorted) {
scopeIndices.putIfAbsent(highlight.scope, () {
scopes.add(highlight.scope);
return scopes.length - 1;
});
}
final carriageIndex = carriageReturnScope == null
? null
: scopeIndices.putIfAbsent(carriageReturnScope, () {
scopes.add(carriageReturnScope);
return scopes.length - 1;
});
setCarriageReturnHighlight(
carriageIndex == null ? null : TreeSitterHighlight(carriageIndex),
);
final utf16ToByte = _utf16ToUtf8ByteOffsets(source);
final starts = <int, List<TreeSitterHtmlHighlight>>{};
final ends = <int, List<TreeSitterHtmlHighlight>>{};
for (final highlight in sorted) {
starts.putIfAbsent(highlight.startUtf16, () => []).add(highlight);
ends.putIfAbsent(highlight.endUtf16, () => []).add(highlight);
}
final boundaries = <int>{
0,
source.length,
...starts.keys,
...ends.keys,
}.toList()..sort();
final events = <TreeSitterHighlightEvent>[];
var previous = 0;
for (final boundary in boundaries) {
if (boundary > previous) {
events.add(
TreeSitterHighlightEvent.source(
start: utf16ToByte[previous],
end: utf16ToByte[boundary],
),
);
}
if (ends[boundary] case final ending?) {
for (final _ in ending.reversed) {
events.add(const TreeSitterHighlightEvent.end());
}
}
if (starts[boundary] case final starting?) {
for (final highlight in starting) {
events.add(
TreeSitterHighlightEvent.start(
TreeSitterHighlight(scopeIndices[highlight.scope]!),
),
);
}
}
previous = boundary;
}
render(events, utf8.encode(source), (highlight, output) {
output.addAll(
utf8.encode('class=${_escapeAttribute(scopes[highlight.index])}'),
);
});
return lines().toList(growable: false);
}