mdview2: лимит файла 256 КБ, обрыв с сообщением вместо потери строк

- MAX_PAGES 8->16 (256 КБ), MAX_INDEX_PAGES/MAX_CACHE_DIR_PAGES 8->9
  (18432 строк), кап контент-кэша 64->40 стр./набор (80 на оба).
  Бюджет worst-case (UTF-8 Latin+BOM): ~150 из 215 EMM-страниц.
- При исчерпании контент-кэша (cache_reserve==0) или лимита строк
  индексация обрывается и последняя строка заменяется предупреждением
  (IF_TRUNC_MSG, рисуется вживую с ATTR_WARN — жёлтый по красному),
  без хвоста пустых строк. Раньше переполнение молча давало len=0
  (пустые строки) без какого-либо индикатора.
- help: лимиты обновлены (256 КБ / 18432 строк).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 20:48:35 +03:00
parent 733746572c
commit bc3483c2dd
5 changed files with 55 additions and 7 deletions
+3 -3
View File
@@ -70,7 +70,7 @@ static const uint8_t md_pallete[16 * 4] = {
* неинициализированные static-объекты могут конфликтовать по размещению * неинициализированные static-объекты могут конфликтовать по размещению
* и перезаписывать друг друга (см. memory/sdcc_static_storage_gotcha). */ * и перезаписывать друг друга (см. memory/sdcc_static_storage_gotcha). */
#define MAX_INDEX_PAGES 8u /* максимум 8 * 2048 = 16384 сегментов индекса */ #define MAX_INDEX_PAGES 9u /* максимум 9 * 2048 = 18432 сегментов индекса (256 КБ) */
/* ================================================================== /* ==================================================================
* Рендер-кэш: директория и пул EMM-страниц * Рендер-кэш: директория и пул EMM-страниц
@@ -97,9 +97,9 @@ static const uint8_t md_pallete[16 * 4] = {
#define CACHE_DIR_REC_SIZE ((uint16_t)sizeof(cache_rec_t)) #define CACHE_DIR_REC_SIZE ((uint16_t)sizeof(cache_rec_t))
#define CACHE_DIR_RECS_PER_PAGE (16384 / CACHE_DIR_REC_SIZE) /* 2048 */ #define CACHE_DIR_RECS_PER_PAGE (16384 / CACHE_DIR_REC_SIZE) /* 2048 */
#define MAX_CACHE_DIR_PAGES 8u #define MAX_CACHE_DIR_PAGES 9u /* симметрично индексу: 9 * 2048 = 18432 записей */
/* MAX_CACHE_LINE_LEN — в mdview2.h (буфер ячеек g_cells живёт в mdview2_index.c). */ /* MAX_CACHE_LINE_LEN — в mdview2.h (буфер ячеек g_cells живёт в mdview2_index.c). */
#define MAX_CACHE_CONTENT_PAGES 64u /* запас: до 1 МБ кэша контента */ #define MAX_CACHE_CONTENT_PAGES 40u /* кап контента на набор: 40 * 16 КБ = 640 КБ (80 на оба набора) */
static uint8_t cache_dir_blk = 0; static uint8_t cache_dir_blk = 0;
static uint8_t cache_dir_pages = 0; static uint8_t cache_dir_pages = 0;
+8 -1
View File
@@ -32,7 +32,7 @@
#define PAGE_BITS 14u #define PAGE_BITS 14u
#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */ #define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u)) #define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
#define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */ #define MAX_PAGES 16 /* 16 страниц × 16 КБ = 256 КБ */
#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE) #define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE)
#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */ #define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
@@ -57,6 +57,7 @@
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN) #define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN) #define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK) #define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK)
#define ATTR_WARN COLOR(COLOR_YELLOW, COLOR_RED) /* строка-обрыв (исчерпан кэш/лимит) */
/* ---- Атрибуты диалога справки ------------------------------------ */ /* ---- Атрибуты диалога справки ------------------------------------ */
#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK) #define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
@@ -87,6 +88,12 @@
#define IF_HSCROLL 0x10u /* блок горизонтально скроллируется ЦЕЛИКОМ (код/таблица), #define IF_HSCROLL 0x10u /* блок горизонтально скроллируется ЦЕЛИКОМ (код/таблица),
* включая строки короче 80 — двигаются как единый блок. * включая строки короче 80 — двигаются как единый блок.
* НЕ ставится на HR и границы fence (им нечего прятать). */ * НЕ ставится на HR и границы fence (им нечего прятать). */
#define IF_TRUNC_MSG 0x20u /* строка-обрыв: рисуется ВЖИВУЮ фикс. текстом (без контент-кэша);
* cache_rec.reserved несёт код причины (TRUNC_*). */
/* ---- Причина обрыва индексации (cache_rec.reserved при IF_TRUNC_MSG) ---- */
#define TRUNC_CONTENT 1 /* исчерпан контент-кэш (MAX_CACHE_CONTENT_PAGES) */
#define TRUNC_LINES 2 /* исчерпана ёмкость индекса (max_lines) */
/* ---- Словарь сегментов/стилей (общий для индексатора и таблиц) ---- */ /* ---- Словарь сегментов/стилей (общий для индексатора и таблиц) ---- */
/* Тип сегмента-продолжения, передаваемый в emit_seg()/inline_scan(). */ /* Тип сегмента-продолжения, передаваемый в emit_seg()/inline_scan(). */
+1 -1
View File
@@ -95,7 +95,7 @@ void show_help(void)
help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG); help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG);
help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG); help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG);
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
help_line(r++, " File size: up to 128 KB (EMM). Lines: up to 16384.", ATTR_HELP_BG); help_line(r++, " File size: up to 256 KB (EMM). Lines: up to 18432.", ATTR_HELP_BG);
help_line(r++, "", ATTR_HELP_BG); help_line(r++, "", ATTR_HELP_BG);
(void)getkey(); (void)getkey();
+30 -2
View File
@@ -241,6 +241,12 @@ uint8_t g_ncells = 0; /* ячеек в текущем сег
static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */ static uint8_t g_ncells_at_space = 0; /* снимок g_ncells на последнем пробеле (для усечения при переносе) */
static uint8_t g_skip_flush = 0; /* следующий emit_seg пропустит lag-флаш (pending уже зафлашен вручную, напр. перед таблицей) */ static uint8_t g_skip_flush = 0; /* следующий emit_seg пропустит lag-флаш (pending уже зафлашен вручную, напр. перед таблицей) */
/* Обрыв индексации: причина (0 / TRUNC_CONTENT / TRUNC_LINES) и строка, на
* которой упёрлись. Выставляются ОДИН раз (первая причина побеждает); по ним
* index_lines() в конце пишет строку-сообщение (IF_TRUNC_MSG, рисуется вживую). */
static uint8_t g_trunc_cause = 0;
static uint16_t g_trunc_line = 0;
/* Пишет одну (char,attr)-ячейку в g_cells, молча игнорируя переполнение /* Пишет одну (char,attr)-ячейку в g_cells, молча игнорируя переполнение
* MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно * MAX_CACHE_LINE_LEN. Оставлена ФУНКЦИЕЙ намеренно: инлайн в сильно
* регистро-нагруженный скан-цикл на Z80/SDCC увеличивает спиллы и замедляет * регистро-нагруженный скан-цикл на Z80/SDCC увеличивает спиллы и замедляет
@@ -274,6 +280,9 @@ static void seg_commit_cells(uint16_t line_idx)
if (cache_reserve((uint16_t)(g_ncells * 2u), &out_rec.page, &out_rec.off)) { if (cache_reserve((uint16_t)(g_ncells * 2u), &out_rec.page, &out_rec.off)) {
cache_commit(out_rec.page, out_rec.off, g_cells, (uint16_t)(g_ncells * 2u)); cache_commit(out_rec.page, out_rec.off, g_cells, (uint16_t)(g_ncells * 2u));
} else { } else {
/* Контент-кэш исчерпан: фиксируем причину и строку обрыва (один раз).
* index_lines() оборвёт цикл и перезапишет эту строку сообщением. */
if (!g_trunc_cause) { g_trunc_cause = TRUNC_CONTENT; g_trunc_line = line_idx; }
out_rec.page = 0; out_rec.off = 0; out_rec.len = 0; out_rec.page = 0; out_rec.off = 0; out_rec.len = 0;
} }
cache_dir_put(line_idx, &out_rec); cache_dir_put(line_idx, &out_rec);
@@ -283,7 +292,11 @@ static void seg_commit_cells(uint16_t line_idx)
static void emit_seg(uint32_t off, uint8_t style, uint8_t ckind, uint8_t cont) static void emit_seg(uint32_t off, uint8_t style, uint8_t ckind, uint8_t cont)
{ {
(void)ckind; /* тип сегмента восстанавливается флагами idx-записи */ (void)ckind; /* тип сегмента восстанавливается флагами idx-записи */
if (n_lines >= max_lines) { index_truncated = 1; return; } if (n_lines >= max_lines) {
index_truncated = 1;
if (!g_trunc_cause) { g_trunc_cause = TRUNC_LINES; g_trunc_line = (uint16_t)(max_lines - 1); }
return;
}
cur_rec.off = off; cur_rec.off = off;
cur_rec.flags = (uint8_t)(cont ? IF_CONT : 0); cur_rec.flags = (uint8_t)(cont ? IF_CONT : 0);
cur_rec.style = (uint8_t)(style & 7u); cur_rec.style = (uint8_t)(style & 7u);
@@ -687,11 +700,13 @@ void index_lines(void)
uint8_t in_block = 0; uint8_t in_block = 0;
n_lines = 0; n_lines = 0;
index_truncated = 0; index_truncated = 0;
g_trunc_cause = 0;
g_trunc_line = 0;
cur_seg_off = 0xFFFFFFFFu; /* пока не эмитили ни одного сегмента */ cur_seg_off = 0xFFFFFFFFu; /* пока не эмитили ни одного сегмента */
if (file_size == 0) return; if (file_size == 0) return;
uint32_t p = 0; uint32_t p = 0;
while (p < file_size && n_lines < max_lines && !g_abort) { while (p < file_size && n_lines < max_lines && !g_abort && !g_trunc_cause) {
/* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead /* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead
* склейки/классификации ~ один абзац). file_size при этом = доступная * склейки/классификации ~ один абзац). file_size при этом = доступная
@@ -957,6 +972,19 @@ void index_lines(void)
* комментарий) — последняя эмитированная строка файла так и остаётся * комментарий) — последняя эмитированная строка файла так и остаётся
* незафлашенной, её ячейки ещё в g_cells; коммитим здесь. */ * незафлашенной, её ячейки ещё в g_cells; коммитим здесь. */
if (n_lines > 0) seg_commit_cells((uint16_t)(n_lines - 1)); if (n_lines > 0) seg_commit_cells((uint16_t)(n_lines - 1));
/* Обрыв по контенту/лимиту строк (не путать с пользовательским 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); /* документ кончается на строке-обрыве */
}
} }
#define EM_NONE 0 #define EM_NONE 0
#define EM_BOLD 1 #define EM_BOLD 1
+13
View File
@@ -28,6 +28,19 @@ static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
cache_rec_t rec; cache_rec_t rec;
cache_dir_get(line_idx, &rec); cache_dir_get(line_idx, &rec);
/* Строка-обрыв (исчерпан контент-кэш / лимит строк): фикс. текст вживую,
* без обращения к контент-кэшу (см. index_lines финализацию). */
if (rec.flags & IF_TRUNC_MSG) {
bios_set_place(row, 0);
bios_fillcharattr(' ', ATTR_WARN, SCREEN_W);
put_str_attr(2, row,
(rec.reserved == TRUNC_LINES)
? "*** Line limit reached - document truncated ***"
: "*** Content cache exhausted - document truncated ***",
ATTR_WARN);
return;
}
bios_set_place(row, 0); bios_set_place(row, 0);
bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W); bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W);