bc3483c2dd
- 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>
226 lines
10 KiB
C
226 lines
10 KiB
C
/*
|
||
* mdview2_md.c — представление MD-документа (peer к mdview2_raw.c): отрисовка
|
||
* области из рендер-кэша + прокрутка (верт./гориз.) + навигация md_key().
|
||
* Статус-бар, меню и спиннер — в mdview2_status.c (общие для MD/RAW).
|
||
*
|
||
* Работает поверх готового рендер-кэша (cache_dir_get → cache_rec_t) и
|
||
* состояния навигации (top_line/viewport_x/n_lines, см. mdview2.h). Никакого
|
||
* парсинга/обращения к исходному файлу: всё рисуется из (char,attr)-ячеек кэша
|
||
* через win_rest_remap (ремап 8-битных кодировок — в mdview2_enc.c).
|
||
*
|
||
* Пара видов: mdview2_md.c (MD, из кэша) ↔ mdview2_raw.c (RAW, из fb()).
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include <conio.h> /* wrchar/scroll/COLOR */
|
||
#include <bios/text.h> /* bios_set_place/bios_fillcharattr */
|
||
#include "mdview2.h"
|
||
|
||
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
|
||
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
|
||
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
|
||
* байт ВНУТРИ ТОГО ЖЕ кэш-буфера (cache_reserve гарантирует, что вся
|
||
* строка лежит в одной странице, так что off+vx*2 не пересекает
|
||
* границу страницы); не nowrap-строки игнорируют viewport_x, как и
|
||
* раньше в живом render_line() (там это называлось effective_vx). */
|
||
static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
|
||
{
|
||
cache_rec_t 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_fillcharattr(' ', ATTR_TEXT, SCREEN_W);
|
||
|
||
/* Горизонтальный сдвиг применяется ко ВСЕМ строкам hscroll-блока (код,
|
||
* таблицы) — по ТИПУ, а не по длине: блок двигается целиком, включая
|
||
* строки короче 80 (короткая строка, ушедшая за левый край, просто
|
||
* показывает маркер '<' на пустом месте). HR и границы fence не имеют
|
||
* IF_HSCROLL и не сдвигаются. */
|
||
uint8_t vx = (rec.flags & IF_HSCROLL) ? viewport_x : 0;
|
||
|
||
if (rec.len > vx) {
|
||
uint16_t remaining = (uint16_t)(rec.len - vx);
|
||
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
|
||
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
|
||
win_rest_remap(row, w, rec.page, off);
|
||
if (remaining > SCREEN_W) {
|
||
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
||
}
|
||
}
|
||
if (vx > 0) {
|
||
wrchar(0, row, '<', ATTR_TRUNC);
|
||
}
|
||
}
|
||
|
||
/* Перерисовывает все VIEW_H строк окна из кэша — основной путь
|
||
* перерисовки видимой области после загрузки файла (Фаза 3-4). */
|
||
void draw_viewport_from_cache(void)
|
||
{
|
||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||
draw_line_from_cache((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
|
||
}
|
||
}
|
||
|
||
|
||
/* ==================================================================
|
||
* Прокрутка
|
||
* ================================================================== */
|
||
|
||
/* Ограничивает top_line допустимым диапазоном с учётом высоты окна. */
|
||
void clamp_top(void)
|
||
{
|
||
if (n_lines <= VIEW_H) {
|
||
top_line = 0;
|
||
} else if (top_line > n_lines - VIEW_H) {
|
||
top_line = (uint16_t)(n_lines - VIEW_H);
|
||
}
|
||
}
|
||
|
||
/* Прокрутка вверх на n строк с частичной перерисовкой при n == 1. */
|
||
void md_scroll_up(uint16_t n)
|
||
{
|
||
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
|
||
if( new_top_line != top_line) {
|
||
top_line = new_top_line;
|
||
if (n == 1) {
|
||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);
|
||
draw_line_from_cache((uint16_t)(top_line), (uint8_t)(VIEW_TOP_ROW));
|
||
} else {
|
||
draw_viewport_from_cache();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Прокрутка вниз на n строк с частичной перерисовкой при n == 1. */
|
||
void md_scroll_down(uint16_t n)
|
||
{
|
||
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
|
||
(uint16_t)(top_line + n) : n_lines - VIEW_H;
|
||
if( new_top_line != top_line) {
|
||
top_line = new_top_line;
|
||
if (n == 1) {
|
||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);
|
||
draw_line_from_cache((uint16_t)(top_line + VIEW_H - 1), (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||
} else {
|
||
clamp_top();
|
||
draw_viewport_from_cache();
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Горизонтальный сдвиг (только если в окне есть nowrap-строки).
|
||
* Максимум сдвига ограничен самой широкой nowrap-строкой на экране.
|
||
* Фаза 5: ширина берётся прямо из директории кэша (rec.len — уже
|
||
* посчитанная реальная длина в ячейках после рендера), без единого
|
||
* обращения к fb()/seg_off()/исходному файлу. */
|
||
void md_scroll_horizon(int8_t delta)
|
||
{
|
||
uint16_t maxw = 0;
|
||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||
uint16_t li = (uint16_t)(top_line + i);
|
||
if (li >= n_lines) break;
|
||
cache_rec_t rec;
|
||
cache_dir_get(li, &rec);
|
||
if (!(rec.flags & IF_HSCROLL)) continue; /* только код/таблицы */
|
||
if (rec.len > maxw) maxw = rec.len; /* самая широкая строка блока */
|
||
}
|
||
if (maxw == 0) return; /* нет строк шире экрана */
|
||
|
||
/* Максимальный сдвиг = ширина за пределами экрана, в границах uint8. */
|
||
uint16_t over = (maxw > SCREEN_W) ? (uint16_t)(maxw - SCREEN_W) : 0;
|
||
if (over > 248u) over = 248u;
|
||
uint8_t max_vx = (uint8_t)over;
|
||
|
||
int16_t nx = (int16_t)viewport_x + delta;
|
||
if (nx < 0) nx = 0;
|
||
if (nx > (int16_t)max_vx) nx = max_vx;
|
||
|
||
/* ПОРЯДОК ВАЖЕН: записываем viewport_x ДО сравнения, а сравниваем уже
|
||
* сохранённую копию old_vx с new_vx. Иначе SDCC генерирует для
|
||
* `if (new_vx != viewport_x) viewport_x = new_vx;` такой код:
|
||
* ld a, new_vx ; sub a,(viewport_x) ; jr Z,.. ; ld (viewport_x),a
|
||
* — т.е. для записи переиспользует регистр A, испорченный вычитанием в
|
||
* сравнении, и кладёт в viewport_x не new_vx, а (new_vx - old_vx).
|
||
* (asm-дамп 2026-06-24 подтвердил; даёт 10-8=2, 10-2=8 — ровно
|
||
* наблюдавшийся "прыжок" скролла.) Записав viewport_x первой, мы убираем
|
||
* портящий sub с пути записи; сравнение ниже только решает, перерисовывать
|
||
* ли экран. */
|
||
uint8_t new_vx = (uint8_t)nx;
|
||
uint8_t old_vx = viewport_x;
|
||
viewport_x = new_vx;
|
||
if (new_vx != old_vx)
|
||
draw_viewport_from_cache();
|
||
}
|
||
|
||
/* Процент прокрутки (0..100) относительно доступного диапазона. */
|
||
static uint8_t calc_md_pct(void)
|
||
{
|
||
uint16_t total = drawable_lines();
|
||
if (total <= VIEW_H) return 100;
|
||
return (uint8_t)((uint32_t)top_line * 100UL / (uint32_t)(total - VIEW_H));
|
||
}
|
||
|
||
/* ---- навигация MD после загрузки (peer к raw_key) ---------------- */
|
||
|
||
/* Обрабатывает навигационную клавишу в MD-режиме (вызывается из главного
|
||
* цикла). Сама перерисовывает область/статус. Возврат: 1 — обработано,
|
||
* 0 — не наша клавиша (F1/F8/F10 разбирает main). */
|
||
uint8_t md_key(uint8_t scan)
|
||
{
|
||
switch (scan) {
|
||
case KEY_UP:
|
||
md_scroll_up(1);
|
||
break;
|
||
case KEY_DOWN:
|
||
md_scroll_down(1);
|
||
break;
|
||
case KEY_LEFT:
|
||
md_scroll_horizon(-(int8_t)HPAN_STEP);
|
||
break;
|
||
case KEY_RIGHT:
|
||
md_scroll_horizon(+(int8_t)HPAN_STEP);
|
||
break;
|
||
case KEY_PGUP:
|
||
md_scroll_up(VIEW_H);
|
||
break;
|
||
case KEY_PGDN:
|
||
md_scroll_down(VIEW_H);
|
||
break;
|
||
case KEY_HOME:
|
||
if(top_line != 0 || viewport_x != 0 ) {
|
||
top_line = 0;
|
||
viewport_x = 0;
|
||
draw_viewport_from_cache();
|
||
}
|
||
break;
|
||
case KEY_END:
|
||
uint16_t new_top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0;
|
||
if(top_line != new_top_line || viewport_x != 0 ) {
|
||
top_line = new_top_line;
|
||
viewport_x = 0;
|
||
draw_viewport_from_cache();
|
||
}
|
||
break;
|
||
|
||
default:
|
||
return 0; /* не наша клавиша */
|
||
}
|
||
|
||
render_percent_progress(calc_md_pct());
|
||
render_md_status_numbers();
|
||
return 1;
|
||
}
|