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
+58 -87
View File
@@ -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 <stdint.h>
#include <string.h>
#include <conio.h> /* COLOR()/COLOR_* для ATTR_* в styles_map */
#include <conio.h> /* COLOR()/COLOR_* для ATTR_* в сканерах */
#include <sprinter.h>
#include <sprinter_mem.h> /* 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++;
}
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 != ' ') */
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. */
/* Строит индекс сегментов рендера по загруженному файлу. */