From 68d5be6e47737f70480180bf3fdf510cd94d6e46 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Thu, 25 Jun 2026 23:31:54 +0300 Subject: [PATCH] =?UTF-8?q?mdview2:=20RAW-=D0=BF=D1=80=D0=BE=D1=81=D0=BC?= =?UTF-8?q?=D0=BE=D1=82=D1=80=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=20(F2/F3)=20+=20=D0=B8=D0=BD=D0=BA=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20?= =?UTF-8?q?UTF-8=20=D0=BA=D0=BE=D0=BD=D0=B2=D0=B5=D1=80=D1=82=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новый модуль mdview2_raw.c (+ mdview2.h с общими константами/атрибутами и extern'ами) — первый шаг разбиения монолита. Опционален через mdview2_conf.h (#define WITH_RAW): при 0 — пустой объектник, нулевой расход (проверено: размер как до RAW). RAW-просмотр (без markdown-форматирования): - работает по активному буферу (8-бит как есть / UTF-8 декодированный), 1 байт = 1 ячейка, \t→пробел, ремап CP1251/KOI8 на отрисовке; - два под-режима: wrap (перенос кратно 80) и hscroll (одна строка + ←/→); - прокрутка на 1 строку через аппаратный scroll + отрисовка одной строки; вывод char-буфером (bios_write_until по фону ATTR_TEXT), без win_rest/scratch; - индекс/кэш markdown не используются, 0 доп. EMM. Клавиши/меню: - F2 — тумблер RAW↔MD (запоминает под-режим RAW); - F3 — Wrap/Unwrap (только в RAW), метка показывает целевой режим; - меню перестроено: блоки по 8 кол (col i*8), номера всех 10 клавиш без 'F' (' 1'..' 9','10'), текст-функция 6 симв. сразу за номером и только когда функция доступна; F8 сокращён до CodePg. Инкрементальная UTF-8→CP866 конвертация: вместо полного прохода перед индексацией — чанками впереди позиции чтения (CONV_MARGIN), первый экран появляется быстро. Конвертер читает оригинал через cv_read (своя W3-страница), index_lines докручивает конвертацию; progress_tick рисует по текущему виду (MD/RAW), без мелькания чужого вида. Co-Authored-By: Claude Opus 4.8 --- examples/mdview2/Makefile | 1 + examples/mdview2/mdview2.c | 300 +++++++++++++++++++--------- examples/mdview2/mdview2.h | 88 +++++++++ examples/mdview2/mdview2_conf.h | 16 ++ examples/mdview2/mdview2_raw.c | 334 ++++++++++++++++++++++++++++++++ 5 files changed, 643 insertions(+), 96 deletions(-) create mode 100644 examples/mdview2/mdview2.h create mode 100644 examples/mdview2/mdview2_conf.h create mode 100644 examples/mdview2/mdview2_raw.c diff --git a/examples/mdview2/Makefile b/examples/mdview2/Makefile index 2bf0769..d119cf1 100644 --- a/examples/mdview2/Makefile +++ b/examples/mdview2/Makefile @@ -8,6 +8,7 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..) EXAMPLE := mdview2 +EXTRA_SRCS := mdview2_raw.c MEMORY := small include $(PROJ_ROOT)/app.mk diff --git a/examples/mdview2/mdview2.c b/examples/mdview2/mdview2.c index 0211294..0d2f881 100644 --- a/examples/mdview2/mdview2.c +++ b/examples/mdview2/mdview2.c @@ -30,25 +30,7 @@ #include #include #include - -/* ---- Геометрия экрана -------------------------------------------- */ - -#define SCREEN_W 80 -#define SCREEN_H 32 -#define VIEW_TOP_ROW 1 -#define VIEW_H 30 /* видимая область: строки 1..30 включительно */ -#define MENU_ROW 31 -#define TAB_STOP 4 - -/* ---- Параметры файла и памяти ------------------------------------ */ - -#define PAGE_BITS 14u -#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */ -#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u)) -#define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */ -#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE) /* максимальный размер файла: 131072 байт */ -// #define MAX_LINES 2048 -#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */ +#include "mdview2.h" /* геометрия, атрибуты, extern для модуля RAW */ /* ---- Палитра атрибутов ------------------------------------------- */ @@ -71,33 +53,7 @@ static const uint8_t md_pallete[16 * 4] = { 0xFF, 0xFF, 0xFF, 0xFF }; -#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK) -#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE) -#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE) -#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE) -#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE) -#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE) -#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE) -#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE) -#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE) -#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE) -#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE) -#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE) -#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE) -#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE) -#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE) /* рамка таблиц; отдельный (не контентный) attr — чтобы ремап кодировок не трогал box-глифы */ -#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE) -#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN) -#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN) -#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN) -#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK) - -/* ---- Атрибуты диалога справки ------------------------------------ */ - -#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK) -#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK) -#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK) -#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK) +/* Атрибуты экрана/справки и геометрия — в mdview2.h (общие с модулем RAW). */ #define ATTR_HELP_HINT COLOR(COLOR_YELLOW, COLOR_BLACK) /* Геометрия диалога справки (в символьных координатах 80×32). */ @@ -269,7 +225,7 @@ static uint8_t emph_to_attr(uint8_t emph, uint8_t base_attr); static uint16_t n_lines = 0; static uint16_t max_lines = 0; /* ёмкость индекса: index_pages * 2048 */ static uint16_t top_line = 0; -static uint32_t file_size = 0; +uint32_t file_size = 0; static uint8_t file_blk = 0; static uint8_t file_pages = 0; static uint8_t file_phys[MAX_PAGES] = {0}; @@ -285,6 +241,11 @@ static char filename[64] = {0}; /* Прогрессивная загрузка: рисуем первый экран и пускаем скролл, пока * index_lines() ещё досканирует файл (кооперативно, без прерываний). */ +#if WITH_RAW +uint8_t g_view = VIEW_MD; /* режим просмотра (общий с mdview2_raw.c) */ +static uint8_t g_ready = 0; /* 1 когда первичный документ построен — F2 (RAW) рабочий */ +static uint8_t g_raw_last = VIEW_RAW_WRAP; /* последний под-режим RAW (возврат по F2) */ +#endif static uint8_t g_loading = 0; /* 1 во время index_lines() */ static uint8_t g_first_drawn = 0; /* первый экран уже нарисован */ static uint8_t g_f8_enabled = 0; /* можно ли сейчас переключать кодировку (метка F8 в меню + F8 в load_key) */ @@ -300,8 +261,8 @@ static void progress_tick(void); /* определена ниже (нужн #define ENC_KOI8R 2 #define ENC_UTF8 3 static uint8_t g_encoding = ENC_CP866; /* активная кодировка */ -static const uint8_t *g_remap = 0; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */ -static uint8_t g_scratch_phys = 0; /* EMM-страница под ремап-буфер строки для win_rest */ +const uint8_t *g_remap = 0; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */ +uint8_t g_scratch_phys = 0; /* EMM-страница под ремап-буфер строки для win_rest */ /* UTF-8 набор (Фаза 2): src декодируется в отдельный блок EMM-страниц как * чистый CP866. После конвертации активный буфер файла (file_phys/file_size/ @@ -312,7 +273,16 @@ static uint8_t utf_blk = 0; static uint8_t utf_pages = 0; static uint8_t utf_phys[MAX_PAGES] = {0}; static uint32_t utf_size = 0; -static uint8_t utf_avail = 0; /* 1 = UTF-8 набор удалось сконвертировать/построить */ +static uint8_t utf_avail = 0; /* 1 = страницы UTF-8 выделены */ + +/* Инкрементальная конвертация UTF-8→CP866: идёт чанками, чередуясь с + * индексацией (index_lines докручивает конвертацию впереди позиции чтения), + * чтобы первый экран появлялся быстро, а не после полного прохода. */ +static uint8_t g_utf_building = 0; /* 1 пока конвертация не дошла до конца исходника */ +static uint32_t utf_src = 0; /* позиция чтения ОРИГИНАЛА (utf-8) конвертером */ +static uint8_t cv_page = 0xFF;/* orig-страница в W3 для cv_read (своя от cur_page) */ +#define CONV_MARGIN 4096u /* на сколько байт держать конвертацию впереди индексатора */ +static void utf_convert_more(uint32_t target); /* forward (нужна в index_lines) */ /* Снимок исходного (8-битного) файлового буфера — набор DOC_8BIT читает его * как есть, набор DOC_UTF8 читает utf_phys. */ @@ -386,10 +356,11 @@ static void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr) /* Полностью очищает указанную строку экрана символами пробела с attr. */ -static void fill_row(uint8_t y, uint8_t attr) +void fill_row(uint8_t y, uint8_t attr) { - bios_set_place(y, 0); - bios_fillcharattr(' ', attr, SCREEN_W); + // bios_set_place(y, 0); + // bios_fillcharattr(' ', attr, SCREEN_W); + bios_clearwin(y, 0, 1, SCREEN_W, attr); } /* Продвигает спиннер на один кадр. @@ -439,7 +410,7 @@ static inline void map_page(uint8_t page) * bank_read читает через IN — общий аппаратный регистр, без теневой RAM. * NB: __naked обязателен (внутри собственный ret); IX/IY не трогаем * (callee-saved), BC/DE/HL/AF — caller-saved, можно клобберить. */ -static char fb(uint32_t p) __naked +char fb(uint32_t p) __naked { (void)p; /* p = HLDE; доступ только из asm */ __asm @@ -497,7 +468,7 @@ static char fb(uint32_t p) __naked * Когда Фаза 2 начала пакетировать много строк на одной странице с * РАЗНЫМИ off, всё стало читать с начала страницы (т.е. контент первой * записи) независимо от реальной строки — добавлен off, IX = 0xC000+off. */ -static void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off) __naked +void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off) __naked { (void)row; (void)col; (void)h; (void)w; (void)page; (void)off; __asm @@ -586,7 +557,7 @@ static void idx_put(uint16_t idx, const idx_rec_t *r) bank_write(index_phys[page], off, r, INDEX_REC_SIZE); } -static uint32_t seg_off(uint16_t idx) +uint32_t seg_off(uint16_t idx) { idx_rec_t r; if (idx >= n_lines) return file_size; @@ -1433,6 +1404,12 @@ static void index_lines(void) uint32_t p = 0; while (p < file_size && n_lines < max_lines && !g_abort) { + /* 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)); + if (g_loading) progress_tick(); /* первый экран + статус + скролл по готовым данным */ else if ((n_lines & 15) == 0) @@ -1957,14 +1934,28 @@ static void conv_emit_cp(uint32_t cp) /* Декодирует активный (исходный UTF-8) буфер в utf_phys как CP866. * Битые/обрезанные последовательности → '?'; 4-байтные (эмодзи и т.п.) → '?'. */ -static void utf8_convert(void) +/* Последовательное чтение байта ОРИГИНАЛА (utf-8) конвертером. Оригинал лежит + * в orig_file_phys[]; мапим его страницу в W3 напрямую, со своей кэш-переменной + * cv_page (отдельной от cur_page индексатора, т.к. оба используют W3 и + * чередуются). На границе с fb()-чтением utf-буфера кэши взаимно сбрасываются. */ +static uint8_t cv_read(uint32_t s) { - conv_n = 0; conv_page = 0; conv_off = 0; - uint32_t n = file_size; - uint32_t p = 0; - while (p < n) { - if ((uint16_t)((uint16_t)p & 0x0FFFu) == 0) spinner_tick(); - uint8_t b = (uint8_t)fb(p++); + uint8_t pg = (uint8_t)(s >> PAGE_BITS); + if (pg != cv_page) { sprinter_page_w3(orig_file_phys[pg]); cv_page = pg; } + return *((volatile uint8_t *)(0xC000u + (uint16_t)(s & PAGE_MASK))); +} + +/* Возобновляемая конвертация: дописывает utf_phys из оригинала, пока + * сконвертированных (flushed) байт меньше target и не достигнут конец + * исходника. По выходу file_size = доступная (сконвертированная) часть; при + * достижении конца фиксирует utf_size и снимает g_utf_building. */ +static void utf_convert_more(uint32_t target) +{ + cv_page = 0xFF; /* W3 был на utf (fb индексатора) — пере-смаппим orig */ + uint32_t n = orig_file_size; + while (utf_src < n && + (uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) { + uint8_t b = (uint8_t)cv_read(utf_src++); if (b < 0x80) { conv_emit_cp(b); continue; } uint32_t cp; @@ -1972,21 +1963,23 @@ static void utf8_convert(void) if ((b & 0xE0) == 0xC0) { cp = (uint32_t)(b & 0x1F); need = 1; } else if ((b & 0xF0) == 0xE0) { cp = (uint32_t)(b & 0x0F); need = 2; } else if ((b & 0xF8) == 0xF0) { cp = (uint32_t)(b & 0x07); need = 3; } - else { conv_put('?'); continue; } /* одиночный континюэйшн/битый лид */ + else { conv_put('?'); continue; } uint8_t ok = 1; for (uint8_t k = 0; k < need; k++) { - if (p >= n) { ok = 0; break; } - uint8_t cb = (uint8_t)fb(p); + if (utf_src >= n) { ok = 0; break; } + uint8_t cb = (uint8_t)cv_read(utf_src); if ((cb & 0xC0) != 0x80) { ok = 0; break; } cp = (cp << 6) | (uint32_t)(cb & 0x3F); - p++; + utf_src++; } - if (!ok || need == 3) { conv_put('?'); continue; } /* битое / 4-байтное */ + if (!ok || need == 3) { conv_put('?'); continue; } conv_emit_cp(cp); } conv_flush(); - utf_size = (uint32_t)conv_page * PAGE_SIZE + conv_off; + file_size = (uint32_t)conv_page * PAGE_SIZE + conv_off; /* доступно индексатору */ + if (utf_src >= n) { utf_size = file_size; g_utf_building = 0; } + cur_page = 0xFF; /* W3 был на orig — fb индексатора пере-смаппит utf */ } /* Выделяет страницы под UTF-8 набор и конвертирует туда исходник (читает @@ -1994,15 +1987,16 @@ static void utf8_convert(void) * активный буфер: это делает build_doc() при сборке набора DOC_UTF8. * Конвертированный поток ≤ исходного (кириллица 2 байта → 1), хватает того же * числа страниц. Возврат: 1 — успех, 0 — нет EMM. */ -static uint8_t alloc_and_convert_utf8(void) +/* Выделяет страницы под UTF-8 набор (конвертированный ≤ оригинала по размеру). + * Сама конвертация — инкрементальная, через utf_convert_more(). Возврат: 1 — + * успех, 0 — нет EMM. */ +static uint8_t utf_alloc(void) { - utf_pages = file_pages; + utf_pages = orig_file_pages; utf_blk = mem_alloc_pages(utf_pages); if (utf_blk == 0) return 0; for (uint8_t i = 0; i < utf_pages; i++) utf_phys[i] = mem_get_page(utf_blk, i); - - utf8_convert(); /* читает оригинал (active) → utf_phys */ return 1; } @@ -2121,18 +2115,30 @@ static void render_updated_status(void) /* Отрисовывает нижнюю строку меню. */ static void render_menu(void) { - /* Строка меню разбита на 10 блоков по 8 колонок: метку Fn кладём в блок - * (n-1)*8, чтобы позиции соответствовали номерам клавиш (F1→0, F8→7, - * F10→9). Пока заполнены только F1/F8/F10. */ + /* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль + * ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция + * (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст — + * только у задействованных и доступных сейчас. */ fill_row(MENU_ROW, ATTR_MENU_T); - put_str_attr(0 * 8 + 1, MENU_ROW, "F1", ATTR_MENU_K); - put_str_attr(0 * 8 + 4, MENU_ROW, "Help", ATTR_MENU_T); - if (g_f8_enabled) { /* F8 показываем только когда переключение кодировки возможно */ - put_str_attr(7 * 8 + 1, MENU_ROW, "F8", ATTR_MENU_K); - put_str_attr(7 * 8 + 4, MENU_ROW, "Codepage", ATTR_MENU_T); + + char num[3]; num[2] = 0; + for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */ + num[0] = ' '; num[1] = (char)('1' + i); + put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K); } - put_str_attr(9 * 8 + 0, MENU_ROW, "F10", ATTR_MENU_K); - put_str_attr(9 * 8 + 4, MENU_ROW, "Exit", ATTR_MENU_T); + put_str_attr(9 * 8, MENU_ROW, "10", ATTR_MENU_K); /* F10 */ + + put_str_attr(0 * 8 + 2, MENU_ROW, "Help", ATTR_MENU_T); +#if WITH_RAW + if (g_ready) /* RAW доступен после постройки документа */ + put_str_attr(1 * 8 + 2, MENU_ROW, (g_view == VIEW_MD) ? "RAW" : "MD", ATTR_MENU_T); + if (g_view != VIEW_MD) /* Wrap/Unwrap — только в RAW */ + put_str_attr(2 * 8 + 2, MENU_ROW, + (g_view == VIEW_RAW_WRAP) ? "UnWrap" : "Wrap", ATTR_MENU_T); +#endif + if (g_f8_enabled) /* смена кодировки сейчас возможна */ + put_str_attr(7 * 8 + 2, MENU_ROW, "CodePg", ATTR_MENU_T); + put_str_attr(9 * 8 + 2, MENU_ROW, "Exit", ATTR_MENU_T); } /* ================================================================== @@ -2370,6 +2376,22 @@ static void load_key(uint16_t k) /* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */ static void progress_tick(void) { +#if WITH_RAW + /* В RAW-режиме буфер документа готов сразу после конвертации (до индекса), + * поэтому первый экран RAW можно показать немедленно — рисуем raw_draw, а + * не MD-вид (иначе мелькал бы MD). Индекс при этом строится дальше (нужен + * для MD-вида). */ + if (g_view != VIEW_MD) { + if (!g_first_drawn) { + raw_draw(); + raw_status_numbers(); + g_first_drawn = 1; + cur_page = 0xFF; /* raw_draw сбил W3-маппинг, на который опирается fb() */ + } + spinner_tick(); + return; + } +#endif /* Прогрессивная отрисовка строящегося (всегда показываемого) набора. */ uint8_t touched = 0; if (!g_first_drawn && drawable_lines() >= VIEW_H) { @@ -2403,20 +2425,30 @@ static void progress_tick(void) * живых глобалах, а progress_tick через свап показывает/листает уже готовый * g_disp_doc. Возврат: 0 — успех, <0 — нехватка EMM (-5/-6/-7). * - * UTF-8 конвертация делается ЛЕНИВО здесь, а не до первого экрана: при входе - * (для DOC_UTF8 ещё не сконвертированного) живой буфер обязан быть = оригинал - * (8-битные байты) — utf8_convert читает его через fb(). Это так и в случае - * первичного UTF-8 (сразу после load_file), и вторичного (после сборки - * первичного 8-битного, чей буфер = оригинал). */ + * UTF-8 набор конвертируется ИНКРЕМЕНТАЛЬНО: выделяем страницы, переключаем + * живой буфер на utf и запускаем конвертацию первого чанка; дальше index_lines + * докручивает её впереди позиции чтения (g_utf_building). Конвертер читает + * оригинал из orig_file_phys[] через cv_read (не через fb, который теперь + * указывает на utf). */ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible) { if (slot == DOC_UTF8) { - if (!utf_avail) { /* конвертируем сейчас (live = оригинал) */ - if (!alloc_and_convert_utf8()) return -7; + if (!utf_avail) { /* первый вход: выделить страницы */ + if (!utf_alloc()) return -7; utf_avail = 1; + conv_n = 0; conv_page = 0; conv_off = 0; + utf_src = 0; + g_utf_building = 1; } - file_blk = utf_blk; file_pages = utf_pages; file_size = utf_size; + file_blk = utf_blk; file_pages = utf_pages; memcpy(file_phys, utf_phys, MAX_PAGES); + if (g_utf_building) { + file_size = 0; + cur_page = 0xFF; + utf_convert_more(CONV_MARGIN); /* первый чанк — для первого экрана */ + } else { + file_size = utf_size; + } } else { file_blk = orig_file_blk; file_pages = orig_file_pages; file_size = orig_file_size; memcpy(file_phys, orig_file_phys, MAX_PAGES); @@ -2436,6 +2468,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible) index_lines(); g_loading = 0; + /* Зафиксировать конвертацию (на случай обрыва индекса по max_lines до конца + * исходника) — иначе g_utf_building остался бы 1 и сломал бы следующую сборку. */ + if (g_utf_building) { utf_size = file_size; g_utf_building = 0; } + g_doc[slot].built = 1; doc_save_live(slot); return 0; @@ -2445,6 +2481,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible) * 8-бит↔8-бит внутри одного набора — мгновенный ремап; переход в другой набор * — мгновенный doc_switch, если он построен, иначе ЛЕНИВАЯ сборка по месту * (со спиннером), результат кэшируется. */ +/* Меняет кодировку: тот же набор — ремап; другой готовый — doc_switch; + * непостроенный — ЛЕНИВАЯ ТИХАЯ сборка (спиннер, без отрисовки документа). + * НЕ перерисовывает документ/статус — это делает вызывающий в своём режиме + * (MD: draw_viewport_from_cache; RAW: raw_draw), чтобы не мелькал чужой вид. */ static void switch_encoding(uint8_t next) { uint8_t target = (next == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT; @@ -2456,20 +2496,24 @@ static void switch_encoding(uint8_t next) set_encoding(next); g_doc[target].enc = next; } else { /* строим набор лениво */ doc_save_live(g_active_doc); /* сохранить текущий (позиция и т.п.) */ +#if WITH_RAW + g_ready = 0; render_menu(); /* идёт индексация — прячем F2 */ +#endif spinner_show(1); - int rc = build_doc(target, next, 1); /* visible: рисует прогрессивно */ + int rc = build_doc(target, next, 1); /* visible: прогрессивный показ первого экрана + * (progress_tick рисует по текущему виду — MD/RAW) */ spinner_show(0); +#if WITH_RAW + g_ready = 1; render_menu(); +#endif if (rc < 0) { /* нет EMM — откат на текущий набор */ if (target == DOC_UTF8) g_utf_failed = 1; doc_load_live(g_active_doc); - clamp_top(); draw_viewport_from_cache(); render_full_status(); return; } g_disp_doc = target; /* build_doc уже выставил active/enc */ } - clamp_top(); - draw_viewport_from_cache(); - render_full_status(); + clamp_top(); /* привести top_line к новому набору (для MD) */ } int main(int argc, char **argv) @@ -2581,6 +2625,9 @@ int main(int argc, char **argv) * счётчиком строк. */ spinner_show(0); g_f8_enabled = 1; +#if WITH_RAW + g_ready = 1; /* документ построен — теперь доступен F2 (RAW) */ +#endif render_menu(); clamp_top(); render_full_status(); @@ -2595,6 +2642,65 @@ int main(int argc, char **argv) if (ascii == 0x1B) break; /* клавиша Esc */ continue; } + +#if WITH_RAW + /* F2 — тумблер RAW ↔ MD (рабочий только после постройки документа). */ + if (scan == KEY_F2) { + if (!g_ready) continue; + if (g_view == VIEW_MD) { + g_view = g_raw_last; /* → RAW (последний под-режим) */ + raw_seed_from(top_line); /* позиция из MD */ + raw_screen_init(); + render_full_status(); render_menu(); + raw_status_numbers(); raw_draw(); + } else { + g_raw_last = g_view; /* запомнить под-режим RAW */ + g_view = VIEW_MD; + clamp_top(); + render_full_status(); render_menu(); + draw_viewport_from_cache(); + } + continue; + } + + /* RAW-режим: F3 Wrap/Unwrap, своя навигация; F8 — кодировка; F1/F10. */ + if (g_view != VIEW_MD) { + if (scan == KEY_F10) goto exit_loop; + if (scan == KEY_F1) { + show_help(); + raw_screen_init(); + render_full_status(); render_menu(); + raw_status_numbers(); raw_draw(); + continue; + } + if (scan == KEY_F3) { /* Wrap ↔ Unwrap */ + g_view = (g_view == VIEW_RAW_WRAP) ? VIEW_RAW_HSCROLL : VIEW_RAW_WRAP; + g_raw_last = g_view; + raw_renorm(); + raw_screen_init(); + render_menu(); + raw_status_numbers(); raw_draw(); + continue; + } + if (scan == KEY_F8) { + uint8_t next = (g_encoding == ENC_CP866) ? ENC_CP1251 : + (g_encoding == ENC_CP1251) ? ENC_KOI8R : + (g_encoding == ENC_KOI8R) ? ENC_UTF8 : ENC_CP866; + if (next == ENC_UTF8 && g_utf_failed) next = ENC_CP866; + uint8_t target = (next == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT; + /* другой буфер (в/из UTF-8) → в начало ДО сборки, чтобы прогрессивный + * raw_draw в progress_tick показывал верх нового набора. */ + if (target != g_active_doc) raw_home(); + switch_encoding(next); /* меняет набор/ремап */ + if (g_abort) goto exit_loop; + render_full_status(); raw_status_numbers(); raw_draw(); + continue; + } + raw_key(scan); + continue; + } +#endif /* WITH_RAW */ + switch (scan) { case KEY_F1: show_help(); break; case KEY_F8: { /* цикл CP866→CP1251→KOI8R→UTF8→CP866; нужный набор дособерётся лениво */ @@ -2604,6 +2710,8 @@ int main(int argc, char **argv) if (next == ENC_UTF8 && g_utf_failed) next = ENC_CP866; /* UTF-8 недоступен */ switch_encoding(next); if (g_abort) goto exit_loop; /* Esc/F10 во время ленивой досборки */ + render_full_status(); /* перерисовка — в MD-режиме */ + draw_viewport_from_cache(); continue; } case KEY_F10: goto exit_loop; diff --git a/examples/mdview2/mdview2.h b/examples/mdview2/mdview2.h new file mode 100644 index 0000000..006497f --- /dev/null +++ b/examples/mdview2/mdview2.h @@ -0,0 +1,88 @@ +/* + * mdview2.h — общие определения и интерфейс между модулями mdview2. + * + * Монолит mdview2.c расщепляется инкрементально; первый отделённый модуль — + * RAW-просмотр (mdview2_raw.c). Здесь живут разделяемые константы экрана/ + * атрибутов, extern-объявления нужных RAW символов из mdview2.c и API RAW. + */ +#ifndef MDVIEW2_H +#define MDVIEW2_H + +#include +#include /* COLOR(), COLOR_* */ +#include "mdview2_conf.h" /* WITH_RAW / WITH_HEX — выбор опциональных модулей */ + +/* ---- Геометрия экрана -------------------------------------------- */ +#define SCREEN_W 80 +#define SCREEN_H 32 +#define VIEW_TOP_ROW 1 +#define VIEW_H 30 /* видимая область: строки 1..30 включительно */ +#define MENU_ROW 31 +#define TAB_STOP 4 + +/* ---- Параметры файла и памяти ------------------------------------ */ +#define PAGE_BITS 14u +#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */ +#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u)) +#define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */ +#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE) +#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */ + +/* ---- Палитра атрибутов ------------------------------------------- */ +#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK) +#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE) +#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE) +#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE) +#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE) +#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE) +#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE) +#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE) +#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE) +#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE) +#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE) +#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE) +#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE) +#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE) +#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE) +#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE) +#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN) +#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN) +#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN) +#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK) + +/* ---- Атрибуты диалога справки ------------------------------------ */ +#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK) +#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK) +#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK) +#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK) + +/* ---- Режимы просмотра -------------------------------------------- */ +#define VIEW_MD 0 /* markdown с форматированием (по умолчанию) */ +#define VIEW_RAW_WRAP 1 /* RAW: длинные строки переносятся кратно 80 */ +#define VIEW_RAW_HSCROLL 2 /* RAW: длинные строки в одну, гориз. скролл */ + +/* ================================================================== * + * Разделяемые символы mdview2.c, используемые модулем RAW. + * ================================================================== */ +extern uint8_t g_view; /* активный режим просмотра (VIEW_*) */ +extern uint32_t file_size; /* размер активного буфера документа */ +extern const uint8_t *g_remap; /* таблица ремапа [128-255] (0 = нет) */ +extern uint8_t g_scratch_phys; /* scratch EMM-страница для win_rest */ + +char fb(uint32_t p); /* байт активного буфера (W3-маппинг) */ +void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off); +void fill_row(uint8_t y, uint8_t attr); +uint32_t seg_off(uint16_t idx); /* offset исходника для логической строки idx */ + +/* ================================================================== * + * API модуля RAW (mdview2_raw.c). + * ================================================================== */ +void raw_seed_from(uint16_t md_top_line); /* вход в RAW: позиция по top_line */ +void raw_home(void); /* сброс в начало (смена буфера при F8 в/из UTF-8) */ +void raw_draw(void); /* перерисовать область документа в RAW */ +void raw_status_numbers(void); /* числовая зона статус-бара для RAW */ +uint8_t raw_key(uint8_t scan); /* навигация RAW; 1 = обработано */ +void raw_screen_init(void); /* очистка экрана с нужным аттрибутом */ +void raw_renorm(void); /* выровнять позицию при смене под-режима (F3) */ + +#endif /* MDVIEW2_H */ diff --git a/examples/mdview2/mdview2_conf.h b/examples/mdview2/mdview2_conf.h new file mode 100644 index 0000000..c65ddab --- /dev/null +++ b/examples/mdview2/mdview2_conf.h @@ -0,0 +1,16 @@ +/* + * mdview2_conf.h — конфигурация сборки mdview2: какие опциональные модули + * просмотра включать. 1 = включить (код входит в бинарь), 0 = исключить + * полностью (нулевой расход кода/памяти). + * + * Модули обёрнуты в #if WITH_xxx целиком, поэтому файлы можно держать в + * сборке всегда (при 0 они компилируются в пустой объектник) — менять + * нужно только эти define. + */ +#ifndef MDVIEW2_CONF_H +#define MDVIEW2_CONF_H + +#define WITH_RAW 1 /* RAW-просмотр исходника (F2): mdview2_raw.c */ +#define WITH_HEX 0 /* HEX-просмотр (зарезервировано): mdview2_hex.c */ + +#endif /* MDVIEW2_CONF_H */ diff --git a/examples/mdview2/mdview2_raw.c b/examples/mdview2/mdview2_raw.c new file mode 100644 index 0000000..9d742f5 --- /dev/null +++ b/examples/mdview2/mdview2_raw.c @@ -0,0 +1,334 @@ +/* + * mdview2_raw.c — RAW-просмотр исходного текста (без markdown-форматирования). + * + * Работает по АКТИВНОМУ буферу документа через fb() (8-бит = исходные байты, + * UTF-8 = декодированный в CP866 буфер), ремап ≥0x80 (CP1251/KOI8) — на + * отрисовке. Индекс/кэш markdown не используются: позиция — байт-offset, + * 1 байт = 1 ячейка, \t показываем пробелом. Два режима: + * VIEW_RAW_WRAP — длинные строки переносятся кратно 80 (рвём слова); + * VIEW_RAW_HSCROLL — строка в один ряд, горизонтальный скролл. + * + * Память: 0 доп. EMM; near-буфер строки + переиспользование g_scratch_phys. + */ + +#include +#include +#include +#include +#include "mdview2.h" + +#if WITH_RAW /* весь модуль — опционален (mdview_conf.h); при 0 пустой объектник */ + +static uint32_t raw_top = 0; /* байт-offset верхней строки экрана */ +static uint32_t raw_bot = 0; /* offset ряда сразу ПОД экраном (для прокрутки) */ +static uint16_t raw_hpan = 0; /* гориз. сдвиг (только HSCROLL) */ + +#define HPAN_STEP 8u + +/* ---- примитивы навигации по физическим строкам / переносам -------- */ + +static uint32_t raw_line_start(uint32_t off) +{ + while (off && fb(off - 1) != '\n') off--; + return off; +} + +/* WRAP: начало следующего ряда от off (\n завершает строку; иначе +80). */ +static uint32_t raw_next_wrap(uint32_t off) +{ + uint32_t e = off, lim = off + SCREEN_W; + while (e < file_size) { + if (fb(e) == '\n') return e + 1; + e++; + if (e >= lim) { /* прошли 80 без \n */ + if (e < file_size && fb(e) == '\n') return e + 1; /* ровно 80 + \n */ + return e; /* длинная строка → продолжение */ + } + } + return e; /* EOF */ +} + +/* WRAP: начало предыдущего ряда. */ +// static uint32_t raw_prev_wrap(uint32_t off) +// { +// if (off == 0) return 0; +// uint32_t ls = raw_line_start(off); +// if (off > ls) /* тот же абзац: на 80 назад */ +// return off - SCREEN_W; +// /* off == начало строки: последний ряд предыдущей физической строки. +// * Последнюю 80-границу ищем циклом (без 32-битного деления). */ +// uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */ +// uint32_t last = pls; +// while (last + SCREEN_W < (off - 1)) last += SCREEN_W; +// return last; +// } + +static uint32_t raw_prev_wrap(uint32_t off) +{ + if (off == 0) return 0; + uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */ + while (pls + SCREEN_W < (off - 1)) pls += SCREEN_W; + return pls; +} + +/* HSCROLL: начало следующей/предыдущей физической строки. */ +static uint32_t raw_next_line(uint32_t off) +{ + while (off < file_size && fb(off) != '\n') off++; + return (off < file_size) ? off + 1 : off; +} +// static uint32_t raw_prev_line(uint32_t off) +// { +// if (off == 0) return 0; +// uint32_t p = off - 1; /* \n, завершивший пред. строку */ +// if (p == 0) return 0; +// p--; +// while (p != 0 && fb(p) != '\n') p--; +// return (fb(p) == '\n') ? p + 1 : 0; +// } + +static uint32_t raw_prev_line(uint32_t off) +{ + if (off == 0) return 0; + return raw_line_start(off - 1); +} + +/* ---- отрисовка ряда (общий near-буфер → g_scratch_phys → win_rest) - */ + +static uint8_t raw_buf[SCREEN_W + 1]; /* только символы (атрибут единый, задаётся при выводе) */ + +/* В RAW атрибут единый (ATTR_TEXT): буфер — только 80 символов, без пар + * (char,attr). Кладём один байт-глиф (\t→пробел, ремап ≥0x80). */ +static void raw_cell(uint8_t i, uint8_t ch) +{ + if (ch >= 0x80 && g_remap) ch = g_remap[ch - 0x80]; + raw_buf[i] = ch; +} +/* Вывод готового 80-символьного буфера в строку экрана одним BIOS-вызовом + * (char-буфер + единый attr — без WINREST/scratch-страницы). */ +static void raw_flush(uint8_t row) +{ + bios_set_place(row, 0); + // bios_writeattr((const char *)raw_buf, SCREEN_W, ATTR_TEXT); + // bios_writeattr_until((const char *)raw_buf, SCREEN_W, ATTR_TEXT, 0); + bios_write_until((const char *)raw_buf, SCREEN_W, 0); +} + +static uint32_t raw_draw_wrap_row(uint32_t off, uint8_t row) +{ + uint8_t i = 0; + uint32_t e = off, lim = off + SCREEN_W; + while (i < SCREEN_W && e < file_size) { + char c = fb(e); + if (c == '\n') { + raw_cell(i, 0); + break; + } + raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c)); + i++; e++; + } + raw_flush(row); + return raw_next_wrap(off); +} + +// static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row) +// { +// uint32_t e = off; +// while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */ +// uint16_t len = e - off; + +// uint8_t i = 0; +// uint32_t p = off + raw_hpan; +// while (i < SCREEN_W && p < e) { +// char c = fb(p); +// raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c)); +// i++; p++; +// } +// for (; i < SCREEN_W; i++) raw_buf[i] = ' '; + +// if (raw_hpan > 0) raw_buf[0] = '<'; +// if (len > (uint16_t)raw_hpan + SCREEN_W) raw_buf[SCREEN_W - 1] = '>'; +// raw_flush(row); +// return (e < file_size) ? e + 1 : e; +// } + +static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row) +{ + uint32_t e = off; + while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */ + uint16_t len = e - off; + + uint8_t i = 0; + uint32_t p = off + raw_hpan; + while (i < SCREEN_W && p < e) { + char c = fb(p); + raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c)); + i++; p++; + } + // raw_buf[i] = 0; + + if (raw_hpan > 0) + raw_buf[0] = '<'; + if (len > (uint16_t)raw_hpan + SCREEN_W) + raw_buf[SCREEN_W - 1] = '>'; + if (i < SCREEN_W) + raw_buf[i] = 0; + raw_flush(row); + return (e < file_size) ? e + 1 : e; +} + +/* Рисует один ряд от off (по режиму) и возвращает offset следующего ряда. */ +static uint32_t raw_draw_one(uint32_t off, uint8_t row) +{ + if (off >= file_size) { fill_row(row, ATTR_TEXT); return off; } + return (g_view == VIEW_RAW_WRAP) ? raw_draw_wrap_row(off, row) + : raw_draw_hscroll_row(off, row); +} + +void raw_draw(void) +{ + uint32_t off = raw_top; + for (uint8_t r = 0; r < VIEW_H; r++) + off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + r)); + raw_bot = off; /* offset первого ряда НИЖЕ экрана */ +} + +/* Прокрутка на одну строку: аппаратный scroll окна документа + отрисовка + * ОДНОЙ новой строки (как в MD-режиме). raw_bot — offset ряда сразу под + * экраном, ведётся инкрементально. */ +static uint32_t raw_next(uint32_t off) +{ + return (g_view == VIEW_RAW_WRAP) ? raw_next_wrap(off) : raw_next_line(off); +} +static uint32_t raw_prev(uint32_t off) +{ + return (g_view == VIEW_RAW_WRAP) ? raw_prev_wrap(off) : raw_prev_line(off); +} + +static void raw_scroll_down1(void) +{ + if (raw_bot >= file_size) return; /* уже внизу */ + scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */ + raw_bot = raw_draw_one(raw_bot, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1)); + raw_top = raw_next(raw_top); +} +static void raw_scroll_up1(void) +{ + if (raw_top == 0) return; + uint32_t nt = raw_prev(raw_top); + scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */ + (void)raw_draw_one(nt, VIEW_TOP_ROW); + raw_bot = raw_prev(raw_bot); + raw_top = nt; +} + +/* ---- статус-бар (без подсчёта строк: процент по байтам + режим) ---- */ + +/* Процент по байтам без 32-битного деления: масштабируем offset'ы в единицы + * по 1 КБ (>>10), дальше 16-битная арифметика (a*100 ≤ 12800). */ +static uint8_t raw_pct(void) +{ + uint16_t a = (uint16_t)(raw_top >> 10); + uint16_t b = (uint16_t)(file_size >> 10); + if (b == 0) return 0; /* файл < 1 КБ */ + return (uint8_t)(a * 100u / b); +} + +void raw_status_numbers(void) +{ + uint8_t pct = raw_pct(); + textattr(ATTR_BAR); + gotoxy(47, 0); + bios_writeattr_until(g_view == VIEW_RAW_WRAP ? "RAW wrap" : "RAW pan", + 20, ATTR_BAR, 0); + gotoxy(73, 0); + dec8(pct); + cputs("% "); +} + +/* ---- посев позиции / пересев при смене активного буфера ----------- */ + +void raw_seed_from(uint16_t md_top_line) +{ + uint32_t off = seg_off(md_top_line); + if (off > file_size) off = 0; + raw_top = raw_line_start(off); + raw_hpan = 0; +} + +/* Сброс в начало — при смене активного буфера (F8 в/из UTF-8: размеры/offset'ы + * разные, старый raw_top невалиден). Без percent-математики. */ +void raw_home(void) +{ + raw_top = 0; + raw_hpan = 0; +} + +void raw_screen_init(void) { + bios_clearwin(1, 0, 30, 80, ATTR_TEXT); +} + +/* Нормализация позиции при смене под-режима RAW (F3 Wrap/Unwrap): выровнять + * raw_top на начало физической строки и снять горизонтальный сдвиг. */ +void raw_renorm(void) +{ + raw_top = raw_line_start(raw_top); + raw_hpan = 0; +} + +/* ---- навигация (вызывается из главного цикла при g_view != VIEW_MD) - */ + +uint8_t raw_key(uint8_t scan) +{ + uint32_t nt; + + switch (scan) { + case KEY_DOWN: raw_scroll_down1(); raw_status_numbers(); return 1; + case KEY_UP: raw_scroll_up1(); raw_status_numbers(); return 1; + + case KEY_PGDN: + nt = raw_top; + for (uint8_t k = 0; k < VIEW_H; k++) { + uint32_t n2 = raw_next(nt); + if (n2 >= file_size) break; + nt = n2; + } + break; + case KEY_PGUP: + nt = raw_top; + for (uint8_t k = 0; k < VIEW_H; k++) nt = raw_prev(nt); + break; + case KEY_HOME: + nt = 0; raw_hpan = 0; + break; + case KEY_END: + nt = file_size; /* отмотать VIEW_H рядов назад */ + for (uint8_t k = 0; k < VIEW_H; k++) nt = raw_prev(nt); + raw_hpan = 0; + break; + + case KEY_LEFT: + if (g_view == VIEW_RAW_HSCROLL && raw_hpan) { + raw_hpan = (raw_hpan >= HPAN_STEP) ? (uint16_t)(raw_hpan - HPAN_STEP) : 0; + raw_draw(); + raw_status_numbers(); + } + return 1; + case KEY_RIGHT: + if (g_view == VIEW_RAW_HSCROLL) { + if (raw_hpan < 248u) raw_hpan = (uint16_t)(raw_hpan + HPAN_STEP); + raw_draw(); + raw_status_numbers(); + } + return 1; + default: + return 0; /* не наша клавиша */ + } + + raw_top = nt; + raw_draw(); /* PgUp/PgDn/Home/End — полный экран */ + raw_status_numbers(); + return 1; +} + +#endif /* WITH_RAW */