From adf667c0878bbc6647c28ba1e43c760c506aaa5b Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Sat, 4 Jul 2026 21:26:13 +0300 Subject: [PATCH] =?UTF-8?q?mdview2:=20=D1=81=D0=BB=D0=B8=D1=82=D1=8C=20inl?= =?UTF-8?q?ine=5Fscan=20+=20scan=5Fjoin=5Fstream=20=D0=B2=20scan=5Fstream?= =?UTF-8?q?=20=E2=80=94=20exe=2025616=20=E2=86=92=2025100?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B2 stage2: общий цикл inline-форматирования/переносов/склейки в одном scan_stream (mode: NONE / LIST / QUOTE / PLAIN); дублировавшиеся блоки эмиссии пробела/символа, переноса с усечением и отката стиля — в одном экземпляре. Старые имена — тонкие обёртки, API inline_scan для table-модуля не изменился. styles_map умерла: emph_to_attr(ls, ATTR_TEXT) тождественна ей. Индексатор 9839 → 9315 Б. Проверено в MAME: переносы заголовков/списков/ цитат, таблицы, inline-маркеры на границе переноса, жёсткие переносы. Co-Authored-By: Claude Fable 5 --- examples/mdview2/mdview2_index.c | 149 +++++++++++++------------------ 1 file changed, 60 insertions(+), 89 deletions(-) diff --git a/examples/mdview2/mdview2_index.c b/examples/mdview2/mdview2_index.c index 30aaf6d..c79d0bc 100644 --- a/examples/mdview2/mdview2_index.c +++ b/examples/mdview2/mdview2_index.c @@ -9,12 +9,12 @@ * позиции чтения. Разделяемые символы и API — в mdview2.h. * * Приватно (не торчит в .h): idx_rec_t, cur_rec/cur_seg_off, буфер ячеек - * g_cells, styles_map, idx_get/idx_put и все классификаторы/сканеры. + * g_cells, idx_get/idx_put и все классификаторы/сканеры. */ #include #include -#include /* COLOR()/COLOR_* для ATTR_* в styles_map */ +#include /* COLOR()/COLOR_* для ATTR_* в сканерах */ #include #include /* bank_read/bank_write (idx_get/idx_put) */ #include "mdview2.h" @@ -108,12 +108,6 @@ static void set_cur_flags(uint8_t f) { cur_rec.flags |= f; idx_put((uint16_t)(n_ /* INIT_STYLE_* — в mdview2.h (общие с модулем таблиц). */ -/* Таблица сопоставления init-style -> атрибут. - * Порядок должен совпадать с INIT_STYLE_* / EM_* ниже. */ -static const uint8_t styles_map[] = { - ATTR_TEXT, ATTR_TEXT_BOLD, ATTR_TEXT_ITALIC, ATTR_TEXT_UNDERSORE, ATTR_TEXT_CODE, ATTR_TEXT_STRIKE -}; - /* Тип строки, который возвращает classify_line(). */ #define LK_PLAIN 0 #define LK_H1 1 @@ -373,7 +367,7 @@ static uint8_t is_table_raw(uint32_t p) * маркер обработан (вызывающий делает continue), 0 — символ обычный (пробел/ * текст), его кладёт сам вызывающий (и проверяет перенос). Меняет * *pq/*pls/*pseg/*pprev. attr берётся как emph_to_attr(line_style, base) — - * для join base=ATTR_TEXT, что тождественно styles_map[line_style]. */ + * для join base=ATTR_TEXT (атрибут «обычного» текста). */ static uint8_t inline_marker(uint32_t *pq, uint32_t bound, uint8_t *pls, uint8_t *pseg, char *pprev, uint8_t base) { @@ -433,87 +427,45 @@ static uint8_t inline_marker(uint32_t *pq, uint32_t bound, uint8_t *pls, return 0; /* пробел или обычный символ — обрабатывает вызывающий */ } -/* Общий сканер inline-форматирования и переносов. - * Идёт от q до q_end, учитывает стартовую колонку col и при необходимости - * эмитит continuation-сегменты. Возвращает итоговый line_style. */ -uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col, - uint8_t ckind, uint8_t line_style, uint8_t base_attr, - uint8_t nowrap) -{ - uint32_t last_space = 0xFFFFFFFFu; - uint8_t last_space_style = line_style; /* снимок line_style на момент last_space */ - char prev_ch = ' '; - uint8_t seg_col = col; - /* base — атрибут «обычного» текста сегмента. Для заголовка первый сегмент - * имеет title-attr, а continuation-сегменты render рисует как PLAIN - * (ATTR_TEXT, kind=LK_PLAIN при cont) — переключаем base после 1-го переноса. */ - uint8_t base = base_attr; - - while (q < q_end && n_lines < max_lines) { - char ch = fb(q); - - if (ch == '\n') { - /* Жёсткий перенос (уже проверен снаружи) или конец потока. */ - q++; - emit_seg(q, line_style, ckind, (ckind == CK_LIST || ckind == CK_QUOTE) ? 1 : 0); - base = ATTR_TEXT; - last_space = 0xFFFFFFFFu; prev_ch = ' '; seg_col = col; - continue; - } - - /* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор. */ - if (inline_marker(&q, q_end, &line_style, &seg_col, &prev_ch, base)) continue; - - if (ch == ' ') { - gc_put(' ', emph_to_attr(line_style, base)); - last_space = q + 1; - last_space_style = line_style; - g_ncells_at_space = g_ncells; - seg_col++; q++; prev_ch = ' '; - } else { - gc_put(ch, emph_to_attr(line_style, base)); - seg_col++; q++; prev_ch = ch; - } - if (!nowrap && seg_col >= SCREEN_W) { - uint8_t use_ls = (uint8_t)(last_space != 0xFFFFFFFFu && last_space > cur_seg_off); - uint32_t wrap_at = use_ls ? last_space : q; - if (wrap_at >= q_end || wrap_at == cur_seg_off) break; - uint8_t wrap_style = use_ls ? last_space_style : line_style; - if (use_ls) g_ncells = g_ncells_at_space; - emit_seg(wrap_at, wrap_style, ckind, 1); - base = ATTR_TEXT; /* continuation заголовка → PLAIN */ - seg_col = col; last_space = 0xFFFFFFFFu; prev_ch = ' '; q = wrap_at; - line_style = wrap_style; - } - } - return line_style; -} - +#define JOIN_MODE_NONE 0u #define JOIN_MODE_LIST 1u #define JOIN_MODE_QUOTE 2u #define JOIN_MODE_PLAIN 3u -/* Общий потоковый сканер для index_lines(). - * Убирает дублирование между ветками list/quote/plain: - * - mode задаёт правила обработки перевода строки, - * - остальная inline-логика (emphasis/таб/перенос) едина. - * Возвращает позицию q, на которой сканер остановился, и обновляет стиль. */ -static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, - uint8_t ckind, uint8_t *io_line_style, uint8_t mode) +/* ================== Единый сканер сегмента ================================ + * Общий цикл inline-форматирования, переносов и (в join-режимах) склейки + * физических строк — бывшие inline_scan + scan_join_stream. mode: + * JOIN_MODE_NONE — сканировать РОВНО [q, bound) без склейки; '\n' в + * диапазоне не ожидается (вызывающие дают строку/ячейку до EOL); + * активны base_attr (title-attr заголовка) и nowrap (замер таблиц); + * ckind всегда CK_OTHER (continuation-префикс не эмитится). + * JOIN_MODE_LIST/QUOTE/PLAIN — правила '\n'-склейки; bound == file_size, + * base_attr == ATTR_TEXT (emph_to_attr(ls, ATTR_TEXT) тождественно + * бывшему styles_map[ls]), nowrap == 0. + * Возвращает позицию останова; итоговый стиль — через *io_line_style. */ +static uint32_t scan_stream(uint32_t q, uint32_t bound, uint32_t para_start, + uint8_t col, uint8_t ckind, uint8_t *io_line_style, + uint8_t base_attr, uint8_t nowrap, uint8_t mode) { uint32_t last_space = 0xFFFFFFFFu; uint8_t last_space_style = *io_line_style; /* снимок line_style на момент last_space */ char prev_ch = ' '; uint8_t seg_col = col; uint8_t line_style = *io_line_style; + /* base — атрибут «обычного» текста сегмента. Для заголовка первый сегмент + * имеет title-attr, а continuation-сегменты render рисует как PLAIN — + * переключаем base после 1-го переноса (в join-режимах base == ATTR_TEXT + * и присваивание ничего не меняет). */ + uint8_t base = base_attr; - while (q < file_size && n_lines < max_lines) { + while (q < bound && n_lines < max_lines) { char ch = fb(q); uint8_t soft_break = 0; if (ch == '\n') { uint32_t next = q + 1; - if (next >= file_size) break; + if (mode == JOIN_MODE_NONE) break; /* NONE: '\n' в [q,bound) не ожидается */ + if (next >= bound) break; if (mode == JOIN_MODE_LIST) { if (is_line_blank(next)) break; @@ -529,7 +481,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, break; } } - while (next < file_size) { + while (next < bound) { char ws = fb(next); if (ws == ' ' || ws == '\t') next++; else break; @@ -612,7 +564,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, q++; /* как render: пропускаем ведущие пробелы/табы строки-продолжения, * склейка даёт РОВНО один пробел (иначе merge эмитит отступ как лишние ячейки) */ - while (q < file_size && (fb(q) == ' ' || fb(q) == '\t')) q++; + while (q < bound && (fb(q) == ' ' || fb(q) == '\t')) q++; soft_break = 1; ch = ' '; } @@ -621,39 +573,39 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, /* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор; при * soft_break ch==' ' (синтетический пробел склейки) — маркеры * пропускаем, его кладёт ветка пробела ниже. */ - if (!soft_break && inline_marker(&q, file_size, &line_style, &seg_col, &prev_ch, ATTR_TEXT)) + if (!soft_break && inline_marker(&q, bound, &line_style, &seg_col, &prev_ch, base)) continue; if (ch == ' ') { - gc_put(' ', styles_map[line_style]); + gc_put(' ', emph_to_attr(line_style, base)); if (soft_break) { - last_space = q; - last_space_style = line_style; - seg_col++; + last_space = q; /* синтетический пробел склейки */ } else { last_space = q + 1; - last_space_style = line_style; - seg_col++; q++; } - g_ncells_at_space = g_ncells; /* снимок для усечения */ + last_space_style = line_style; + g_ncells_at_space = g_ncells; /* снимок для усечения */ + seg_col++; prev_ch = ' '; } else { - gc_put(ch, styles_map[line_style]); + gc_put(ch, emph_to_attr(line_style, base)); seg_col++; - q++; /* soft_break здесь всегда 0 (ch!=' ') */ + q++; /* soft_break здесь всегда 0 (ch != ' ') */ prev_ch = ch; } - if (seg_col >= SCREEN_W) { + if (!nowrap && seg_col >= SCREEN_W) { uint8_t use_ls = (uint8_t)(last_space != 0xFFFFFFFFu && last_space > cur_seg_off); uint32_t wrap_at = use_ls ? last_space : q; - if (wrap_at >= file_size || wrap_at == cur_seg_off) break; + if (wrap_at >= bound || wrap_at == cur_seg_off) break; uint8_t wrap_style = use_ls ? last_space_style : line_style; if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */ emit_seg(wrap_at, wrap_style, ckind, 1); + base = ATTR_TEXT; /* continuation заголовка → PLAIN (в join base и так ATTR_TEXT) */ /* Префикс continuation-сегмента: список и отступленный абзац (#4) — * col пробелов выравнивания; цитата — отступ + маркер 0xB3 + пробел. - * Для обычного абзаца без отступа col==0 → префикса нет. */ + * Для обычного абзаца без отступа col==0 → префикса нет; CK_OTHER + * (заголовки/таблицы, NONE-режим) — префикса нет. */ if (ckind == CK_LIST || ckind == CK_PLAIN) { gc_fill(' ', ATTR_TEXT, col); } else if (ckind == CK_QUOTE) { @@ -682,6 +634,25 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, return q; } +/* Тонкая обёртка прежнего API: скан ровно [q, q_end) без склейки строк. + * Вызывается для заголовков и ячеек таблиц (всегда CK_OTHER, col=0). */ +uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col, + uint8_t ckind, uint8_t line_style, uint8_t base_attr, + uint8_t nowrap) +{ + (void)scan_stream(q, q_end, q, col, ckind, &line_style, base_attr, nowrap, + JOIN_MODE_NONE); + return line_style; +} + +/* Потоковый сканер для index_lines(): mode задаёт правила '\n'-склейки. */ +static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, + uint8_t ckind, uint8_t *io_line_style, uint8_t mode) +{ + return scan_stream(q, file_size, para_start, col, ckind, io_line_style, + ATTR_TEXT, 0, mode); +} + /* Отрисовка таблиц (row_end/table_*) — в модуле mdview2_table.c. */ /* Строит индекс сегментов рендера по загруженному файлу. */