From 17639ed62d7d30b0fa7fe98cdf3ceb63006e1945 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 25 Jun 2026 14:33:17 +0300 Subject: [PATCH] =?UTF-8?q?mdview2:=20B2=20stage=201=20=E2=80=94=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=BD=D0=BE=D1=81=20inline=5Fmarker=20(-870=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B9=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Разбор inline-маркеров (\X, [x], `, **, ~~, *, _, \t) был продублирован в inline_scan() и scan_join_stream(). Вынесен в общий inline_marker(): возвращает 1 если токен обработан, 0 если обычный символ/пробел (его кладёт вызывающий). attr = emph_to_attr(ls, base); для join base=ATTR_TEXT, что тождественно прежнему styles_map[ls]. В scan_join маркеры пропускаются при soft_break (синтетический пробел склейки кладёт ветка пробела). scan_join_stream 3794→2329, inline_scan 1842→700, inline_marker +1436. _CODE 22246→21376. Рендер идентичный (проверено в MAME). Co-Authored-By: Claude Opus 4.8 --- examples/mdview2/mdview2.c | 242 +++++++++++-------------------------- 1 file changed, 71 insertions(+), 171 deletions(-) diff --git a/examples/mdview2/mdview2.c b/examples/mdview2/mdview2.c index f800bcc..0211294 100644 --- a/examples/mdview2/mdview2.c +++ b/examples/mdview2/mdview2.c @@ -1011,6 +1011,70 @@ static uint8_t is_table_raw(uint32_t p) return 0; } +/* Разбор одного inline-МАРКЕРА в позиции *pq (< bound): \X, [x], `, **, ~~, + * *, _, \t. Общий код для inline_scan() и scan_join_stream(). Возврат: 1 — + * маркер обработан (вызывающий делает continue), 0 — символ обычный (пробел/ + * текст), его кладёт сам вызывающий (и проверяет перенос). Меняет + * *pq/*pls/*pseg/*pprev. attr берётся как emph_to_attr(line_style, base) — + * для join base=ATTR_TEXT, что тождественно styles_map[line_style]. */ +static uint8_t inline_marker(uint32_t *pq, uint32_t bound, uint8_t *pls, + uint8_t *pseg, char *pprev, uint8_t base) +{ + uint32_t q = *pq; + uint8_t ls = *pls; + char ch = fb(q); + + if (ls != INIT_STYLE_CODE && ch == '\\' && (q + 1) < bound && is_escapable(fb(q + 1))) { + char nb = fb(q + 1); + gc_put(nb, emph_to_attr(ls, base)); + *pseg += 1; *pq = q + 2; *pprev = nb; return 1; + } + if (ls != INIT_STYLE_CODE && ch == '[' && (q + 2) < bound && fb(q + 2) == ']') { + gc_put('[', ATTR_TEXT_BOLD); gc_put(fb(q + 1), ATTR_TEXT_BOLD); gc_put(']', ATTR_TEXT_BOLD); + *pseg += 3; *pq = q + 3; *pprev = ']'; return 1; + } + if (ch == '`') { + if (ls == INIT_STYLE_PLAIN) { *pls = INIT_STYLE_CODE; *pq = q + 1; return 1; } + if (ls == INIT_STYLE_CODE) { *pls = INIT_STYLE_PLAIN; *pq = q + 1; return 1; } + gc_put('`', emph_to_attr(ls, base)); *pseg += 1; *pq = q + 1; *pprev = '`'; return 1; + } + if (ch == '*' && (q + 1) < bound && fb(q + 1) == '*') { + char nx = ((q + 2) < bound) ? fb(q + 2) : '\n'; + if (is_emph_flanked(*pprev, nx)) { + if (ls == INIT_STYLE_PLAIN && ws_or_eol(*pprev)) { *pls = INIT_STYLE_BOLD; *pq = q + 2; return 1; } + if (ls == INIT_STYLE_BOLD && ws_or_eol_or_delim(nx)) { *pls = INIT_STYLE_PLAIN; *pq = q + 2; return 1; } + } + gc_put('*', emph_to_attr(ls, base)); gc_put('*', emph_to_attr(ls, base)); + *pseg += 2; *pq = q + 2; *pprev = '*'; return 1; + } + if (ch == '~' && (q + 1) < bound && fb(q + 1) == '~') { + char nx = ((q + 2) < bound) ? fb(q + 2) : '\n'; + if (is_emph_flanked(*pprev, nx)) { + if (ls == INIT_STYLE_PLAIN && ws_or_eol(*pprev)) { *pls = INIT_STYLE_STRIKE; *pq = q + 2; return 1; } + if (ls == INIT_STYLE_STRIKE && ws_or_eol_or_delim(nx)) { *pls = INIT_STYLE_PLAIN; *pq = q + 2; return 1; } + } + gc_put('~', emph_to_attr(ls, base)); gc_put('~', emph_to_attr(ls, base)); + *pseg += 2; *pq = q + 2; *pprev = '~'; return 1; + } + if (ch == '*' || ch == '_') { + char nx = ((q + 1) < bound) ? fb(q + 1) : '\n'; + if (is_emph_flanked(*pprev, nx)) { + uint8_t ns = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER; + if (ls == INIT_STYLE_PLAIN && ws_or_eol(*pprev)) { *pls = ns; *pq = q + 1; return 1; } + if (ls != INIT_STYLE_PLAIN && ws_or_eol_or_delim(nx)) { *pls = INIT_STYLE_PLAIN; *pq = q + 1; return 1; } + } + gc_put(ch, emph_to_attr(ls, base)); *pseg += 1; *pq = q + 1; *pprev = ch; return 1; + } + if (ch == '\t') { + uint8_t old_sc = *pseg; + uint8_t tgt_full = (uint8_t)((old_sc & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP); + uint8_t tgt = tgt_full; if (tgt > SCREEN_W) tgt = SCREEN_W; + gc_fill(' ', emph_to_attr(ls, base), (uint8_t)(tgt_full - old_sc)); + *pseg = tgt; *pq = q + 1; *pprev = ' '; return 1; + } + return 0; /* пробел или обычный символ — обрабатывает вызывающий */ +} + /* Общий сканер inline-форматирования и переносов. * Идёт от q до q_end, учитывает стартовую колонку col и при необходимости * эмитит continuation-сегменты. Возвращает итоговый line_style. */ @@ -1039,73 +1103,9 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col, continue; } - /* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */ - if (line_style != INIT_STYLE_CODE && ch == '\\' && (q + 1) < q_end && is_escapable(fb(q + 1))) { - char nb = fb(q + 1); - gc_put(nb, emph_to_attr(line_style, base)); - seg_col++; q += 2; prev_ch = nb; continue; - } - /* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */ - if (line_style != INIT_STYLE_CODE && ch == '[' && (q + 2) < q_end && fb(q + 2) == ']') { - char mid = fb(q + 1); - gc_put('[', ATTR_TEXT_BOLD); - gc_put(mid, ATTR_TEXT_BOLD); - gc_put(']', ATTR_TEXT_BOLD); - seg_col += 3; q += 3; prev_ch = ']'; continue; - } + /* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор. */ + if (inline_marker(&q, q_end, &line_style, &seg_col, &prev_ch, base)) continue; - if (ch == '`') { - if (line_style == INIT_STYLE_PLAIN) { line_style = INIT_STYLE_CODE; q++; continue; } - if (line_style == INIT_STYLE_CODE) { line_style = INIT_STYLE_PLAIN; q++; continue; } - gc_put('`', emph_to_attr(line_style, base)); - seg_col++; q++; prev_ch = '`'; continue; - } - if (ch == '*' && (q + 1) < q_end && fb(q + 1) == '*') { - char next_ch = ((q + 2) < q_end) ? fb(q + 2) : '\n'; - if (is_emph_flanked(prev_ch, next_ch)) { - if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { - line_style = INIT_STYLE_BOLD; q += 2; continue; - } else if (line_style == INIT_STYLE_BOLD && ws_or_eol_or_delim(next_ch)) { - line_style = INIT_STYLE_PLAIN; q += 2; continue; - } - } - { gc_put('*', emph_to_attr(line_style, base)); gc_put('*', emph_to_attr(line_style, base)); } - seg_col += 2; q += 2; prev_ch = '*'; continue; - } - if (ch == '~' && (q + 1) < q_end && fb(q + 1) == '~') { - char next_ch = ((q + 2) < q_end) ? fb(q + 2) : '\n'; - if (is_emph_flanked(prev_ch, next_ch)) { - if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { - line_style = INIT_STYLE_STRIKE; q += 2; continue; - } else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) { - line_style = INIT_STYLE_PLAIN; q += 2; continue; - } - } - { gc_put('~', emph_to_attr(line_style, base)); gc_put('~', emph_to_attr(line_style, base)); } - seg_col += 2; q += 2; prev_ch = '~'; continue; - } - if (ch == '*' || ch == '_') { - char next_ch = ((q + 1) < q_end) ? fb(q + 1) : '\n'; - if (is_emph_flanked(prev_ch, next_ch)) { - uint8_t new_style = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER; - if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { - line_style = new_style; q++; continue; - } else if (line_style != INIT_STYLE_PLAIN && ws_or_eol_or_delim(next_ch)) { - /* как render: закрывает любой активный эмфазис */ - line_style = INIT_STYLE_PLAIN; q++; continue; - } - } - gc_put(ch, emph_to_attr(line_style, base)); - seg_col++; q++; prev_ch = ch; continue; - } - if (ch == '\t') { - uint8_t old_sc = seg_col; - uint8_t tgt_full = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP); - uint8_t tgt = tgt_full; - if (tgt > SCREEN_W) tgt = SCREEN_W; - gc_fill(' ', emph_to_attr(line_style, base), (uint8_t)(tgt_full - old_sc)); - seg_col = tgt; q++; prev_ch = ' '; continue; - } if (ch == ' ') { gc_put(' ', emph_to_attr(line_style, base)); last_space = q + 1; @@ -1261,112 +1261,12 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, } } - /* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */ - if (line_style != INIT_STYLE_CODE && ch == '\\' && (q + 1) < file_size && is_escapable(fb(q + 1))) { - char nb = fb(q + 1); - gc_put(nb, styles_map[line_style]); - seg_col++; q += 2; prev_ch = nb; continue; - } - /* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */ - if (line_style != INIT_STYLE_CODE && ch == '[' && (q + 2) < file_size && fb(q + 2) == ']') { - char mid = fb(q + 1); - gc_put('[', ATTR_TEXT_BOLD); - gc_put(mid, ATTR_TEXT_BOLD); - gc_put(']', ATTR_TEXT_BOLD); - seg_col += 3; q += 3; prev_ch = ']'; continue; - } + /* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор; при + * soft_break ch==' ' (синтетический пробел склейки) — маркеры + * пропускаем, его кладёт ветка пробела ниже. */ + if (!soft_break && inline_marker(&q, file_size, &line_style, &seg_col, &prev_ch, ATTR_TEXT)) + continue; - if (ch == '`') { - /* Как handle_inline_marker в render: backtick переключает code-режим - * ТОЛЬКО из PLAIN или CODE; если активен эмфазис (bold/italic/...), - * backtick — обычный литеральный символ (нет вложенного кода). */ - if (line_style == INIT_STYLE_PLAIN) { - line_style = INIT_STYLE_CODE; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } - if (line_style == INIT_STYLE_CODE) { - line_style = INIT_STYLE_PLAIN; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } - gc_put('`', styles_map[line_style]); - seg_col++; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - prev_ch = '`'; - continue; - } - if (ch == '*' && (q + 1) < file_size && fb(q + 1) == '*') { - char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n'; - if (is_emph_flanked(prev_ch, next_ch)) { - if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { - line_style = INIT_STYLE_BOLD; - q += 2; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } else if (line_style == INIT_STYLE_BOLD && ws_or_eol_or_delim(next_ch)) { - line_style = INIT_STYLE_PLAIN; - q += 2; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } - } - { gc_put('*', styles_map[line_style]); gc_put('*', styles_map[line_style]); } - seg_col += 2; - q += 2; /* soft_break здесь всегда 0 (ch!=' ') */ - prev_ch = '*'; - continue; - } - if (ch == '~' && (q + 1) < file_size && fb(q + 1) == '~') { - char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n'; - if (is_emph_flanked(prev_ch, next_ch)) { - if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { - line_style = INIT_STYLE_STRIKE; - q += 2; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) { - line_style = INIT_STYLE_PLAIN; - q += 2; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } - } - { gc_put('~', styles_map[line_style]); gc_put('~', styles_map[line_style]); } - seg_col += 2; - q += 2; /* soft_break здесь всегда 0 (ch!=' ') */ - prev_ch = '~'; - continue; - } - if (ch == '*' || ch == '_') { - char next_ch = ((q + 1) < file_size) ? fb(q + 1) : '\n'; - if (is_emph_flanked(prev_ch, next_ch)) { - uint8_t new_style = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER; - if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { - line_style = new_style; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } else if (line_style != INIT_STYLE_PLAIN && ws_or_eol_or_delim(next_ch)) { - /* как render: одиночный маркер закрывает ЛЮБОЙ активный эмфазис */ - line_style = INIT_STYLE_PLAIN; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - continue; - } - } - gc_put(ch, styles_map[line_style]); - seg_col++; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - prev_ch = ch; - continue; - } - if (ch == '\t') { - uint8_t old_sc = seg_col; - uint8_t tgt_full = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP); - uint8_t tgt = tgt_full; - if (tgt > SCREEN_W) tgt = SCREEN_W; - /* render заполняет до НЕкапнутого tgt_full (по cc); для plain cc==seg_col */ - gc_fill(' ', styles_map[line_style], (uint8_t)(tgt_full - old_sc)); - seg_col = tgt; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ - prev_ch = ' '; - continue; - } if (ch == ' ') { gc_put(' ', styles_map[line_style]); if (soft_break) {