From 858e5755ad7e6d99c071aea0775b4e27723ed911 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Wed, 10 Jun 2026 10:35:48 +0300 Subject: [PATCH] ChangeLog: - big commit. --- .gitignore | 3 + README.md | 2 +- examples/mdview/Makefile | 33 ++- examples/mdview/PLAN.md | 570 ------------------------------------ examples/mdview/PLAN_866.md | 570 ------------------------------------ examples/mdview/README.MD | 133 +++++++++ examples/mdview/SAMPLE.MD | 5 - examples/mdview/mdview.c | 114 +++++++- lib/Makefile | 6 +- libc/conio/conio.c | 7 + libc/gfx/gfx_core.c | 45 +-- libc/include/conio.h | 32 ++ libc/include/gfx.h | 11 + libc/include/sprinter_mem.h | 32 +- libc/mem/mem_alloc.c | 137 --------- tests/bankedbg/bank1.c | 14 + tests/bankedbg/bank2.c | 4 + tests/hello/hello.c | 4 +- tests/mem_test/mem_test.c | 9 + tests/mouse/mouse.c | 27 +- 20 files changed, 411 insertions(+), 1347 deletions(-) delete mode 100644 examples/mdview/PLAN.md delete mode 100644 examples/mdview/PLAN_866.md create mode 100644 examples/mdview/README.MD delete mode 100644 examples/mdview/SAMPLE.MD delete mode 100644 libc/mem/mem_alloc.c diff --git a/.gitignore b/.gitignore index 9a14985..fa15163 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ examples/*/*.cdb examples/*/*.mem examples/*/*.rst +# Temporary build directory for floppy disk image preparation +examples/*/.disk_tmp/ + tests/*/*.exe tests/*/*.asm tests/*/*.lst diff --git a/README.md b/README.md index d8a507d..669cf04 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ pages by program size — small programs get only one page. Pick a memory mode on what your program needs: | Mode | Code lives in | Banking | Use when | Note | -|---|---|---|---| +|---|---|---|---|---| | `tiny` (default) | W2 (0x8100+) | no | code+data < 14 KB | | | `small` | W1-W2 (0x4100+) | no | code+data < 30 KB | | | `big` | W2 + W1 banking | yes (W1) | tiny + extra code modules | | diff --git a/examples/mdview/Makefile b/examples/mdview/Makefile index ce69405..5b772d2 100644 --- a/examples/mdview/Makefile +++ b/examples/mdview/Makefile @@ -1,10 +1,39 @@ # Build mdview.exe — Markdown viewer for Sprinter. # # small memory mode: code in W1, data/stack/heap in W2 (32 KB total). -# W3 stays free for the file buffer (EMM-mapped) and Phase 3+ render cache. +# W3 stays free for the file buffer (EMM-mapped). PROJ_ROOT := $(abspath $(CURDIR)/../..) EXAMPLE := mdview MEMORY := small -EXTRA_DATA := SAMPLE.MD PLAN_866.md SAMPLEF.MD + include $(PROJ_ROOT)/app.mk + +# ------------------------------------------------------------------ +# Образ дискеты: только mdview.exe + README.MD (перекодированный +# из UTF-8 в CP866 — рабочую кодировку Sprinter). +# +# README.MD хранится в репозитории в UTF-8; iconv -c конвертирует +# его в CP866, отбрасывая символы без аналога в целевой кодировке. +# Результат кладётся в .disk_tmp/README.MD, чтобы make_disk.py +# использовал правильное имя файла на диске. +# ------------------------------------------------------------------ +DISK_TMP := .disk_tmp +README_DISK := $(DISK_TMP)/README.MD + +$(DISK_TMP): + mkdir -p $@ + +$(README_DISK): README.MD | $(DISK_TMP) + iconv -c -f UTF-8 -t CP866 README.MD > $@ + +floppy: $(EXAMPLE).exe $(README_DISK) + python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(README_DISK) + @echo + @echo "Floppy ready: $(FLOPPY_IMG)" + @echo "Run: cd $(MAME_DIR) && ./run_mame.sh" + +clean: + rm -rf .sprinter-cc-* $(EXAMPLE).exe $(DISK_TMP) + +.PHONY: all clean floppy run diff --git a/examples/mdview/PLAN.md b/examples/mdview/PLAN.md deleted file mode 100644 index 2af3364..0000000 --- a/examples/mdview/PLAN.md +++ /dev/null @@ -1,570 +0,0 @@ -# План: текстовый Markdown viewer для Sprinter (`examples/mdview`) - -## Context - -Тестовая крупная задача — проверить нашу libc на нетривиальном interactive-приложении (полноэкранный UI, файловый I/O, парсер). Параллельно даст хороший showcase платформы и поможет вытащить недоделки в conio/io. Конечная цель: viewer для `.md` файлов с подсветкой синтаксиса, навигацией по тексту и постраничным скроллингом. - -**Ограничения v1 (зафиксированы пользователем):** -- POSIX file API (open/read/lseek/close); FILE pointer / fread / fgets — не использовать -- Подсветка через цвет: **размер заголовка** → цвет шрифта; **bold/italic** → цвет фона (моноширинный фонт без жирного/курсивного начертания) - -**Расширения, реализованные после плана:** -- Файлы до 128 KB (1–8 EMM-страниц, lazy map в W3 через `fb()/map_page()`) — изначально было в v2 -- Анимированный spinner и предварительная отрисовка UI при старте — UX -- Inline emphasis: `_`/`*`/`**` подчиняются XOR flanking-правилу (whitespace ровно с одной стороны), `COLOR_YELLOW` и `2 * 3` остаются литералами - ---- - -## Текущий статус (2026-06-05) - -| Phase | Статус | Комментарий | -|---|---|---| -| 1 Plain text + nav | ✓ | загрузка, индексация, status/menu, ↑↓/PgUp/PgDn/Home/End/F1/F10/Esc | -| 2 Headers + HR | ✓ | H1..H4, `---`/`***`/`___` (с ≥3 marker'ов) | -| 3 Inline emphasis | ✓ | `**` / `*` / `_` / `` ` ``; XOR flanking (см. выше) | -| 4 Lists / quote / fenced code | ✓ | `- / * / +`, `N. / N)`, `> `, ``` ``` ```; light nested lists | -| 4-tables | ✗ | таблицы отложены вместе с Phase 6 | -| 5 Wrap / Unwrap (F2) | ✓ | wrap-by-default; soft wrap; F2 переключает; hpan ←/→ в truncate-режиме | -| 6 Полный layout таблиц | ✗ | deferred | -| 7 Links + search | ✗ | deferred | -| 8 F8 Raw toggle | ✗ | deferred | -| Cache рендеренных строк | ✗ | не нужно по скорости | - -**UX-поправки (отдельно от phase-плана, 2026-06-05):** -- UI (menu + title bar) отрисовывается ДО `load_file`/`index_lines` — пользователь сразу видит интерфейс, а не чёрный экран -- Title bar: `MDVIEW ` (3 пробела между MDVIEW и filename, slot спиннера — col 8) -- Spinner крутится во время `load_file` (по странице) и `index_lines` (раз в 32 логических строки), включая `toggle_wrap` - ---- - -## Архитектура - -### Раскладка экрана (80×32, текст mode 0x03) - -``` -Row 0: ┃ MDVIEW │ mdview.md │ L 1-30 / 142 │ 21% ┃ ← status bar (BG=blue, FG=white) - ▲ ▲ - │ └── filename @ col 10 - └────────── spinner slot @ col 8 (anim. while busy) -Row 1: ┃ ┃ -... ┃ document viewport (30 rows) ┃ -Row 30: ┃ ┃ -Row 31: ┃ F1 Help F2 Wrap F10 Exit ┃ ← menu bar (BG=blue, FG=cyan) -``` - -- Viewport = 30 строк × 80 столбцов. -- Status / menu рендерятся через `wrchar()` (без авто-скролла), viewport — через `LOCATE` + посимвольный `wrchar()` (тоже без авто-скролла, ставит и char и attr за один call). - -### Память - -Memory mode: **`small`** — DSS отводит под наш образ **два банка (W1 + W2)** = 32 KB суммарно (CODE в W1, DATA + STACK + HEAP в W2). Этого должно хватить чтобы НЕ заводить `__banked` функции. W3 остаётся полностью свободным для маппинга больших буферов: - -``` -W0 (0x0000-0x3FFF): ESTEX (system, untouchable) -W1 (0x4000-0x7FFF): CODE (small mode page 1) -W2 (0x8000-0xBFFF): DATA + STACK + HEAP (small mode page 2) -W3 (0xC000-0xFFFF): paged window — лениво переключается между EMM-страницами - файла (до 8 страниц = 128 KB). `cur_page` кэширует - текущую mapping, `fb(p)` маппит нужную страницу при - первом обращении. -``` - -Почему small + W3: -- 32 KB на код+данные с большим запасом → нет банкинга -- W3 — стандартный paged window, под него у нас уже есть `bank_io_w3` API -- Файлы до 128 KB поддерживаются нативно: `mem_alloc_pages(pages_needed)` под весь файл; `map_page()` через `sprinter_page_w3()`; `fb(p)` — единая точка доступа из индексатора и рендера. - -Статики (в W2): -- `line_offset[MAX_LINES]` — uint16_t смещение каждой строки в файловой странице (4 KB на 2048 строк) -- `cache_tag[CACHE_N]` — uint16_t тег слота (200 байт на 100 слотов, появляется в Phase 3) -- `filename[64]`, `top_line`, `total_lines`, `file_size`, `file_blk`, `cache_blk` — единицы байт - -**FILE_BUF**: `((char*)0xC000)` — фиксированная адресация в W3 после маппинга нужной страницы. - -### Поток данных - -``` -main → open() → read() chunks 1KB → write to W1 (mapped EMM page) → close() - → index_lines() (одно сканирование, заполняет line_offset[]) - → render_viewport() + main loop { getkey(); handle(); render_status(); render_viewport() } -``` - -**Тонкость с `read()`**: ESTEX READ записывает по dst-указателю в адресном пространстве вызывающего. Поскольку мы маппим EMM-страницу в W3 (0xC000) ДО вызова read(), указатель 0xC000+offset валиден. Если выяснится что BIOS трогает W3 во время read (графический видеобуфер по умолчанию в W3 при графических режимах, но в текстовом — должен быть свободен) — fallback: читать в 1 KB буфер в W2 и копировать в W3 через `bank_write_w3()`. - ---- - -## Реализация — поэтапная - -### Phase 1 — Plain text viewer (MVP) ✓ - -**Что работает:** -- Загрузка файла (`open/read/close`) в W1-страницу -- Индексация строк (LF / CRLF разделители) -- Status bar: имя файла, L N-M / Total, процент скроллинга -- Menu bar: `F1 Help F10 Exit` -- Навигация: ↑/↓ (1 строка), PgUp/PgDn (30 строк), Home/End (начало/конец), Esc/F10 (выход), F1 (help screen) -- Обрезка строк длиннее 80 символов (без word-wrap) -- Цвета: текст белый на чёрном; status/menu — белый на синем - -**Критичные файлы:** -- `examples/mdview/mdview.c` — main, key loop, rendering, indexing (one-file MVP) -- `examples/mdview/Makefile` -- `examples/mdview/SAMPLE.MD` — тестовый markdown файл - -### Phase 2 — Headers и горизонтальная линия ✓ - -**MD фичи:** -- `# H1` → ярко-жёлтый (COLOR_YELLOW = 14) на чёрном -- `## H2` → ярко-голубой (COLOR_LBLUE = 11) -- `### H3` → ярко-зелёный (COLOR_LGREEN = 10) -- `#### H4+` → серый (COLOR_GREY = 8) -- `---` / `***` на отдельной строке → линия 0xC4 (горизонтальная рамка ASCII) во всю ширину - -### Phase 3 — Inline emphasis ✓ - -**Парсер inline (per-line, runs в одну строку):** -- `**bold**` → ATTR_TEXT_BOLD -- `*italic*` → ATTR_TEXT_ITALIC -- `_underscore_` → ATTR_TEXT_UNDERSORE -- `` `code` `` → ATTR_TEXT_CODE -- Маркеры `**`/`*`/`_`/`` ` `` НЕ рендерятся (съедаются) - -State machine: один активный стиль одновременно (без вложенности); конфликтующий маркер при чужом активном стиле всё равно консьюмится (zero-width) для синхронизации ширины с index_lines. Состояние сбрасывается на каждой строке. - -**Flanking-правило (CommonMark intraword, реализовано после изначального плана):** -- `*` / `**` / `_` считаются markdown-маркером только если whitespace/EOL - ровно с ОДНОЙ стороны (XOR). -- Случаи "оба whitespace" (`2 * 3`, `2 ** 3`) → литералы (арифметика). -- Случаи "ни одного whitespace" (`COLOR_YELLOW`, `FILE*/fread`, `foo*bar*baz`) - → литералы (intraword). -- Backtick (`` ` ``) flanking НЕ требует — `` `code` `` работает без пробелов. -- Правило применено симметрично в 4 местах (`index_lines`, cont-render, - основной inline-парсер, truncation peek), иначе wrap-индексатор и - рендер разъедутся по ширине. - -> **Кэш отформатированных строк** — отложен в самый конец, см. "Phase ∞: оптимизации". -> Скорости текущего наивного рендера хватает на 80×30 = 2400 wrchar / кадр; PgUp/PgDn визуально мгновенен. - -### Phase 4 — Block elements ✓ (без таблиц) - -- Маркированные списки: `- foo`, `* foo`, `+ foo` → префикс `•` (0x07) + пробел; цвет маркера ярче основного -- Нумерованные списки: `1. foo`, `2. foo` → как есть (число оставляем) -- Blockquote: строки с `> ` → префикс `│` (0xB3) серого цвета, остальной текст слегка приглушённый -- Fenced code blocks: `` ``` `` открывает/закрывает блок; все строки между — bg=серый, моноширинно (без inline-парсинга) -- Indented code blocks (4+ пробелов): аналогично fenced, но без явного маркера - -**Light nested lists (v1 — реализовано):** -- `classify_line()` пропускает leading spaces перед ulist/olist/quote маркером, - возвращает `content_off` после маркера → `content_off - p_start` = indent + marker - бит в visible col. -- `render_line()` рисует leading-spaces в `ATTR_TEXT`, потом маркер на сдвинутой - позиции (col = indent). Marker всё ещё фиксирован при горизонтальном pan'е. -- HR / header / fence delim остаются строго col-0 (CommonMark разрешает до 3 - ведущих пробелов для них — упростили). -- Tab-indent → не распознаётся как nesting (только spaces). - -**Phase 4-full — полная поддержка вложенности (deferred):** -- **Tab-indent**: считать tab = 4 пробела для определения уровня. -- **Quote nesting** (`> > foo`): каждый `>` подряд = +1 уровень, каждый рисуется - отдельным `│` в `ATTR_QUOTE_MARKER` (визуальная "лестница" слева). -- **Hanging indent в wrap-continuation**: когда `- some very long bullet text - that wraps...` — continuation seg должен начинаться от content-col (после - маркера), а не от col 0. Сейчас continuation идёт от col 0 (v1 simplification). - Требует хранить `marker_width` per логическая строка (8 бит) или re-classify - first seg при рендере continuation. -- **Lazy continuation**: строки без маркера, но с правильным indent под - предыдущим bullet'ом, должны считаться продолжением того bullet'а - (визуально — общий attr). -- **Strict CommonMark indent rules**: вложенный пункт должен быть на indent - ≥ content_col родителя, иначе считается breakout. Нужен мини-stack - активных списков при индексации. - -### Phase 5 — Wrap / Unwrap длинных строк ✓ - -Дефолт: **wrap on**. F2 переключает; в меню-баре подпись отражает действие -("Unwrap" когда wrap включён, "Wrap" когда выключен). Во время реиндексации -крутится спиннер на title bar. - -**v1 — реализовано:** -- Один массив `line_offset[2048]` хранит ВИДИМЫЕ сегменты (а не логические - строки); биты 0..13 — байтовое смещение, бит 15 — CONT-флаг continuation. -- Wrap-режим: soft wrap на последнем пробеле ≤ 80; hard fallback если - пробела нет. -- Маркеры эмфазиса (`**`/`*`/`_`/`` ` ``) и header-префиксы (`#`/`##`/...) не - учитываются в visible-col при поиске точки переноса. -- "Специальные" логические строки не wrap'аются вообще (одна seg-запись на - логическую строку): fence delim, table row (header/separator/body), HR. -- F2 toggle сохраняет визуальную позицию через `top_offset` в FILE_BUF. -- Bitmaps (`in_code`/`in_table`/`is_tab_hdr`) перестраиваются вместе с - сегментами, индексируются seg-индексом, биты ставятся только на первом - seg'е логической строки. -- Continuation-сегменты рендерятся в стиле "v1: плоско" — plain text, - никаких markdown-классификаций; padding до конца строки `ATTR_TEXT`. - -**v1.5 — отложено для полной картины wrap:** -- **Hanging indent**: continuation от ulist/olist/quote должен выравниваться - под content, а не от col 0. Требует хранить marker_width per логическая - строка ИЛИ re-classify первого seg'а при рендере continuation. -- **Наследование base_attr**: continuation от header'а должен сохранять - цвет; continuation от code body — фон ATTR_TEXT_CODE. Требует хранить - 1 байт `base_attr` per seg ИЛИ lookup первого seg'а. -- **Inline emphasis через границу**: эмфазис, открытый в первом seg'е и не - закрытый, должен продолжаться во втором. Требует хранить emph state per - seg (3 бита). -- Compact way: добавить параллельный массив `seg_meta[MAX_SEGS]` по 1 байту - — пакует marker_width (4 бита) + emph_state (3 бита) + base_attr_idx - (4 бита из таблицы → нужен 2-байтовый seg_meta). -- **Hpan для длинных строк**: если wrap выключен, добавить ←/→ для - горизонтального скролла >80 cols. Общий механизм с tables (Phase 6). - **РЕАЛИЗОВАНО** (light) — `viewport_x` + полный re-render на каждое ←/→. -- **Ускорение hpan через ESTEX WINCOPY/WINREST** (deferred): сейчас pan - делает полный `render_viewport()` = 30 строк × 80 wrchar. Можно - скопировать существующее содержимое viewport'а на N cols влево/вправо - через win-copy, потом рендерить только узкую полосу справа/слева - (HPAN_STEP cols × 30 rows ≈ 240 wrchar вместо 2400). ESTEX SCROLL - горизонталь не поддерживает — нужна именно WINCOPY-операция или - rdchar/wrchar loop. Активировать когда ощутится тормоз; сейчас на - типовом markdown'е не заметно. - -**v2 — отдельная фича, мимо wrap:** -- Toggle подсветки целиком (F3?) -- Search по тексту (Ctrl+F / F4) -- Links `[text](url)` → синий подчёркнутый text, url прячется -- Images `![alt](path)` → `[IMG: alt]` - -### Phase 7 — Links и поиск (post-v1) - -- `[text](url)` → отрисовать только `text` с ярко-синим FG (визуально подчёркнутое) -- `![alt](path)` → `[IMG: alt]` в скобках -- Search по тексту (F3 / Ctrl+F): инкрементальный, подсветка совпадений - -### Phase 8 — F8 Raw / Render toggle - -Переключатель режима отображения: при включённом Raw показывается исходный -текст файла как есть — все markdown-маркеры (`#`, `**`, `_`, `` ` ``, `|`, -`>`, `-`, etc.) рендерятся литералами с `ATTR_TEXT`, без классификации. -Полезно когда: -- нужно увидеть точную разметку (отлаживание .md, скриншоты, копирование) -- markdown-классификатор ошибся и хочется увидеть оригинал -- хочется быстро сравнить "до/после" рендера - -**Поведение:** -- F8 переключает `render_mode` (1=render, 0=raw); меню показывает обратное - действие ("Raw" когда сейчас render, "Render" когда сейчас raw) — той же - логикой что F2/Wrap/Unwrap. -- В Raw режиме: `render_line()` идёт по короткому пути — никакого - `classify_line`, `is_fence_delim`, `is_code_body`, inline-эмфазиса; просто - байтовый дамп FILE_BUF от seg-offset до next-seg/EOL с tab-expansion и - ATTR_TEXT. -- Раздельно от F2: оба режима независимы (можно Raw+Wrap, Raw+Truncate, - Render+Wrap, Render+Truncate). Wrap-логика в `index_lines` работает в - обоих случаях одинаково (опирается на визуальные колонки независимо от - раскраски). -- Статус-бар: добавить индикатор `[R]` / `[V]` (Raw / View) или текстом - `RAW` рядом с именем файла. - -**Минимальная реализация:** -- Один новый static `uint8_t render_mode = 1;` -- В `render_line()`: на самом верху `if (!render_mode) { … raw render … return; }` -- В `render_menu()`: добавить ярлык F8 рядом с F2. -- В главном цикле: `case KEY_F8: toggle_render(); break;` -- `toggle_render()` отличается от `toggle_wrap()` тем, что НЕ перестраивает - `line_offset[]` (wrap-сегментация не меняется), только перерендерит экран. - ---- - -## API-новинки в libc (минимальные) - -### `getkey()` — extended key reader (в libc) - -Текущий `getch()` теряет scan code расширенных клавиш (возвращает только E=ASCII). Добавляем **сразу в `libc/conio/conio.c`** новую функцию рядом с `getch()`: - -```c -// Returns scan in high byte, ASCII in low byte. -// Extended keys (arrows, F-keys, PgUp/PgDn, Home/End): ASCII=0, scan code в high byte. -// Plain keys: ASCII в low byte; high byte содержит positional scan (бит 7 = Ctrl/Alt/Shift modifier). -uint16_t getkey(void) __naked { - __asm - ld c, #0x30 ; ESTEX WAITKEY - rst #0x10 ; A=ASCII, D=scan, E=ASCII - ld e, a ; ensure E=ASCII even if E clobbered - ret ; SDCC __sdcccall(1): возврат uint16_t в DE (D=scan, E=ASCII) - __endasm; -} -``` - -Дополнительно — **в `libc/include/conio.h`** прописать прототип и константы scan-кодов: - -```c -uint16_t getkey(void); - -/* Scan codes for getkey() high byte when ASCII=0 (extended keys). */ -#define KEY_F1 0x0E -#define KEY_F2 0x0F -#define KEY_F3 0x10 -#define KEY_F4 0x11 -#define KEY_F5 0x12 -#define KEY_F6 0x13 -#define KEY_F7 0x14 -#define KEY_F8 0x15 -#define KEY_F9 0x16 -#define KEY_F10 0x17 -#define KEY_F11 0x18 -#define KEY_F12 0x19 -#define KEY_END 0x24 -#define KEY_DOWN 0x25 -#define KEY_PGDN 0x26 -#define KEY_LEFT 0x27 -#define KEY_RIGHT 0x29 -#define KEY_HOME 0x2A -#define KEY_UP 0x2B -#define KEY_PGUP 0x2C -#define KEY_INS 0x23 -#define KEY_DEL 0x22 -``` - -**Скан-коды (из docs/converted/ProgrammerManual.txt:2143-2323):** -| Клавиша | scan | Клавиша | scan | -|---|---|---|---| -| F1 | 0x0E | Up | 0x2B | -| F10 | 0x17 | Down | 0x25 | -| F11 | 0x18 | Left | 0x27 | -| F12 | 0x19 | Right | 0x29 | -| | | PgUp | 0x2C | -| | | PgDn | 0x26 | -| | | Home | 0x2A | -| | | End | 0x24 | - -### Что **переиспользуем** из существующей libc - -- `open/read/lseek/close` — `libc/io/{open,read,lseek}.c` (POSIX wrappers) -- `mem_alloc_pages/mem_free_block/mem_get_page` — `libc/mem/mem_alloc.c` -- `sprinter_page_w3()` — inline `__sfr` write в `libc/include/sprinter.h:113` -- `bank_read_w3/bank_write_w3` — `libc/mem/bank_io_w3.c` (для fallback или v2 multi-page) -- `wrchar(x, y, ch, attr)` — `libc/conio/conio.c:476` (без auto-scroll, идеально для viewport) -- `clrscr_attr(attr)` — `libc/conio/conio.c:395` -- `gotoxy/wherex/wherey` — `libc/conio/conio.c:412-462` (если нужно) -- `kbhit()` — `libc/conio/conio.c:22` (для non-blocking опроса, опционально) -- `dec16/dec8` — `libc/stdio/dec_print.c` (для status bar: текущая строка / total / %) -- `COLOR(fg, bg)` макрос — `libc/include/conio.h:152` -- Цветовые константы `COLOR_*` — `libc/include/conio.h:145` -- `strlen/memcpy/memset` — z80.lib (НЕ переписывать) - ---- - -## Структура исходников - -``` -examples/mdview/ -├── Makefile # стандартный pattern (см. examples/cat/Makefile) -├── mdview.c # Phase 1: всё в одном файле (main, keys, indexing, render) -├── SAMPLE.MD # тестовый markdown -└── README.md # описание и controls -``` - -После Phase 3 раскидать по модулям (если суммарный размер > ~6KB): -``` -mdview.c — main loop, status/menu bars, key dispatch -mdrender.c — line rendering with MD inline parser -mdindex.c — file load + line indexing -``` - -### Сборка - -```makefile -PROJ ?= ../.. -SPRINTER_CC := $(PROJ)/bin/sprinter-cc -mdview.exe: mdview.c - $(SPRINTER_CC) --memory small -o $@ mdview.c -``` - -`--memory small`: код+данные в W2 (DSS даёт нужное число страниц); файл — отдельная EMM-страница в W3. - ---- - -## Структура mdview.c (Phase 1, эскиз) - -```c -#include -#include -#include -#include -#include -#include -#include - -#define VIEW_TOP 1 -#define VIEW_BOT 30 // inclusive -#define VIEW_H 30 -#define SCREEN_W 80 -#define MAX_LINES 2048 -#define FILE_BUF ((char*)0xC000) /* W3 — EMM page mapped here */ -#define TAB_STOP 4 - -#define ATTR_TEXT COLOR(COLOR_WHITE, COLOR_BLACK) -#define ATTR_BAR COLOR(COLOR_WHITE, COLOR_BLUE) -#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLUE) -#define ATTR_MENU_T COLOR(COLOR_WHITE, COLOR_BLUE) - -static uint16_t line_off[MAX_LINES]; -static uint16_t n_lines; -static uint16_t top_line; -static uint16_t file_size; -static uint8_t file_blk; -static char filename[64]; - -static int load_file(const char *path); // open, alloc EMM page, map W3, read, close -static void index_lines(void); // scan FILE_BUF, fill line_off[] -static void render_status(void); // row 0 -static void render_menu(void); // row 31 -static void render_line(uint16_t idx, uint8_t row); // one line @ row -static void render_viewport(void); // VIEW_H lines starting from top_line -static void scroll_up(uint16_t n); -static void scroll_down(uint16_t n); -static void help_screen(void); // F1 - -int main(int argc, char **argv) { - if (argc < 2) { puts("Usage: mdview "); return 1; } - if (load_file(argv[1]) < 0) { puts("load error"); return 1; } - index_lines(); - clrscr_attr(ATTR_TEXT); - render_menu(); - render_status(); - render_viewport(); - for (;;) { - uint16_t k = getkey(); - uint8_t ascii = k & 0xFF; - uint8_t scan = (k >> 8) & 0x7F; // strip mod bit - if (ascii) { - if (ascii == 0x1B) break; // Esc → exit - continue; - } - switch (scan) { - case KEY_F10: goto exit; - case KEY_F1: help_screen(); break; - case KEY_UP: scroll_up(1); break; - case KEY_DOWN: scroll_down(1); break; - case KEY_PGUP: scroll_up(VIEW_H); break; - case KEY_PGDN: scroll_down(VIEW_H); break; - case KEY_HOME: top_line = 0; - render_viewport(); break; - case KEY_END: /* clamp to last viewport */ break; - } - render_status(); - } -exit: - mem_free_block(file_blk); - clrscr_attr(ATTR_TEXT); - return 0; -} -``` - ---- - -## Verification - -### Phase 1 -1. Подготовить `SAMPLE.MD` ~5 KB (заголовки, абзацы, списки) — рендериться будет plain. -2. `cd examples/mdview && make` -3. `python make_disk.py mdview.exe SAMPLE.MD → mc.img && ./run_mame.sh` -4. Проверить: - - Status bar показывает `SAMPLE.MD L 1-30 / N X%` - - Menu bar внизу - - ↑/↓: 1 строка - - PgUp/PgDn: 30 строк, корректное clamp на границах - - Home: top_line=0 - - End: top_line = total_lines - VIEW_H - - F1: показывает help, любая клавиша возвращает - - F10 / Esc: выход, экран очищен -5. Edge cases: пустой файл, файл из одной строки, файл с очень длинной строкой (>80), CRLF и LF mixed. - -### Phase 2-4 -Расширять `SAMPLE.MD` с фичами по мере добавления, визуально верифицировать в MAME. Скриншот-сравнение опционально. - -### Регрессии -- Никаких изменений в libc на Phase 1 кроме (потенциально) добавления `getkey()` в `libc/conio/conio.c` — если так, прогнать `examples/conio2` и `examples/filetest` чтобы убедиться что ничего не сломалось. - ---- - -## Решения по неоднозначностям - -1. **Word-wrap vs truncate**: v1 = truncate (просто). v2 — F2 toggle wrap. -2. **Горизонтальный скроллинг**: v1 — нет; v2 — `←/→` сдвиг viewport по столбцам. -3. **Tab handling**: преобразование при рендере, **tabstop = 4** (стандарт MD). Оригинал в W3 не трогаем. -4. **UTF-8**: рендерим байты как есть. Если файл в CP866 — отрисуется кириллицей через системный фонт. UTF-8 — пока не поддерживаем (визуально будет каша на не-ASCII символах; детект и предупреждение — в v2). - ---- - -### Phase 6 — Полный layout таблиц (deferred) - -Сейчас (Phase 4-tables) таблицы рисуются "декоративно" — пайпы и тире -заменяются на box-drawing chars, но ширины колонок берутся как есть из -исходника. Цель Phase 6 — пересчитать таблицу в нормализованный вид: - -- **Pre-scan таблицы**: пройти все строки одного table-блока, найти - максимальную ширину каждой колонки (с учётом съеденных inline-маркеров — - визуальный размер, не байтовый). -- **Re-emit в буфере**: при загрузке файла (или при первой встрече таблицы) - переписать строки в FILE_BUF так, чтобы все ячейки одной колонки имели - одинаковую ширину; добавить top/bottom рамки (`┌─┬─┐` / `└─┴─┘`) как - синтетические строки. Это позволит сохранить 1:1 соответствие "логическая - строка → одна viewport row" без специальной логики при рендере. -- **Память**: re-emit может УВЕЛИЧИТЬ файл за счёт padding и доп.рамок. - Если буфер близок к 16KB — отрезать таблицу и пометить её overflow'ом. -- **Горизонтальный скроллинг**: если итоговая ширина таблицы (или любой - строки) > SCREEN_W = 80 — добавить ←/→ для horizontal pan. Это будет - общий механизм для длинных строк (см. также wrap mode), не только таблиц. -- **Выравнивание из separator-row**: `:-` → left, `-:` → right, `:-:` → - center; учитывать при padding'е содержимого ячейки. -- **Шаги реализации**: - 1. Walking pass по фенсам/таблицам прямо в `index_lines()` — собрать - extents всех таблиц. - 2. Для каждой таблицы — определить ширины колонок. - 3. Решение: rewrite-in-buffer (проще для рендера, но мутирует исходник) - vs render-time layout (cleaner, но требует отдельной структуры - описания layout'а на каждую таблицу). - 4. Hpan: общий `viewport_x_offset` для всего экрана, или отдельный - "широкий режим" только внутри таблиц. - -> Не блокирующая фича. Запускать когда станет понятен типовой источник -> markdown-файлов (узкие читалки → достаточно текущего декоратора; -> широкие README с большими таблицами → нужен полный layout). - ---- - -### Phase ∞ — Кэш рендеренных строк (low priority) - -Отложено: текущая скорость более чем достаточна. Активировать если появится -сценарий, где видна задержка PgUp/PgDn (например, при тяжёлом inline-парсере -v2 с UTF-8 / linkifier / таблицами). - -**Кэш отформатированных строк** (W3, отдельная EMM-страница): - -``` -Cache layout (16 KB EMM page, всего 16000 байт используется): - slot 0: 80 chars + 80 attrs = 160 bytes @ offset 0 - ... - slot 99: 80 chars + 80 attrs = 160 bytes @ offset 15840 - -Cache tags (W2 static): uint16_t cache_tag[100] = 200 bytes - cache_tag[i] = line_id, или 0xFFFF = invalid -``` - -Стратегия — **direct map (no LRU)**: `slot = line_id % 100`. Коллизия → вытеснение. - -**Batched viewport render**: 2 page-swap'а на ВЕСЬ viewport (cache → file → cache), -не 60 как при наивной реализации. - -При сборке: `cache_blk = mem_alloc_pages(1)` после `file_blk`; `mem_free_block` на exit. - ---- - -## Что отложено в v2 - -- ~~Файлы >16 KB~~ — **сделано в v1.5** (до 128 KB через 1–8 EMM-страниц + lazy map в W3). -- ~~Word wrap~~ — **сделано** (Phase 5, F2 toggle). -- Search (Find / Find next) — F3 / F4. -- F8 Raw / Render toggle — спецификация в Phase 8. -- Links `[text](url)` + image alt — Phase 7. -- Tables — Phase 6 (полный layout). -- Toggle highlight on/off — частный случай F8 Raw. diff --git a/examples/mdview/PLAN_866.md b/examples/mdview/PLAN_866.md deleted file mode 100644 index 9c05245..0000000 --- a/examples/mdview/PLAN_866.md +++ /dev/null @@ -1,570 +0,0 @@ -# : ⥪⮢ Markdown viewer Sprinter (`examples/mdview`) - -## Context - -⮢ 㯭 - ஢ libc ਢ쭮 interactive-ਫ (࠭ UI, 䠩 I/O, ). ࠫ쭮 訩 showcase conio/io. 筠 楫: viewer `.md` 䠩 ᢥ⪮ ᨭ⠪, 樥 ⥪ ࠭ ஫. - -**࠭祭 v1 (䨪஢ 짮⥫):** -- POSIX file API (open/read/lseek/close); FILE pointer / fread / fgets - ᯮ짮 -- ᢥ⪠ १ 梥: **ࠧ ** -> 梥 ; **bold/italic** -> 梥 䮭 (ਭ 䮭 ୮/ᨢ ⠭) - -**७, ॠ ᫥ :** -- 128 KB (1-8 EMM-࠭, lazy map W3 १ `fb()/map_page()`) - 砫쭮 뫮 v2 -- ஢ spinner ।⥫쭠 ᮢ UI - UX -- Inline emphasis: `_`/`*`/`**` 稭 XOR flanking-ࠢ (whitespace ஢ ஭), `COLOR_YELLOW` `2 * 3` ࠫ - ---- - -## 騩 (2026-06-05) - -| Phase | | ਩ | -|---|---|---| -| 1 Plain text + nav | | 㧪, , status/menu, ^V/PgUp/PgDn/Home/End/F1/F10/Esc | -| 2 Headers + HR | | H1..H4, `---`/`***`/`___` ( >=3 marker') | -| 3 Inline emphasis | | `**` / `*` / `_` / `` ` ``; XOR flanking (. ) | -| 4 Lists / quote / fenced code | | `- / * / +`, `N. / N)`, `> `, ``` ``` ```; light nested lists | -| 4-tables | | ⠡ ⫮ Phase 6 | -| 5 Wrap / Unwrap (F2) | | wrap-by-default; soft wrap; F2 ४砥; hpan <-/-> truncate-० | -| 6 layout ⠡ | | deferred | -| 7 Links + search | | deferred | -| 8 F8 Raw toggle | | deferred | -| Cache ७७ ப | | 㦭 ᪮ | - -**UX-ࠢ (⤥쭮 phase-, 2026-06-05):** -- UI (menu + title bar) ᮢ뢠 `load_file`/`index_lines` - 짮⥫ ࠧ 䥩, ࠭ -- Title bar: `MDVIEW ` (3 ஡ MDVIEW filename, slot ᯨ - col 8) -- Spinner ६ `load_file` ( ࠭) `index_lines` (ࠧ 32 ᪨ ப), `toggle_wrap` - ---- - -## ⥪ - -### ᪫ ࠭ (80x32, ⥪ mode 0x03) - -``` -Row 0: MDVIEW mdview.md L 1-30 / 142 21% <- status bar (BG=blue, FG=white) - - filename @ col 10 - spinner slot @ col 8 (anim. while busy) -Row 1: -... document viewport (30 rows) -Row 30: -Row 31: F1 Help F2 Wrap F10 Exit <- menu bar (BG=blue, FG=cyan) -``` - -- Viewport = 30 ப x 80 ⮫殢. -- Status / menu ७ १ `wrchar()` ( -஫), viewport - १ `LOCATE` + ᨬ `wrchar()` (⮦ -஫, ⠢ char attr call). - -### - -Memory mode: **`small`** - DSS ⢮ ࠧ ** (W1 + W2)** = 32 KB 㬬୮ (CODE W1, DATA + STACK + HEAP W2). ⮣ 墠 ⮡ `__banked` 㭪樨. W3 ᢮ ஢: - -``` -W0 (0x0000-0x3FFF): ESTEX (system, untouchable) -W1 (0x4000-0x7FFF): CODE (small mode page 1) -W2 (0x8000-0xBFFF): DATA + STACK + HEAP (small mode page 2) -W3 (0xC000-0xFFFF): paged window - ४砥 EMM-࠭栬 - 䠩 ( 8 ࠭ = 128 KB). `cur_page` - ⥪ mapping, `fb(p)` 㦭 ࠭ - ࢮ 饭. -``` - -祬 small + W3: -- 32 KB + 訬 ᮬ -> -- W3 - ⠭ paged window, 㦥 `bank_io_w3` API -- 128 KB ন ⨢: `mem_alloc_pages(pages_needed)` 䠩; `map_page()` १ `sprinter_page_w3()`; `fb(p)` - 窠 㯠 ७. - -⨪ ( W2): -- `line_offset[MAX_LINES]` - uint16_t ᬥ饭 ப 䠩 ࠭ (4 KB 2048 ப) -- `cache_tag[CACHE_N]` - uint16_t ⥣ ᫮ (200 100 ᫮⮢,  Phase 3) -- `filename[64]`, `top_line`, `total_lines`, `file_size`, `file_blk`, `cache_blk` - - -**FILE_BUF**: `((char*)0xC000)` - 䨪஢ W3 ᫥ 㦭 ࠭. - -### ⮪ - -``` -main -> open() -> read() chunks 1KB -> write to W1 (mapped EMM page) -> close() - -> index_lines() ( ᪠஢, line_offset[]) - -> render_viewport() + main loop { getkey(); handle(); render_status(); render_viewport() } -``` - -** `read()`**: ESTEX READ 뢠 dst-㪠⥫ ᭮ ࠭⢥ 뢠饣. ᪮ EMM-࠭ W3 (0xC000) 맮 read(), 㪠⥫ 0xC000+offset . ᫨ ᭨ BIOS ண W3 ६ read (᪨ 㬮砭 W3 ᪨ ०, ⥪⮢ - ᢮) - fallback: 1 KB W2 ஢ W3 १ `bank_write_w3()`. - ---- - -## - ⠯ - -### Phase 1 - Plain text viewer (MVP) - -** ࠡ⠥:** -- 㧪 䠩 (`open/read/close`) W1-࠭ -- ப (LF / CRLF ࠧ⥫) -- Status bar: 䠩, L N-M / Total, 業 ஫ -- Menu bar: `F1 Help F10 Exit` -- : ^/V (1 ப), PgUp/PgDn (30 ப), Home/End (砫/), Esc/F10 (室), F1 (help screen) -- १ ப 80 ᨬ ( word-wrap) -- : ⥪ ୮; status/menu - ᨭ - -** 䠩:** -- `examples/mdview/mdview.c` - main, key loop, rendering, indexing (one-file MVP) -- `examples/mdview/Makefile` -- `examples/mdview/SAMPLE.MD` - ⮢ markdown 䠩 - -### Phase 2 - Headers ਧ⠫쭠 - -**MD :** -- `# H1` -> મ- (COLOR_YELLOW = 14) ୮ -- `## H2` -> મ-㡮 (COLOR_LBLUE = 11) -- `### H3` -> મ- (COLOR_LGREEN = 10) -- `#### H4+` -> (COLOR_GREY = 8) -- `---` / `***` ⤥쭮 ப -> 0xC4 (ਧ⠫쭠 ࠬ ASCII) ਭ - -### Phase 3 - Inline emphasis - -** inline (per-line, runs ப):** -- `**bold**` -> ATTR_TEXT_BOLD -- `*italic*` -> ATTR_TEXT_ITALIC -- `_underscore_` -> ATTR_TEXT_UNDERSORE -- `` `code` `` -> ATTR_TEXT_CODE -- થ `**`/`*`/`_`/`` ` `` ७ (ꥤ) - -State machine: ⨢ ⨫ ६ ( ); 䫨騩 થ 㦮 ⨢ ⨫ ࠢ  (zero-width) ᨭ஭樨 ਭ index_lines. ﭨ 뢠 ப. - -**Flanking-ࠢ (CommonMark intraword, ॠ ᫥ 砫쭮 ):** -- `*` / `**` / `_` markdown-થ஬ ⮫쪮 ᫨ whitespace/EOL - ஢ ஭ (XOR). -- 砨 " whitespace" (`2 * 3`, `2 ** 3`) -> ࠫ (䬥⨪). -- 砨 " whitespace" (`COLOR_YELLOW`, `FILE*/fread`, `foo*bar*baz`) - -> ࠫ (intraword). -- Backtick (`` ` ``) flanking ॡ - `` `code` `` ࠡ⠥ ஡. -- ࠢ ਬ ᨬ筮 4 (`index_lines`, cont-render, - ᭮ inline-, truncation peek), wrap- - ७ ࠧꥤ ਭ. - -> ** ଠ஢ ப** - ⫮ ᠬ , . "Phase : ⨬樨". -> ⥪饣 ७ 墠⠥ 80x30 = 2400 wrchar / ; PgUp/PgDn 㠫쭮 . - -### Phase 4 - Block elements ( ⠡) - -- ન஢ ᯨ᪨: `- foo`, `* foo`, `+ foo` -> 䨪 `o` (0x07) + ஡; 梥 થ ᭮ -- 㬥஢ ᯨ᪨: `1. foo`, `2. foo` -> (᫮ ⠢塞) -- Blockquote: ப `> ` -> 䨪 `` (0xB3) ண 梥, ⠫쭮 ⥪ ᫥ ਣ -- Fenced code blocks: `` ``` `` 뢠/뢠 ; ப - bg=, ਭ ( inline-ᨭ) -- Indented code blocks (4+ ஡): 筮 fenced,  થ - -**Light nested lists (v1 - ॠ):** -- `classify_line()` ய᪠ leading spaces । ulist/olist/quote થ஬, - 頥 `content_off` ᫥ થ -> `content_off - p_start` = indent + marker - visible col. -- `render_line()` leading-spaces `ATTR_TEXT`, ⮬ થ ᤢ⮩ - 樨 (col = indent). Marker 䨪஢ ਧ⠫쭮 pan'. -- HR / header / fence delim ண col-0 (CommonMark ࠧ蠥 3 - ஡ - ⨫). -- Tab-indent -> ᯮ nesting (⮫쪮 spaces). - -**Phase 4-full - প (deferred):** -- **Tab-indent**: tab = 4 ஡ । ஢. -- **Quote nesting** (`> > foo`): `>` = +1 ஢, - ⤥ `` `ATTR_QUOTE_MARKER` (㠫쭠 "⭨" ᫥). -- **Hanging indent wrap-continuation**: `- some very long bullet text - that wraps...` - continuation seg 稭 content-col (᫥ - થ), col 0. continuation col 0 (v1 simplification). - ॡ ࠭ `marker_width` per ᪠ ப (8 ) re-classify - first seg ७ continuation. -- **Lazy continuation**: ப થ, ࠢ indent - ।騬 bullet', த ⮣ bullet' - (㠫쭮 - 騩 attr). -- **Strict CommonMark indent rules**: 㭪 - indent >= content_col த⥫, ⠥ breakout. 㦥 -stack - ⨢ ᯨ᪮ 樨. - -### Phase 5 - Wrap / Unwrap ப - -䮫: **wrap on**. F2 ४砥; - ࠦ ⢨ -("Unwrap" wrap , "Wrap" 몫祭). ६ २樨 - ᯨ title bar. - -**v1 - ॠ:** -- ᨢ `line_offset[2048]` ࠭ ᥣ ( ᪨ - ப); 0..13 - ⮢ ᬥ饭, 15 - CONT-䫠 continuation. -- Wrap-०: soft wrap ᫥ ஡ <= 80; hard fallback ᫨ - ஡ . -- થ 䠧 (`**`/`*`/`_`/`` ` ``) header-䨪 (`#`/`##`/...) - 뢠 visible-col ᪥ 窨 ७. -- "樠" ᪨ ப wrap' ( seg- - ப): fence delim, table row (header/separator/body), HR. -- F2 toggle ࠭ 㠫 १ `top_offset` FILE_BUF. -- Bitmaps (`in_code`/`in_table`/`is_tab_hdr`) ࠨ - ᥣ⠬, seg-ᮬ, ⠢ ⮫쪮 ࢮ - seg' ᪮ ப. -- Continuation-ᥣ ७ ⨫ "v1: ᪮" - plain text, - markdown-䨪権; padding ப `ATTR_TEXT`. - -**v1.5 - ⫮ ⨭ wrap:** -- **Hanging indent**: continuation ulist/olist/quote ࠢ - content, col 0. ॡ ࠭ marker_width per ᪠ - ப re-classify ࢮ seg' ७ continuation. -- **᫥ base_attr**: continuation header' ࠭ - 梥; continuation code body - 䮭 ATTR_TEXT_CODE. ॡ ࠭ - 1 `base_attr` per seg lookup ࢮ seg'. -- **Inline emphasis १ ࠭**: 䠧, ࢮ seg' - , த ஬. ॡ ࠭ emph state per - seg (3 ). -- Compact way: ࠫ ᨢ `seg_meta[MAX_SEGS]` 1 - - marker_width (4 ) + emph_state (3 ) + base_attr_idx - (4 ⠡ -> 㦥 2-⮢ seg_meta). -- **Hpan ப**: ᫨ wrap 몫祭, <-/-> - ਧ⠫쭮 ஫ >80 cols. 騩 堭 tables (Phase 6). - **** (light) - `viewport_x` + re-render <-/->. -- **᪮७ hpan १ ESTEX WINCOPY/WINREST** (deferred): ᥩ pan - `render_viewport()` = 30 ப x 80 wrchar. - ᪮஢ 饥 ᮤন viewport' N cols /ࠢ - १ win-copy, ⮬ ७ ⮫쪮 㧪 ࠢ/᫥ - (HPAN_STEP cols x 30 rows 240 wrchar 2400). ESTEX SCROLL - ਧ⠫ ন - 㦭 WINCOPY- - rdchar/wrchar loop. ⨢஢ ମ; ᥩ - ⨯ markdown' ⭮. - -**v2 - ⤥쭠 , wrap:** -- Toggle ᢥ⪨ 楫 (F3?) -- Search ⥪ (Ctrl+F / F4) -- Links `[text](url)` -> ᨭ ભ text, url -- Images `![alt](path)` -> `[IMG: alt]` - -### Phase 7 - Links (post-v1) - -- `[text](url)` -> ᮢ ⮫쪮 `text` મ-ᨭ FG (㠫쭮 ભ⮥) -- `![alt](path)` -> `[IMG: alt]` ᪮ -- Search ⥪ (F3 / Ctrl+F): ६⠫, ᢥ⪠ ᮢ - -### Phase 8 - F8 Raw / Render toggle - -४⥫ ० ⮡ࠦ: 񭭮 Raw 뢠 室 -⥪ 䠩 - markdown-થ (`#`, `**`, `_`, `` ` ``, `|`, -`>`, `-`, etc.) ७ ࠫ `ATTR_TEXT`, 䨪樨. - : -- 㦭 㢨 ࠧ (⫠ .md, ਭ, ஢) -- markdown-䨪 訡 㢨 ਣ -- ࠢ "/᫥" ७ - -**:** -- F8 ४砥 `render_mode` (1=render, 0=raw); 뢠 ⭮ - ⢨ ("Raw" ᥩ render, "Render" ᥩ raw) - ⮩ - F2/Wrap/Unwrap. -- Raw ०: `render_line()` ⪮ - - `classify_line`, `is_fence_delim`, `is_code_body`, inline-䠧; - ⮢ FILE_BUF seg-offset next-seg/EOL tab-expansion - ATTR_TEXT. -- 쭮 F2: ० ᨬ ( Raw+Wrap, Raw+Truncate, - Render+Wrap, Render+Truncate). Wrap- `index_lines` ࠡ⠥ - (ࠥ 㠫 ᨬ - ᪨). -- -: `[R]` / `[V]` (Raw / View) ⥪⮬ - `RAW` 冷 䠩. - -**쭠 ॠ:** -- static `uint8_t render_mode = 1;` -- `render_line()`: ᠬ `if (!render_mode) { ... raw render ... return; }` -- `render_menu()`: F8 冷 F2. -- 横: `case KEY_F8: toggle_render(); break;` -- `toggle_render()` ⫨砥 `toggle_wrap()` ⥬, ࠨ - `line_offset[]` (wrap-ᥣ ), ⮫쪮 ७ ࠭. - ---- - -## API- libc () - -### `getkey()` - extended key reader ( libc) - -騩 `getch()` scan code ७ (頥 ⮫쪮 E=ASCII). 塞 **ࠧ `libc/conio/conio.c`** 㭪 冷 `getch()`: - -```c -// Returns scan in high byte, ASCII in low byte. -// Extended keys (arrows, F-keys, PgUp/PgDn, Home/End): ASCII=0, scan code high byte. -// Plain keys: ASCII low byte; high byte ᮤন positional scan ( 7 = Ctrl/Alt/Shift modifier). -uint16_t getkey(void) __naked { - __asm - ld c, #0x30 ; ESTEX WAITKEY - rst #0x10 ; A=ASCII, D=scan, E=ASCII - ld e, a ; ensure E=ASCII even if E clobbered - ret ; SDCC __sdcccall(1): uint16_t DE (D=scan, E=ASCII) - __endasm; -} -``` - -⥫쭮 - ** `libc/include/conio.h`** ய ⨯ ⠭ scan-: - -```c -uint16_t getkey(void); - -/* Scan codes for getkey() high byte when ASCII=0 (extended keys). */ -#define KEY_F1 0x0E -#define KEY_F2 0x0F -#define KEY_F3 0x10 -#define KEY_F4 0x11 -#define KEY_F5 0x12 -#define KEY_F6 0x13 -#define KEY_F7 0x14 -#define KEY_F8 0x15 -#define KEY_F9 0x16 -#define KEY_F10 0x17 -#define KEY_F11 0x18 -#define KEY_F12 0x19 -#define KEY_END 0x24 -#define KEY_DOWN 0x25 -#define KEY_PGDN 0x26 -#define KEY_LEFT 0x27 -#define KEY_RIGHT 0x29 -#define KEY_HOME 0x2A -#define KEY_UP 0x2B -#define KEY_PGUP 0x2C -#define KEY_INS 0x23 -#define KEY_DEL 0x22 -``` - -**- ( docs/converted/ProgrammerManual.txt:2143-2323):** -| | scan | | scan | -|---|---|---|---| -| F1 | 0x0E | Up | 0x2B | -| F10 | 0x17 | Down | 0x25 | -| F11 | 0x18 | Left | 0x27 | -| F12 | 0x19 | Right | 0x29 | -| | | PgUp | 0x2C | -| | | PgDn | 0x26 | -| | | Home | 0x2A | -| | | End | 0x24 | - -### **२ᯮ㥬** 饩 libc - -- `open/read/lseek/close` - `libc/io/{open,read,lseek}.c` (POSIX wrappers) -- `mem_alloc_pages/mem_free_block/mem_get_page` - `libc/mem/mem_alloc.c` -- `sprinter_page_w3()` - inline `__sfr` write `libc/include/sprinter.h:113` -- `bank_read_w3/bank_write_w3` - `libc/mem/bank_io_w3.c` ( fallback v2 multi-page) -- `wrchar(x, y, ch, attr)` - `libc/conio/conio.c:476` ( auto-scroll, 쭮 viewport) -- `clrscr_attr(attr)` - `libc/conio/conio.c:395` -- `gotoxy/wherex/wherey` - `libc/conio/conio.c:412-462` (᫨ 㦭) -- `kbhit()` - `libc/conio/conio.c:22` ( non-blocking , 樮쭮) -- `dec16/dec8` - `libc/stdio/dec_print.c` ( status bar: ⥪ ப / total / %) -- `COLOR(fg, bg)` - `libc/include/conio.h:152` -- ⮢ ⠭ `COLOR_*` - `libc/include/conio.h:145` -- `strlen/memcpy/memset` - z80.lib ( ९뢠) - ---- - -## 室 - -``` -examples/mdview/ - Makefile # ⠭ pattern (. examples/cat/Makefile) - mdview.c # Phase 1: 䠩 (main, keys, indexing, render) - SAMPLE.MD # ⮢ markdown - README.md # ᠭ controls -``` - -᫥ Phase 3 ᪨ (᫨ 㬬 ࠧ > ~6KB): -``` -mdview.c - main loop, status/menu bars, key dispatch -mdrender.c - line rendering with MD inline parser -mdindex.c - file load + line indexing -``` - -### ઠ - -```makefile -PROJ ?= ../.. -SPRINTER_CC := $(PROJ)/bin/sprinter-cc -mdview.exe: mdview.c - $(SPRINTER_CC) --memory small -o $@ mdview.c -``` - -`--memory small`: + W2 (DSS 㦭 ᫮ ࠭); 䠩 - ⤥쭠 EMM-࠭ W3. - ---- - -## mdview.c (Phase 1, ᪨) - -```c -#include -#include -#include -#include -#include -#include -#include - -#define VIEW_TOP 1 -#define VIEW_BOT 30 // inclusive -#define VIEW_H 30 -#define SCREEN_W 80 -#define MAX_LINES 2048 -#define FILE_BUF ((char*)0xC000) /* W3 - EMM page mapped here */ -#define TAB_STOP 4 - -#define ATTR_TEXT COLOR(COLOR_WHITE, COLOR_BLACK) -#define ATTR_BAR COLOR(COLOR_WHITE, COLOR_BLUE) -#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLUE) -#define ATTR_MENU_T COLOR(COLOR_WHITE, COLOR_BLUE) - -static uint16_t line_off[MAX_LINES]; -static uint16_t n_lines; -static uint16_t top_line; -static uint16_t file_size; -static uint8_t file_blk; -static char filename[64]; - -static int load_file(const char *path); // open, alloc EMM page, map W3, read, close -static void index_lines(void); // scan FILE_BUF, fill line_off[] -static void render_status(void); // row 0 -static void render_menu(void); // row 31 -static void render_line(uint16_t idx, uint8_t row); // one line @ row -static void render_viewport(void); // VIEW_H lines starting from top_line -static void scroll_up(uint16_t n); -static void scroll_down(uint16_t n); -static void help_screen(void); // F1 - -int main(int argc, char **argv) { - if (argc < 2) { puts("Usage: mdview "); return 1; } - if (load_file(argv[1]) < 0) { puts("load error"); return 1; } - index_lines(); - clrscr_attr(ATTR_TEXT); - render_menu(); - render_status(); - render_viewport(); - for (;;) { - uint16_t k = getkey(); - uint8_t ascii = k & 0xFF; - uint8_t scan = (k >> 8) & 0x7F; // strip mod bit - if (ascii) { - if (ascii == 0x1B) break; // Esc -> exit - continue; - } - switch (scan) { - case KEY_F10: goto exit; - case KEY_F1: help_screen(); break; - case KEY_UP: scroll_up(1); break; - case KEY_DOWN: scroll_down(1); break; - case KEY_PGUP: scroll_up(VIEW_H); break; - case KEY_PGDN: scroll_down(VIEW_H); break; - case KEY_HOME: top_line = 0; - render_viewport(); break; - case KEY_END: /* clamp to last viewport */ break; - } - render_status(); - } -exit: - mem_free_block(file_blk); - clrscr_attr(ATTR_TEXT); - return 0; -} -``` - ---- - -## Verification - -### Phase 1 -1. ⮢ `SAMPLE.MD` ~5 KB (, , ᯨ᪨) - ७ 㤥 plain. -2. `cd examples/mdview && make` -3. `python make_disk.py mdview.exe SAMPLE.MD -> mc.img && ./run_mame.sh` -4. ஢: - - Status bar 뢠 `SAMPLE.MD L 1-30 / N X%` - - Menu bar - - ^/V: 1 ப - - PgUp/PgDn: 30 ப, ४⭮ clamp ࠭ - - Home: top_line=0 - - End: top_line = total_lines - VIEW_H - - F1: 뢠 help,  頥 - - F10 / Esc: 室, ࠭ 饭 -5. Edge cases: ⮩ 䠩, 䠩 ப, 䠩 祭 ப (>80), CRLF LF mixed. - -### Phase 2-4 - `SAMPLE.MD` 砬 , 㠫쭮 ஢ MAME. ਭ-ࠢ 樮쭮. - -### ᨨ -- libc Phase 1 ஬ (⥭樠쭮) `getkey()` `libc/conio/conio.c` - ᫨ ⠪, ண `examples/conio2` `examples/filetest` ⮡ 㡥 祣 ᫮. - ---- - -## 襭 筮 - -1. **Word-wrap vs truncate**: v1 = truncate (). v2 - F2 toggle wrap. -2. **ਧ⠫ ஫**: v1 - ; v2 - `<-/->` ᤢ viewport ⮫栬. -3. **Tab handling**: ८ࠧ ७, **tabstop = 4** (⠭ MD). ਣ W3 ண. -4. **UTF-8**: ७ਬ . ᫨ 䠩 CP866 - ਫ楩 १ ⥬ 䮭. UTF-8 - ন (㠫쭮 㤥 -ASCII ᨬ; ⥪ ।० - v2). - ---- - -### Phase 6 - layout ⠡ (deferred) - - (Phase 4-tables) ⠡ "⨢" - - box-drawing chars, ਭ -室. Phase 6 - ⠡ ଠ : - -- **Pre-scan ⠡**: ன ப table-, - ᨬ ਭ ( ⮬ ꥤ inline-થ஢ - - 㠫 ࠧ, ⮢). -- **Re-emit **: 㧪 䠩 ( ࢮ ⠡) - ९ ப FILE_BUF ⠪, ⮡ 祩 - ਭ; top/bottom ࠬ (`Ŀ` / ``) - ᨭ᪨ ப. ࠭ 1:1 ᮮ⢥⢨ "᪠ - ப -> viewport row" ᯥ樠쭮 ७. -- ****: re-emit 䠩 padding .ࠬ. - ᫨ 16KB - १ ⠡ overflow'. -- **ਧ⠫ ஫**: ᫨ ⮣ ਭ ⠡ (  - ப) > SCREEN_W = 80 - <-/-> horizontal pan. 㤥 - 騩 堭 ப (. ⠪ wrap mode), ⮫쪮 ⠡. -- **ࠢ separator-row**: `:-` -> left, `-:` -> right, `:-:` -> - center; 뢠 padding' ᮤন 祩. -- ** ॠ樨**: - 1. Walking pass 䥭ᠬ/⠡栬 אַ `index_lines()` - ᮡ - extents ⠡. - 2. ⠡ - । ਭ . - 3. 襭: rewrite-in-buffer ( ७, 室) - vs render-time layout (cleaner, ॡ ⤥쭮 - ᠭ layout' ⠡). - 4. Hpan: 騩 `viewport_x_offset` ᥣ ࠭, ⤥ - "ப ०" ⮫쪮 ⠡. - -> . ᪠ ⠭ ⥭ ⨯ 筨 -> markdown-䠩 (㧪 ⠫ -> 筮 ⥪饣 ; -> ப README 訬 ⠡栬 -> 㦥 layout). - ---- - -### Phase - ७७ ப (low priority) - -⫮: ⥪ ᪮ 祬 筠. ⨢஢ ᫨  -業਩, প PgUp/PgDn (ਬ, 񫮬 inline- -v2 UTF-8 / linkifier / ⠡栬). - -** ଠ஢ ப** (W3, ⤥쭠 EMM-࠭): - -``` -Cache layout (16 KB EMM page, ᥣ 16000 ᯮ): - slot 0: 80 chars + 80 attrs = 160 bytes @ offset 0 - ... - slot 99: 80 chars + 80 attrs = 160 bytes @ offset 15840 - -Cache tags (W2 static): uint16_t cache_tag[100] = 200 bytes - cache_tag[i] = line_id, 0xFFFF = invalid -``` - -⥣ - **direct map (no LRU)**: `slot = line_id % 100`. -> ᭥. - -**Batched viewport render**: 2 page-swap' viewport (cache -> file -> cache), - 60 ॠ樨. - - ᡮથ: `cache_blk = mem_alloc_pages(1)` ᫥ `file_blk`; `mem_free_block` exit. - ---- - -## ⫮ v2 - -- ~~ >16 KB~~ - **ᤥ v1.5** ( 128 KB १ 1-8 EMM-࠭ + lazy map W3). -- ~~Word wrap~~ - **ᤥ** (Phase 5, F2 toggle). -- Search (Find / Find next) - F3 / F4. -- F8 Raw / Render toggle - ᯥ䨪 Phase 8. -- Links `[text](url)` + image alt - Phase 7. -- Tables - Phase 6 ( layout). -- Toggle highlight on/off - 砩 F8 Raw. diff --git a/examples/mdview/README.MD b/examples/mdview/README.MD new file mode 100644 index 0000000..f0d40d8 --- /dev/null +++ b/examples/mdview/README.MD @@ -0,0 +1,133 @@ +# MDView — Просмотрщик Markdown для Sprinter + +**MDView** — программа для просмотра документов в формате *Markdown* на компьютере +Sprinter (процессор Z80). Документ хранится в отдельном W3 окне и не занимает +основную RAM программы. + +## Возможности + +- Документы до **128 КБ** (8 страниц EMM по 16 КБ каждая) +- До **16 384** экранных строк в индексе +- Автоматический перенос слов по ширине экрана (80 столбцов) +- Горизонтальный сдвиг для широких строк (блоки кода, таблицы) +- Статус-бар: имя файла, диапазон строк, процент прокрутки +- Спиннер в строке состояния во время загрузки и индексации +- Поддержка «мягкого» склеивания строк в абзацах и цитатах + +## Запуск + +``` +mdview [имя_файла.md] +``` + +Если имя файла не задано, загружается `README.MD`. + +## Управление + +``` +Клавиша Действие +───────────── ──────────────────────────────────────── +Up Down Прокрутка на одну строку вверх / вниз +PgUp PgDn Прокрутка на страницу (30 строк) +Home Начало документа +End Конец документа +Left Right Горизонтальный сдвиг (только nowrap-строки) +F1 Окно справки +F10 / Esc Выход из программы +``` + +## Синтаксис Markdown + +### Заголовки + +Поддерживаются уровни H1–H4. Уровни H5 и H6 отображаются как H4. + + # Заголовок первого уровня + ## Заголовок второго уровня + ### Заголовок третьего уровня + #### Заголовок четвёртого уровня + +### Текстовое форматирование + +**Жирный текст** выделяется двойными звёздочками: `**текст**` + +*Курсив* выделяется одиночными звёздочками `*текст*` или знаком подчёркивания `_текст_` + +`Встроенный код` обозначается обратными кавычками + +~~Зачёркнутый текст~~ — двойные тильды: `~~текст~~` + +### Ненумерованный список + +Маркеры `-`, `*` или `+`: + +- Первый пункт списка +- Второй пункт списка +- Третий пункт с достаточно длинным текстом, который при необходимости + будет перенесён на следующую строку с сохранением отступа + +### Нумерованный список + +1. Первый элемент +2. Второй элемент +3. Третий элемент + +### Цитата + +> Блок цитаты начинается с символа `>`. Несколько последовательных +> строк одной цитаты склеиваются в единый абзац с автоматическим +> переносом слов. + +### Блок кода (verbatim) + +Блок кода заключается в тройные обратные кавычки. Внутри блока +текст отображается «как есть» без разбора Markdown: + +``` +#include +#include + +int main(void) { + puts("Hello, Sprinter!"); + return 0; +} +``` + +### Горизонтальная линия + +Три или более символов `---`, `***` или `___` на отдельной строке: + +--- + +## Технические характеристики + +- **Платформа:** Sprinter, процессор Z80 @ 21 МГц +- **Кодировка:** CP866 (DOS Cyrillic) +- **Максимальный размер файла:** 128 КБ +- **Максимальное число строк в индексе:** 16 384 +- **Режим памяти:** small + - Код программы, cтек, данные, куча — окнa W1-W2 (32 КБ, адреса 0x4000–0xBFFF). + - Буфер файла — страницы EMM, отображаемые в W3 (0xC000–0xFFFF) + +## TODO + +1. **Увеличение размера документов.** Снять лимит 128 КБ: Достаточно + разрешить работать с большим кол-вом страниц памяти, пока оттестированно + на работе с 8-мю страницами по 16Кб. + +2. **Форматированные таблицы.** Разбирать строки вида `| ячейка | ячейка |` + с автоматическим выравниванием столбцов и отрисовкой разделительных + линий (строки `|---|---|`). На текущий момент таблицы отображаются + как обычные nowrap-строки без выравнивания. + +3. **Поддержка кодировок CP1251 и UTF-8.** Автоопределение кодировки + по BOM, явное указание через аргумент командной строки (`--encoding cp1251`), + возможность переключения кодировки во время просмотра (`F8`). + Нужно прежде всего для документов на русском языке; CP866 кодировака уже поддерживается. + +4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода + символов через BIOS WRCHAR (пакетный вывод, DMA). + +--- + +*MDView v0.2 · (c) 2026 Петров А.Г.* diff --git a/examples/mdview/SAMPLE.MD b/examples/mdview/SAMPLE.MD deleted file mode 100644 index 130155c..0000000 --- a/examples/mdview/SAMPLE.MD +++ /dev/null @@ -1,5 +0,0 @@ -MDVIEW Sample Document - -This is a sample Markdown file for testing the Sprinter *mdview text -viewer*. Phase 3 adds inline emphasis: bold, italic and underscore -runs render with distinct background colours. diff --git a/examples/mdview/mdview.c b/examples/mdview/mdview.c index bbe6e34..0a0bbe5 100644 --- a/examples/mdview/mdview.c +++ b/examples/mdview/mdview.c @@ -87,6 +87,20 @@ static const uint8_t md_pallete[16 * 4] = { #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 ATTR_HELP_HINT COLOR(COLOR_YELLOW, COLOR_BLACK) + +/* Геометрия диалога справки (в символьных координатах 80×32). */ +#define HELP_X 8u /* левая граница рамки */ +#define HELP_Y 4u /* верхняя граница рамки */ +#define HELP_W 64u /* ширина рамки (включая │) */ +#define HELP_H 22u /* высота рамки (включая ─) */ + /* ---- Глобальное состояние ---------------------------------------- */ /* Файл до 128 КБ хранится в наборе EMM-страниц (до 8 шт. по 16 КБ). @@ -1662,6 +1676,86 @@ static void die(const char *msg) (void)getkey(); } +/* ================================================================== + * Диалог справки (F1) + * ================================================================== */ + +/* Заполняет одну внутреннюю строку диалога (r=0 — первая строка за рамкой). + * Строка s в кодировке CP866; остаток до края дополняется пробелами. */ +static void help_line(uint8_t r, const char *s, uint8_t attr) +{ + uint8_t x = HELP_X + 1u; + uint8_t y = HELP_Y + 1u + r; + uint8_t i = 0; + while (s[i] && i < HELP_W - 2u) { + wrchar((uint8_t)(x + i), y, s[i], attr); + i++; + } + while (i < HELP_W - 2u) { + wrchar((uint8_t)(x + i), y, ' ', ATTR_HELP_BG); + i++; + } +} + +static void show_help(void) +{ + /* " Помощь " в CP866 (8 байт) */ + static const char title[] = " \x8F\xAE\xAC\xAE\xE9\xEC "; + uint8_t tlen = 8u; + uint8_t lft = (uint8_t)((HELP_W - 2u - tlen) / 2u); /* = 27 */ + uint8_t rgt = (uint8_t)(HELP_W - 2u - tlen - lft); + uint8_t cx, i; + + /* Верхняя граница рамки с заголовком по центру */ + wrchar(HELP_X, HELP_Y, 0xDA, ATTR_HELP_BDR); /* ┌ */ + cx = HELP_X + 1u; + for (i = 0; i < lft; i++, cx++) wrchar(cx, HELP_Y, 0xC4, ATTR_HELP_BDR); + for (i = 0; i < tlen; i++, cx++) wrchar(cx, HELP_Y, title[i], ATTR_HELP_TIT); + for (i = 0; i < rgt; i++, cx++) wrchar(cx, HELP_Y, 0xC4, ATTR_HELP_BDR); + wrchar(HELP_X + HELP_W - 1u, HELP_Y, 0xBF, ATTR_HELP_BDR); /* ┐ */ + + /* Боковые границы (левый и правый │ для каждой строки тела) */ + for (uint8_t r = 1u; r < HELP_H - 1u; r++) { + wrchar(HELP_X, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */ + wrchar(HELP_X + HELP_W - 1u, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */ + } + + /* Нижняя граница рамки */ + wrchar(HELP_X, HELP_Y + HELP_H - 1u, 0xC0, ATTR_HELP_BDR); /* └ */ + for (i = 1u; i < HELP_W - 1u; i++) + wrchar(HELP_X + i, HELP_Y + HELP_H - 1u, 0xC4, ATTR_HELP_BDR); + wrchar(HELP_X + HELP_W - 1u, HELP_Y + HELP_H - 1u, 0xD9, ATTR_HELP_BDR); /* ┘ */ + + /* Содержимое (20 внутренних строк) */ + uint8_t r = 0; + help_line(r++, "", ATTR_HELP_BG); + help_line(r++, " MDView v0.2 -- Markdown Viewer for Sprinter", ATTR_HELP_HDR); + help_line(r++, " (c) 2026 Petrov A.G.", ATTR_HELP_BG); + help_line(r++, "", ATTR_HELP_BG); + help_line(r++, " Navigation:", ATTR_HELP_HDR); + help_line(r++, " \x18 \x19 Scroll one line up / down", ATTR_HELP_BG); + help_line(r++, " PgUp PgDn Scroll one page up / down", ATTR_HELP_BG); + help_line(r++, " Home End Jump to beginning / end of document", ATTR_HELP_BG); + help_line(r++, " \x1B \x1A Horizontal pan (code blocks/tables)", ATTR_HELP_BG); + help_line(r++, " Esc F10 Exit", ATTR_HELP_BG); + help_line(r++, "", ATTR_HELP_BG); + help_line(r++, " Markdown elements:", ATTR_HELP_HDR); + help_line(r++, " # ## ### Headings H1-H6", ATTR_HELP_BG); + help_line(r++, " **bold** *italic* `code` ~~strike~~", ATTR_HELP_BG); + help_line(r++, " > quote ``` ... ``` Fenced code block", ATTR_HELP_BG); + help_line(r++, " - * + item 1. 2. Ordered list", ATTR_HELP_BG); + help_line(r++, " |----|----| Tables", 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++, "", ATTR_HELP_BG); + + (void)getkey(); + + render_full_status(); + render_viewport(); + render_menu(); +} + /* ================================================================== * Точка входа * ================================================================== */ @@ -1669,8 +1763,23 @@ static void die(const char *msg) int main(int argc, char **argv) { const char *path; - if (argc >= 2) path = argv[1]; - else path = "SAMPLE.MD"; + if (argc >= 2) { + path = argv[1]; + } else { + path = "README.MD"; + /* Дефолтный файл отсутствует — выводим подсказку без смены режима экрана. */ + { + int chk = open(path, O_RDONLY); + if (chk < 0) { + cputs("MDView v0.2 -- Markdown Viewer for Sprinter\r\n"); + cputs("(c) 2026 Petrov A.G.\r\n\r\n"); + cputs("Usage: mdview \r\n"); + cputs(" Displays a Markdown document (CP866, up to 128 KB).\r\n"); + return 1; + } + close(chk); + } + } /* Копируем путь в static filename[] для отображения в статус-баре. */ { @@ -1730,6 +1839,7 @@ int main(int argc, char **argv) continue; } switch (scan) { + case KEY_F1: show_help(); break; case KEY_F10: goto exit_loop; case KEY_UP: scroll_up(1); break; case KEY_DOWN: scroll_down(1); break; diff --git a/lib/Makefile b/lib/Makefile index c31b3cf..b64b6e1 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -25,8 +25,10 @@ LIBC_C := \ libc/sys/atexit.c \ libc/conio/conio.c \ libc/conio/cprintf.c \ + libc/conio/text_palette.c \ libc/io/dir.c \ libc/video/videomode_raw.c \ + libc/video/palette.c \ libc/errno/_errno_set.c \ libc/env/env.c \ libc/errno/errno.c \ @@ -42,8 +44,10 @@ LIBC_C := \ libc/io/stat.c \ libc/mem/bank_io_w3.c \ libc/mem/bank_io_w1.c \ - libc/mem/mem_alloc.c \ + libc/mem/mem_estex.c \ + libc/mem/mem_bios.c \ libc/gfx/gfx_core.c \ + libc/gfx/gfx_palette.c \ libc/gfx/gfx_raw_common.c \ libc/gfx/gfx_raw_256.c \ libc/gfx/gfx_raw_16.c \ diff --git a/libc/conio/conio.c b/libc/conio/conio.c index 0bb754e..0b1373a 100644 --- a/libc/conio/conio.c +++ b/libc/conio/conio.c @@ -356,6 +356,11 @@ char cputs(const char *s) __naked or a, l ret z + ;; IX is callee-saved under SDCC's z80 ABI. We use it as a + ;; function pointer below (ld ix, #__raw_putch_raw0/1) so save + ;; the caller's value up front and restore before every ret. + push ix + ;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0 ld a, (_g_text_attr + 1) or a, a @@ -398,11 +403,13 @@ char cputs(const char *s) __naked _cputs_loop_end: call __set_cursor + pop ix ; restore caller's IX xor a, a ; return 0 ret _cputs_fast: call __cputs_pchars + pop ix ; restore caller's IX xor a, a ; return 0 ret __endasm; diff --git a/libc/gfx/gfx_core.c b/libc/gfx/gfx_core.c index 7df4123..9157fc8 100644 --- a/libc/gfx/gfx_core.c +++ b/libc/gfx/gfx_core.c @@ -13,7 +13,7 @@ * gfx_set_draw_page / get — updates _gfx_addr_base for the new page * gfx_set_bank / get — sets the W3 page byte (0x50..0x5F) * gfx_wait_vsync — EI; HALT until next frame interrupt - * gfx_pal_load / gfx_pal_set — BIOS $A4 PIC_SET_PAL wrappers + * (palette wrappers live in gfx_palette.c, backed by libc/video/palette.c) * * Shared state (extern from this file): * _gfx_addr_base — 0xC000 for page 0, 0xC140 for page 1. Every @@ -136,43 +136,6 @@ void gfx_wait_vsync(void) __naked __endasm; } -/* ---- Palette (BIOS $A4 PIC_SET_PAL) ----------------------------- */ - -static uint8_t pal_num_; -static uint8_t pal_start_; -static uint8_t pal_count_; -static uint16_t pal_data_; - -void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count, - const uint8_t *data) -{ - pal_num_ = pal_num; - pal_start_ = start; - pal_count_ = count; - pal_data_ = (uint16_t)(uintptr_t)data; - - __asm - push ix - ld a, (_pal_start_) - ld e, a ; E = start - ld a, (_pal_count_) - ld d, a ; D = count (0 → 256) - ld hl, (_pal_data_) ; HL = data - ld b, #0xFF ; mask - ld a, (_pal_num_) ; A = palette number - ld c, #0xA4 ; BIOS PIC_SET_PAL - rst #0x08 - pop ix - __endasm; -} - -void gfx_pal_set(uint8_t pal_num, uint8_t idx, - uint8_t r, uint8_t g, uint8_t b) -{ - uint8_t entry[4]; - entry[0] = b; - entry[1] = g; - entry[2] = r; - entry[3] = 0; - gfx_pal_load(pal_num, idx, 1, entry); -} +/* Palette wrappers (gfx_pal_load / gfx_pal_set / gfx_pal_get / + * gfx_pal_get_color / gfx_pal_reset) moved to gfx_palette.c — see also + * libc/video/palette.c for the shared low-level $A4 / $A6 implementation. */ diff --git a/libc/include/conio.h b/libc/include/conio.h index fc6e281..d3c57fe 100644 --- a/libc/include/conio.h +++ b/libc/include/conio.h @@ -201,4 +201,36 @@ enum { #define COLOR_BLINK 0x80u #define COLOR(fg, bg) ((uint8_t)((((bg) & 0x07) << 4) | ((fg) & 0x0F))) +/* Text-mode palette. The 16 logical CGA colours seen by COLOR(fg, bg) + * actually live in four 256-entry hardware palette planes indexed by the + * full 8-bit attribute byte: + * + * TEXT_PAL_PAPER — background colour, non-blink phase + * TEXT_PAL_INK — foreground colour, non-blink phase + * TEXT_PAL_BLINK_PAPER — background colour during the blink half-cycle + * TEXT_PAL_BLINK_INK — foreground colour during the blink half-cycle + * + * For non-blinking attributes (bit 7 = 0) all four planes display the + * same colours, so writing to PAPER/INK is enough. For blinking attrs + * (bit 7 = 1) the renderer alternates between the non-blink and blink + * planes — that's how flash is implemented in hardware. + * + * These wrappers add 4 to the plane index and forward to the low-level + * API (pal_load / pal_set_color / pal_get / pal_get_color). + * Use text_pal_reset() to restore the system default CGA palette. */ +#define TEXT_PAL_PAPER 0 +#define TEXT_PAL_INK 1 +#define TEXT_PAL_BLINK_PAPER 2 +#define TEXT_PAL_BLINK_INK 3 + +void text_pal_load (uint8_t plane, uint8_t start, uint8_t count, + const uint8_t *bgr0); +void text_pal_set_color(uint8_t plane, uint8_t attr, + uint8_t r, uint8_t g, uint8_t b); +void text_pal_get (uint8_t plane, uint8_t start, uint8_t count, + uint8_t *bgr0); +void text_pal_get_color(uint8_t plane, uint8_t attr, + uint8_t *r, uint8_t *g, uint8_t *b); +void text_pal_reset (void); + #endif diff --git a/libc/include/gfx.h b/libc/include/gfx.h index 6485c90..f4004d0 100644 --- a/libc/include/gfx.h +++ b/libc/include/gfx.h @@ -145,4 +145,15 @@ void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count, void gfx_pal_set (uint8_t pal_num, uint8_t idx, uint8_t r, uint8_t g, uint8_t b); +/* Read a contiguous block of entries back from a graphics palette. */ +void gfx_pal_get (uint8_t pal_num, uint8_t start, uint8_t count, + uint8_t *data); + +/* Read one entry into R, G, B pointers (any may be NULL). */ +void gfx_pal_get_color(uint8_t pal_num, uint8_t idx, + uint8_t *r, uint8_t *g, uint8_t *b); + +/* Restore the system default graphics palette (BIOS $A6, type=1). */ +void gfx_pal_reset(void); + #endif diff --git a/libc/include/sprinter_mem.h b/libc/include/sprinter_mem.h index ebb8eea..825bae8 100644 --- a/libc/include/sprinter_mem.h +++ b/libc/include/sprinter_mem.h @@ -47,23 +47,47 @@ * n : 1..255 * ret : blk_id (1..255) on success; 0 on failure with errno set. * The id is opaque — pass it to mem_get_page() and mem_free_block(). */ -uint8_t mem_alloc_pages(uint8_t n); +uint8_t mem_alloc_pages_estex(uint8_t n); +uint8_t mem_alloc_pages_bios(uint8_t n); /* Release a block previously returned by mem_alloc_pages(). * On error errno is set (e.g. EINVAL for unknown id). Double-free is * NOT idempotent: the second call sets errno. */ -void mem_free_block(uint8_t blk_id); +void mem_free_block_estex(uint8_t blk_id); +void mem_free_block_bios(uint8_t blk_id); /* Translate (block, page-index) into a physical page number suitable * for sprinter_page_w1/w2/w3() or the bank_*() helpers below. * blk_id: from mem_alloc_pages() * idx : 0..(n-1) * ret : physical page (1..255) on success; 0 on failure (errno set). */ -uint8_t mem_get_page(uint8_t blk_id, uint8_t idx); +uint8_t mem_get_page_bios(uint8_t blk_id, uint8_t idx); /* Query the EMM allocator state. Both pointers must be non-NULL. * Cannot fail (no error path). */ -void mem_info(uint16_t *total, uint16_t *free_pages); +void mem_info_estex(uint16_t *total, uint16_t *free_pages); +void mem_info_bios(uint16_t *total, uint16_t *free_pages); + +#define MEM_MANAGE_MODE_BIOS + +#ifdef MEM_MANAGE_MODE_ESTEX + +#define mem_alloc_pages mem_alloc_pages_estex +#define mem_free_block mem_free_block_estex +#define mem_info mem_info_estex + +#define mem_get_page mem_get_page_bios + +#elif defined MEM_MANAGE_MODE_BIOS + +#define mem_alloc_pages mem_alloc_pages_bios +#define mem_free_block mem_free_block_bios +#define mem_info mem_info_bios + +#define mem_get_page mem_get_page_bios + +#endif + /* =================================================================== * Far-page accessors via window 3 (base 0xC000, port 0xE2) diff --git a/libc/mem/mem_alloc.c b/libc/mem/mem_alloc.c deleted file mode 100644 index 87903a2..0000000 --- a/libc/mem/mem_alloc.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * mem_alloc_pages / mem_free_block / mem_get_page / mem_info — ESTEX EMM - * wrappers for explicit 16 KB-page allocation. - * - * ESTEX $3C INFOMEM → HL=total pages, BC=free pages - * ESTEX $3D GETMEM B=npages → A=block id, CF=err - * ESTEX $3E FREEMEM A=block id → CF=err - * BIOS $C4 EMM_GETPAGE A=blk, B=idx → A=physical page CF=err - * - * Pattern: every RST 10h / RST 8 is bracketed with push/pop IX because - * ESTEX/BIOS clobber it and the C caller uses it as a frame pointer. - */ - -#include -#include - -/* - * Allocate `n` contiguous 16-KB physical pages from the EMM pool. - * - * in: n — 1..255 (number of pages requested). - * out: blk_id (1..255) on success; 0 on failure with errno set. - * - * The returned block id is opaque — pass it to mem_get_page() to obtain - * each physical-page number, and to mem_free_block() when done. Block - * ids start at 1; id 0 is reserved as the "allocation failed" sentinel. - */ -uint8_t mem_alloc_pages(uint8_t n) __naked -{ - (void)n; - __asm - ;; SDCC single-uint8 arg → A on entry; ESTEX GETMEM wants n in B. - push ix - ld b, a - ld c, #0x3D ; ESTEX GETMEM - rst #0x10 - pop ix - jr c, _alloc_fail - ret ; CF=0 → A = blk_id, return as uint8 in A - _alloc_fail: - call __errno_set ; CF=1 → A = ESTEX errcode - xor a, a ; return 0 = failure sentinel - ret - __endasm; -} - -/* - * Release a block previously returned by mem_alloc_pages(). - * - * in: blk_id (1..255). - * out: void; on ESTEX error errno is set (e.g. EINVAL for unknown id). - * - * Idempotent guarantees are NOT provided — freeing the same block twice - * sets errno on the second call. Caller is responsible for tracking - * ownership. - */ -void mem_free_block(uint8_t blk_id) __naked -{ - (void)blk_id; - __asm - ;; SDCC single-uint8 arg → A on entry. - push ix - ld c, #0x3E ; ESTEX FREEMEM - rst #0x10 - pop ix - ret nc ; CF=0 → success - jp __errno_set ; CF=1 → A = ESTEX errcode; tail-call helper - __endasm; -} - -/* - * Translate a (block, page-index) pair into a physical 16-KB page number, - * suitable for OUT to PORT_PAGE_W1/W2/W3 or for bank_*() helpers. - * - * in: blk_id — id returned by mem_alloc_pages(). - * idx — 0..(n-1), where n was the count passed to alloc. - * out: physical page number (1..255) on success; - * 0 on failure with errno set (invalid block or idx out of range). - */ -uint8_t mem_get_page(uint8_t blk_id, uint8_t idx) __naked -{ - (void)blk_id; (void)idx; - __asm - ;; 2-arg uint8/uint8: blk_id → A, idx → L. - push ix - ld b, l ; BIOS wants idx in B - ;; A still has blk_id - ld c, #0xC4 ; BIOS EMM_GETPAGE - rst #0x08 - pop ix - ret nc ; CF=0 → A = phys page (return value) - ;; CF=1 → A = errcode; set errno, return 0 as sentinel. - call __errno_set - xor a, a - ret - __endasm; -} - -/* - * Query the EMM allocator about its current state. - * - * *total ← number of 16-KB physical pages installed in the system - * *free_pages ← number currently available for allocation - * - * Both pointers must be non-NULL writeable uint16_t locations. - * No error path: ESTEX INFOMEM always succeeds. - */ -void mem_info(uint16_t *total, uint16_t *free_pages) __naked -{ - (void)total; (void)free_pages; - __asm - ;; HL = total ptr, DE = free_pages ptr on entry. - ;; RST 10 clobbers both — stash on the stack across the call. - push ix - push hl ; later [SP+2] = total_ptr - push de ; TOS [SP+0] = free_pages_ptr - - ld c, #0x3C ; ESTEX INFOMEM → HL = total, BC = free - rst #0x10 - - pop de ; DE = free_pages_ptr - ld a, c - ld (de), a - inc de - ld a, b - ld (de), a ; *free_pages = BC - - pop de ; DE = total_ptr - ld a, l - ld (de), a - inc de - ld a, h - ld (de), a ; *total = HL - - pop ix - ret - __endasm; -} diff --git a/tests/bankedbg/bank1.c b/tests/bankedbg/bank1.c index bb9e69a..37b0d1d 100644 --- a/tests/bankedbg/bank1.c +++ b/tests/bankedbg/bank1.c @@ -15,3 +15,17 @@ void bank1_func(int x) __banked putchar('0' + x % 10); putchar('\n'); } + +void bank1_func2(int x) __banked +{ + (void)x; + puts("BANK1-2: hello from a banked function (W1)!"); + puts("BANK1-2: window 1 phys page = "); + hex8(_io_page_w1); /* should be BANK1's phys page */ + putchar('\n'); + putchar('1'); + putchar('='); + putchar('0' + (x / 10) % 10); + putchar('0' + x % 10); + putchar('\n'); +} diff --git a/tests/bankedbg/bank2.c b/tests/bankedbg/bank2.c index 488155f..5f1a87b 100644 --- a/tests/bankedbg/bank2.c +++ b/tests/bankedbg/bank2.c @@ -2,6 +2,8 @@ #include #include +void bank1_func2(int x) __banked; + void bank2_func(int x) __banked { (void)x; @@ -15,4 +17,6 @@ void bank2_func(int x) __banked putchar('0' + (x / 10) % 10); putchar('0' + x % 10); putchar('\n'); + + bank1_func2(10); } diff --git a/tests/hello/hello.c b/tests/hello/hello.c index 7a4a42b..7aa8fb5 100644 --- a/tests/hello/hello.c +++ b/tests/hello/hello.c @@ -18,7 +18,7 @@ int main(void) { errno = -1; int16_t a0 = 0, a1 = 0; - a0 = get_text_attr(); + a0 = get_text_attr(); uint8_t vMode = get_videotextmode(); set_videotextmode(TEXT_MODE_80x32); @@ -52,7 +52,7 @@ int main(void) /* Back to a normal attribute so the goodbye reads cleanly. */ textattr(COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)); - printf("a0=%d a1=%d now=%d errno=%d\n", + cprintf("a0=%d a1=%d now=%d errno=%d\n\r", a0, a1, get_text_attr(), errno); #ifdef DEBUG_RT printf("w2_self_allocated = %u\n", w2_self_allocated); diff --git a/tests/mem_test/mem_test.c b/tests/mem_test/mem_test.c index f993efe..5cf406d 100644 --- a/tests/mem_test/mem_test.c +++ b/tests/mem_test/mem_test.c @@ -16,6 +16,8 @@ * 5. Free the block, show the free-page count again. */ +uint32_t buff[256]; + static void show_mem(const char *label) { uint16_t total, free_pages; @@ -28,6 +30,13 @@ int main(void) { puts("Sprinter page allocator demo"); puts(""); + + memset(buff, 0, sizeof(buff)); + // printf("mem_io_ports_test() = 0x%02X\n", mem_io_ports_test()); + printf("buff size (sizeof) = %u\n", sizeof(buff)); + printf("buff size one element (sizeof) = %u\n", sizeof(buff[0])); + printf("buff first element = %u\n", buff[0]); + printf("buff last element = %u\n", buff[(sizeof(buff) / sizeof(buff[0])) - 1]); show_mem("before:"); diff --git a/tests/mouse/mouse.c b/tests/mouse/mouse.c index ae6b9c4..b0d32d3 100644 --- a/tests/mouse/mouse.c +++ b/tests/mouse/mouse.c @@ -10,6 +10,8 @@ #include #include +mouse_state_t st; + int main(void) { textattr(COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)); @@ -29,7 +31,6 @@ int main(void) mouse_bounds_y(0, 255); mouse_show(); - mouse_state_t st; int last_x = -1, last_y = -1; uint8_t last_btn = 0xFF; /* Sensitivity is a "raw steps per cursor pixel" divider: smaller = @@ -41,11 +42,13 @@ int main(void) mouse_set_sensitivity(sens_x, sens_y); int sens_dirty = 1; + st.x = st.y = 0; st.buttons = 0; + while (1) { mouse_read(&st); if (st.x != last_x || st.y != last_y || st.buttons != last_btn) { gotoxy(0, 6); - cprintf("x=%4u y=%4u text(%2u,%2u) buttons=0x%02X L%c R%c ", + printf("x=%4u y=%4u text(%2u,%2u) buttons=0x%02X L%c R%c ", st.x, st.y, st.x / 8, st.y / 8, st.buttons, @@ -57,19 +60,19 @@ int main(void) } if (sens_dirty) { gotoxy(0, 8); - cprintf("sensitivity horz=%3u vert=%3u ", sens_x, sens_y); + printf("sensitivity horz=%3u vert=%3u ", sens_x, sens_y); sens_dirty = 0; } - if (!kbhit()) continue; - int k = getch(); - if (k == 27) break; /* ESC */ - if (k == '1' && sens_x > 1) { sens_x -= 1; sens_dirty = 1; } - if (k == '2' && sens_x < 254) { sens_x += 1; sens_dirty = 1; } - if (k == '3' && sens_y > 1) { sens_y -= 1; sens_dirty = 1; } - if (k == '4' && sens_y < 254) { sens_y += 1; sens_dirty = 1; } - if (sens_dirty) - mouse_set_sensitivity(sens_x, sens_y); + if (!kbhit()) continue; + int k = getch(); + if (k == 27) break; /* ESC */ + if (k == '1' && sens_x > 1) { sens_x -= 1; sens_dirty = 1; } + if (k == '2' && sens_x < 254) { sens_x += 1; sens_dirty = 1; } + if (k == '3' && sens_y > 1) { sens_y -= 1; sens_dirty = 1; } + if (k == '4' && sens_y < 254) { sens_y += 1; sens_dirty = 1; } + if (sens_dirty) + mouse_set_sensitivity(sens_x, sens_y); } mouse_hide();