mobile: keep multi-backtick code spans and decode link labels in previews

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Tom Brow
2026-10-29 07:35:21 -07:00
parent 836d0e7cbb
commit c77326a918
2 changed files with 60 additions and 12 deletions

View File

@@ -151,6 +151,7 @@ List<PreviewPiece> messagePreviewPieces(
}
const _maxLineLength = 2000;
const _backtick = 0x60;
final _rule = RegExp(r'^([-*_])(?:\s*\1){2,}$');
final _tableDivider = RegExp(r'^\|?(?:\s*:?-+:?\s*\|)+\s*:?-*:?\s*\|?$');
@@ -236,20 +237,57 @@ class _PreviewBuilder {
piece is PreviewText ? text(piece.text) : pieces.add(piece);
/// One line of prose: inline code is literal, everything else is inline
/// Markdown.
/// Markdown. A code span opens at a run of backticks and closes at the next
/// run of the same length; a run with no match is literal.
void line(String line) {
final parts = line.split('`');
// An unpaired backtick is literal.
if (parts.length.isEven) {
parts[parts.length - 2] += '`${parts.removeLast()}';
}
for (var i = 0; i < parts.length; i++) {
if (i.isOdd) {
text(parts[i]);
} else {
_inline(parts[i]);
var prose = 0;
var cursor = 0;
while (cursor < line.length) {
if (line.codeUnitAt(cursor) != _backtick) {
cursor++;
continue;
}
final open = cursor;
final openEnd = _runEnd(line, open);
final run = openEnd - open;
int? close;
var scan = openEnd;
while (scan < line.length) {
if (line.codeUnitAt(scan) != _backtick) {
scan++;
continue;
}
final end = _runEnd(line, scan);
if (end - scan == run) {
close = scan;
break;
}
scan = end;
}
if (close == null) {
cursor = openEnd;
continue;
}
_inline(line.substring(prose, open));
var code = line.substring(openEnd, close);
if (code.length > 2 &&
code.startsWith(' ') &&
code.endsWith(' ') &&
code.trim().isNotEmpty) {
code = code.substring(1, code.length - 1);
}
text(code);
cursor = prose = close + run;
}
_inline(line.substring(prose));
}
static int _runEnd(String line, int start) {
var end = start;
while (end < line.length && line.codeUnitAt(end) == _backtick) {
end++;
}
return end;
}
void _inline(String source) {
@@ -274,7 +312,9 @@ class _PreviewBuilder {
add(PreviewChannel(_channelName(deepLink.channelId)));
continue;
}
_tokens(_unwrapEmphasis(label.isEmpty ? url : label));
_tokens(
_unwrapEmphasis(label.isEmpty ? url : decodeMarkdownLabelSyntax(label)),
);
}
_tokens(_unwrapEmphasis(source.substring(start)));
}

View File

@@ -45,6 +45,14 @@ void main() {
('lone asterisk', '2 * 3 = 6', '2 * 3 = 6'),
('inline code', 'run `**not bold**` here', 'run **not bold** here'),
('unpaired backtick', 'a ` b **c**', 'a ` b c'),
('double-backtick code', 'x ``**a** `b` @c`` y', 'x **a** `b` @c y'),
('unmatched backtick run', 'a `` b `c` **d**', 'a `` b c d'),
('padded code span', '`` `x` ``', '`x`'),
(
'encoded label brackets',
'[report &#91;Q4&#93;](https://x.y)',
'report [Q4]',
),
('link', 'see [the docs](https://example.com)', 'see the docs'),
('bare link label', '[https://x.y](https://x.y)', 'https://x.y'),
('rule', 'above\n---\nbelow', 'above\nbelow'),