mdview2: слить inline_scan + scan_join_stream в scan_stream — exe 25616 → 25100

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 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 21:26:13 +03:00
parent ffd179e064
commit adf667c087
+60 -89
View File
@@ -9,12 +9,12 @@
* позиции чтения. Разделяемые символы и API — в mdview2.h. * позиции чтения. Разделяемые символы и API — в mdview2.h.
* *
* Приватно (не торчит в .h): idx_rec_t, cur_rec/cur_seg_off, буфер ячеек * Приватно (не торчит в .h): idx_rec_t, cur_rec/cur_seg_off, буфер ячеек
* g_cells, styles_map, idx_get/idx_put и все классификаторы/сканеры. * g_cells, idx_get/idx_put и все классификаторы/сканеры.
*/ */
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <conio.h> /* COLOR()/COLOR_* для ATTR_* в styles_map */ #include <conio.h> /* COLOR()/COLOR_* для ATTR_* в сканерах */
#include <sprinter.h> #include <sprinter.h>
#include <sprinter_mem.h> /* bank_read/bank_write (idx_get/idx_put) */ #include <sprinter_mem.h> /* bank_read/bank_write (idx_get/idx_put) */
#include "mdview2.h" #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_* — в 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(). */ /* Тип строки, который возвращает classify_line(). */
#define LK_PLAIN 0 #define LK_PLAIN 0
#define LK_H1 1 #define LK_H1 1
@@ -373,7 +367,7 @@ static uint8_t is_table_raw(uint32_t p)
* маркер обработан (вызывающий делает continue), 0 — символ обычный (пробел/ * маркер обработан (вызывающий делает continue), 0 — символ обычный (пробел/
* текст), его кладёт сам вызывающий (и проверяет перенос). Меняет * текст), его кладёт сам вызывающий (и проверяет перенос). Меняет
* *pq/*pls/*pseg/*pprev. attr берётся как emph_to_attr(line_style, base) — * *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, static uint8_t inline_marker(uint32_t *pq, uint32_t bound, uint8_t *pls,
uint8_t *pseg, char *pprev, uint8_t base) 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; /* пробел или обычный символ — обрабатывает вызывающий */ return 0; /* пробел или обычный символ — обрабатывает вызывающий */
} }
/* Общий сканер inline-форматирования и переносов. #define JOIN_MODE_NONE 0u
* Идёт от 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_LIST 1u #define JOIN_MODE_LIST 1u
#define JOIN_MODE_QUOTE 2u #define JOIN_MODE_QUOTE 2u
#define JOIN_MODE_PLAIN 3u #define JOIN_MODE_PLAIN 3u
/* Общий потоковый сканер для index_lines(). /* ================== Единый сканер сегмента ================================
* Убирает дублирование между ветками list/quote/plain: * Общий цикл inline-форматирования, переносов и (в join-режимах) склейки
* - mode задаёт правила обработки перевода строки, * физических строк — бывшие inline_scan + scan_join_stream. mode:
* - остальная inline-логика (emphasis/таб/перенос) едина. * JOIN_MODE_NONE — сканировать РОВНО [q, bound) без склейки; '\n' в
* Возвращает позицию q, на которой сканер остановился, и обновляет стиль. */ * диапазоне не ожидается (вызывающие дают строку/ячейку до EOL);
static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col, * активны base_attr (title-attr заголовка) и nowrap (замер таблиц);
uint8_t ckind, uint8_t *io_line_style, uint8_t mode) * 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; uint32_t last_space = 0xFFFFFFFFu;
uint8_t last_space_style = *io_line_style; /* снимок line_style на момент last_space */ uint8_t last_space_style = *io_line_style; /* снимок line_style на момент last_space */
char prev_ch = ' '; char prev_ch = ' ';
uint8_t seg_col = col; uint8_t seg_col = col;
uint8_t line_style = *io_line_style; 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); char ch = fb(q);
uint8_t soft_break = 0; uint8_t soft_break = 0;
if (ch == '\n') { if (ch == '\n') {
uint32_t next = q + 1; 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 (mode == JOIN_MODE_LIST) {
if (is_line_blank(next)) break; 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; break;
} }
} }
while (next < file_size) { while (next < bound) {
char ws = fb(next); char ws = fb(next);
if (ws == ' ' || ws == '\t') next++; if (ws == ' ' || ws == '\t') next++;
else break; else break;
@@ -612,7 +564,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
q++; q++;
/* как render: пропускаем ведущие пробелы/табы строки-продолжения, /* как render: пропускаем ведущие пробелы/табы строки-продолжения,
* склейка даёт РОВНО один пробел (иначе merge эмитит отступ как лишние ячейки) */ * склейка даёт РОВНО один пробел (иначе merge эмитит отступ как лишние ячейки) */
while (q < file_size && (fb(q) == ' ' || fb(q) == '\t')) q++; while (q < bound && (fb(q) == ' ' || fb(q) == '\t')) q++;
soft_break = 1; soft_break = 1;
ch = ' '; 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) — общий разбор; при /* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор; при
* soft_break ch==' ' (синтетический пробел склейки) — маркеры * 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; continue;
if (ch == ' ') { if (ch == ' ') {
gc_put(' ', styles_map[line_style]); gc_put(' ', emph_to_attr(line_style, base));
if (soft_break) { if (soft_break) {
last_space = q; last_space = q; /* синтетический пробел склейки */
last_space_style = line_style;
seg_col++;
} else { } else {
last_space = q + 1; last_space = q + 1;
last_space_style = line_style;
seg_col++;
q++; q++;
} }
g_ncells_at_space = g_ncells; /* снимок для усечения */ last_space_style = line_style;
g_ncells_at_space = g_ncells; /* снимок для усечения */
seg_col++;
prev_ch = ' '; prev_ch = ' ';
} else { } else {
gc_put(ch, styles_map[line_style]); gc_put(ch, emph_to_attr(line_style, base));
seg_col++; seg_col++;
q++; /* soft_break здесь всегда 0 (ch!=' ') */ q++; /* soft_break здесь всегда 0 (ch != ' ') */
prev_ch = 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); uint8_t use_ls = (uint8_t)(last_space != 0xFFFFFFFFu && last_space > cur_seg_off);
uint32_t wrap_at = use_ls ? last_space : q; 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; uint8_t wrap_style = use_ls ? last_space_style : line_style;
if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */ if (use_ls) g_ncells = g_ncells_at_space; /* усечь до пробела */
emit_seg(wrap_at, wrap_style, ckind, 1); emit_seg(wrap_at, wrap_style, ckind, 1);
base = ATTR_TEXT; /* continuation заголовка → PLAIN (в join base и так ATTR_TEXT) */
/* Префикс continuation-сегмента: список и отступленный абзац (#4) — /* Префикс continuation-сегмента: список и отступленный абзац (#4) —
* col пробелов выравнивания; цитата — отступ + маркер 0xB3 + пробел. * col пробелов выравнивания; цитата — отступ + маркер 0xB3 + пробел.
* Для обычного абзаца без отступа col==0 → префикса нет. */ * Для обычного абзаца без отступа col==0 → префикса нет; CK_OTHER
* (заголовки/таблицы, NONE-режим) — префикса нет. */
if (ckind == CK_LIST || ckind == CK_PLAIN) { if (ckind == CK_LIST || ckind == CK_PLAIN) {
gc_fill(' ', ATTR_TEXT, col); gc_fill(' ', ATTR_TEXT, col);
} else if (ckind == CK_QUOTE) { } 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; 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. */ /* Отрисовка таблиц (row_end/table_*) — в модуле mdview2_table.c. */
/* Строит индекс сегментов рендера по загруженному файлу. */ /* Строит индекс сегментов рендера по загруженному файлу. */