mdview2: фоновая сборка второго набора кодировки в паузах между клавишами; версия v1.0 (b3)
- индексатор порезан на резюмируемые шаги index_begin/index_step/index_finish; межшаговое состояние в статиках модуля, в docset_t не входит - bg_build_start/bg_step: второй набор (UTF-8 при 8-битном первичном и наоборот) строится в idle главного цикла; холдаун после клавиш, спиннер погашен (g_bg_building) — фон незаметен - F8 до готовности докручивает начатое фоном (ветка resume в build_doc), а не строит заново; общий setup вынесен в doc_setup - кодировка в статус-баре показывается сразу (детект/F8), не дожидаясь конца индексации - побочный фикс: UTF-конвертация впереди проверки останова — >4КБ абзац больше не обрывает конвертацию остатка - README.md (новый, v1.0 b3), CHANGELOG.md; дискета: README/DEMO/CHANGES в трёх кодировках (пути автодетекта) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* mdview2_index.c — парсер/индексатор markdown (сердце приложения).
|
||||
*
|
||||
* Единственный проход по исходному файлу (index_lines): классификация строк
|
||||
* Единственный проход по исходному файлу (index_begin/index_step): классификация строк
|
||||
* (classify_line, is_*_raw), сборка абзацев с переносом (scan_join_stream),
|
||||
* inline-разметка (inline_marker/inline_scan) и эмиссия готовых (char,attr)-
|
||||
* ячеек в рендер-кэш (gc_put → seg_commit_cells → cache_*). Ведёт индекс
|
||||
@@ -669,31 +669,88 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
||||
|
||||
/* Отрисовка таблиц (row_end/table_*) — в модуле mdview2_table.c. */
|
||||
|
||||
/* Строит индекс сегментов рендера по загруженному файлу. */
|
||||
void index_lines(void)
|
||||
/* ================== Резюмируемый проход индексации =======================
|
||||
* Бывший монолит index_lines() порезан на begin/step/finish, чтобы фоновая
|
||||
* сборка второго набора (bg_step ядра) могла выполнять индексацию порциями
|
||||
* в паузах между клавишами. Всё межшаговое состояние — в статиках модуля
|
||||
* (idx_p/idx_in_block ниже + g_cells/cur_rec/g_trunc_* выше); оно НЕ входит
|
||||
* в docset_t и переживает свапы наборов, потому что обработчики клавиш
|
||||
* видимого набора индексатор не трогают. */
|
||||
static uint32_t idx_p; /* позиция чтения исходника между шагами */
|
||||
static uint8_t idx_in_block; /* внутри fenced code-блока */
|
||||
static uint8_t idx_active; /* 1 = проход начат и ещё не финализирован */
|
||||
|
||||
/* Начинает новый проход (сброс позиции/буферов/причин обрыва). */
|
||||
void index_begin(void)
|
||||
{
|
||||
uint8_t in_block = 0;
|
||||
idx_p = 0;
|
||||
idx_in_block = 0;
|
||||
n_lines = 0;
|
||||
index_truncated = 0;
|
||||
g_trunc_cause = 0;
|
||||
g_trunc_line = 0;
|
||||
cur_seg_off = 0xFFFFFFFFu; /* пока не эмитили ни одного сегмента */
|
||||
if (file_size == 0) return;
|
||||
g_ncells = 0;
|
||||
g_ncells_at_space = 0;
|
||||
g_skip_flush = 0;
|
||||
idx_active = 1;
|
||||
}
|
||||
|
||||
uint32_t p = 0;
|
||||
while (p < file_size && n_lines < max_lines && !g_abort && !g_trunc_cause) {
|
||||
/* Хвост прохода: финальный флаш ячеек + строка-обрыв (бывший финал index_lines). */
|
||||
static void index_finish(void)
|
||||
{
|
||||
idx_active = 0;
|
||||
/* emit_seg() флашит ячейки с отставанием на одну строку (см. её
|
||||
* комментарий) — последняя эмитированная строка файла так и остаётся
|
||||
* незафлашенной, её ячейки ещё в g_cells; коммитим здесь. */
|
||||
if (n_lines > 0) seg_commit_cells((uint16_t)(n_lines - 1));
|
||||
|
||||
/* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead
|
||||
* склейки/классификации ~ один абзац). file_size при этом = доступная
|
||||
* сконвертированная часть и растёт по ходу. */
|
||||
if (g_utf_building && (uint32_t)(p + CONV_MARGIN) > file_size)
|
||||
utf_convert_more((uint32_t)(p + CONV_MARGIN));
|
||||
/* Лимит строк ловим ЗДЕСЬ, а не в emit_seg: главный цикл выходит по
|
||||
* `n_lines < max_lines` ДО того, как emit_seg будет вызван в переполненном
|
||||
* состоянии (для code-block — один emit_seg на строку, эта ветка не
|
||||
* достигается). Признак — остановились, а файл не кончился; g_abort и
|
||||
* исчерпание контента (TRUNC_CONTENT) уже обработаны/исключены. */
|
||||
if (!g_trunc_cause && !g_abort && idx_p < file_size) {
|
||||
g_trunc_cause = TRUNC_LINES;
|
||||
g_trunc_line = (uint16_t)(max_lines - 1);
|
||||
}
|
||||
|
||||
if (g_loading)
|
||||
progress_tick(); /* первый экран + статус + скролл по готовым данным */
|
||||
else if ((n_lines & 15) == 0)
|
||||
spinner_tick();
|
||||
/* Файл был обрезан до 256 КБ при загрузке, а индексация дошла до конца
|
||||
* клампнутых данных без исчерпания контента/строк → ДОПИСЫВАЕМ строку
|
||||
* (место есть: n_lines < max_lines). Приоритет ниже content/lines —
|
||||
* те означают, что обрезали ещё раньше, их сообщение точнее. */
|
||||
if (!g_trunc_cause && !g_abort && g_file_clamped) {
|
||||
g_trunc_cause = TRUNC_FILE;
|
||||
g_trunc_line = n_lines;
|
||||
}
|
||||
|
||||
/* Обрыв по контенту/лимиту строк (не путать с пользовательским g_abort):
|
||||
* документ заканчиваем строкой-сообщением на g_trunc_line. Сообщение
|
||||
* рисуется вживую (IF_TRUNC_MSG) — место в контент-кэше ему не нужно, что
|
||||
* и снимает тупик «кэш полон, а сообщение тоже надо закэшировать». */
|
||||
if (g_trunc_cause && !g_abort) {
|
||||
cache_rec_t r;
|
||||
r.page = 0; r.off = 0; r.len = 0;
|
||||
r.flags = IF_TRUNC_MSG; r.reserved = g_trunc_cause;
|
||||
r.pad[0] = 0; r.pad[1] = 0;
|
||||
cache_dir_put(g_trunc_line, &r);
|
||||
n_lines = (uint16_t)(g_trunc_line + 1); /* документ кончается на строке-обрыве */
|
||||
}
|
||||
}
|
||||
|
||||
/* Одна итерация бывшего главного цикла index_lines(): классифицирует и
|
||||
* эмитит очередной блок исходника (параграф/строку кода/таблицу целиком).
|
||||
* do{}while(0) сохраняет исходные `continue` тела как выход из итерации
|
||||
* с записью локальной p обратно в idx_p. */
|
||||
static void index_iter(void)
|
||||
{
|
||||
if (g_loading)
|
||||
progress_tick(); /* первый экран + статус + скролл по готовым данным */
|
||||
else if (!g_bg_building && (n_lines & 15) == 0)
|
||||
spinner_tick();
|
||||
|
||||
uint32_t p = idx_p;
|
||||
do {
|
||||
/* Пустая строка — разделитель параграфов.
|
||||
* В индекс добавляем не более одной пустой экранной строки подряд. */
|
||||
if (is_line_blank(p)) {
|
||||
@@ -708,13 +765,13 @@ void index_lines(void)
|
||||
|
||||
/* ---- fenced code-блок (граница или тело) ---- */
|
||||
if (is_fence_raw(p)) {
|
||||
in_block = !in_block;
|
||||
idx_in_block = !idx_in_block;
|
||||
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
|
||||
set_cur_flags(IF_NOWRAP);
|
||||
p = next_line(p);
|
||||
continue;
|
||||
}
|
||||
if (in_block) {
|
||||
if (idx_in_block) {
|
||||
emit_seg(p, INIT_STYLE_PLAIN, CK_OTHER, 0);
|
||||
set_cur_flags(IF_NOWRAP | IF_CODE | IF_HSCROLL); /* код скроллится целым блоком */
|
||||
{
|
||||
@@ -926,43 +983,32 @@ void index_lines(void)
|
||||
p = next_line(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* emit_seg() флашит ячейки с отставанием на одну строку (см. её
|
||||
* комментарий) — последняя эмитированная строка файла так и остаётся
|
||||
* незафлашенной, её ячейки ещё в g_cells; коммитим здесь. */
|
||||
if (n_lines > 0) seg_commit_cells((uint16_t)(n_lines - 1));
|
||||
} while (0);
|
||||
idx_p = p;
|
||||
}
|
||||
|
||||
/* Лимит строк ловим ЗДЕСЬ, а не в emit_seg: главный цикл выходит по
|
||||
* `n_lines < max_lines` ДО того, как emit_seg будет вызван в переполненном
|
||||
* состоянии (для code-block — один emit_seg на строку, эта ветка не
|
||||
* достигается). Признак — остановились, а файл не кончился; g_abort и
|
||||
* исчерпание контента (TRUNC_CONTENT) уже обработаны/исключены. */
|
||||
if (!g_trunc_cause && !g_abort && p < file_size) {
|
||||
g_trunc_cause = TRUNC_LINES;
|
||||
g_trunc_line = (uint16_t)(max_lines - 1);
|
||||
}
|
||||
/* Выполняет до budget итераций индексации. Возврат: 1 — проход завершён
|
||||
* (индекс финализирован index_finish), 0 — есть ещё работа.
|
||||
* Конвертация UTF-8 держится впереди позиции чтения ЗДЕСЬ, до проверки
|
||||
* условия останова: пока исходник не сконвертирован до конца, idx_p не
|
||||
* может «догнать» file_size и оборвать проход преждевременно. */
|
||||
uint8_t index_step(uint8_t budget)
|
||||
{
|
||||
if (!idx_active) return 1;
|
||||
while (budget-- > 0) {
|
||||
/* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead
|
||||
* склейки/классификации ~ один абзац). file_size при этом = доступная
|
||||
* сконвертированная часть и растёт по ходу. */
|
||||
if (g_utf_building && (uint32_t)(idx_p + CONV_MARGIN) > file_size)
|
||||
utf_convert_more((uint32_t)(idx_p + CONV_MARGIN));
|
||||
|
||||
/* Файл был обрезан до 256 КБ при загрузке, а индексация дошла до конца
|
||||
* клампнутых данных без исчерпания контента/строк → ДОПИСЫВАЕМ строку
|
||||
* (место есть: n_lines < max_lines). Приоритет ниже content/lines —
|
||||
* те означают, что обрезали ещё раньше, их сообщение точнее. */
|
||||
if (!g_trunc_cause && !g_abort && g_file_clamped) {
|
||||
g_trunc_cause = TRUNC_FILE;
|
||||
g_trunc_line = n_lines;
|
||||
}
|
||||
|
||||
/* Обрыв по контенту/лимиту строк (не путать с пользовательским g_abort):
|
||||
* документ заканчиваем строкой-сообщением на g_trunc_line. Сообщение
|
||||
* рисуется вживую (IF_TRUNC_MSG) — место в контент-кэше ему не нужно, что
|
||||
* и снимает тупик «кэш полон, а сообщение тоже надо закэшировать». */
|
||||
if (g_trunc_cause && !g_abort) {
|
||||
cache_rec_t r;
|
||||
r.page = 0; r.off = 0; r.len = 0;
|
||||
r.flags = IF_TRUNC_MSG; r.reserved = g_trunc_cause;
|
||||
r.pad[0] = 0; r.pad[1] = 0;
|
||||
cache_dir_put(g_trunc_line, &r);
|
||||
n_lines = (uint16_t)(g_trunc_line + 1); /* документ кончается на строке-обрыве */
|
||||
if (!(idx_p < file_size && n_lines < max_lines && !g_abort && !g_trunc_cause)) {
|
||||
index_finish();
|
||||
return 1;
|
||||
}
|
||||
index_iter();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#define EM_NONE 0
|
||||
#define EM_BOLD 1
|
||||
|
||||
Reference in New Issue
Block a user