Compare commits
15 Commits
0dedc4dac8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bd116d7361 | |||
| 858e5755ad | |||
| f87b52bb7f | |||
| 07c4004bd9 | |||
| 6992c1436e | |||
| 07c398a560 | |||
| 4bed9d3f3f | |||
| 463a058f56 | |||
| 8abc1d6c16 | |||
| 982af12710 | |||
| 394ee3a2cd | |||
| 47c9cd326a | |||
| ca5f30b332 | |||
| 0ad0559fc8 | |||
| 035d93ab51 |
@@ -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
|
||||
|
||||
@@ -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 | |
|
||||
@@ -217,7 +217,6 @@ This repository contains:
|
||||
|
||||
* **Sprinter / Peters Plus** — Иван Мак, Дмитрий Паринов and the original team
|
||||
* **SDCC** — for the underlying Z80 compiler
|
||||
* **z88dk +pps** — Дмитрий M. for paving the way with the first Sprinter target
|
||||
* **MAME** — for the Sprinter Sp2000 emulation
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# MDView: описание работы и тестовые паттерны
|
||||
## Назначение
|
||||
`mdview` — консольный просмотрщик Markdown для текстового режима 80x32.
|
||||
Программа загружает `.MD`-файл, индексирует его на экранные сегменты и рендерит содержимое с учётом переносов, стилей и специальных блоков.
|
||||
|
||||
## Как работает программа
|
||||
### 1) Загрузка файла
|
||||
- На старте вызывается `load_file(path)`.
|
||||
- Если путь не передан, используется `SAMPLE.MD`.
|
||||
- При ошибках загрузки программа показывает причину и завершает работу после нажатия клавиши.
|
||||
|
||||
### 2) Индексация (`index_lines`)
|
||||
- Файл разбирается в массив сегментов для быстрого рендера.
|
||||
- Поддерживаются ключевые типы строк:
|
||||
- заголовки `#..####`,
|
||||
- маркированные/нумерованные списки,
|
||||
- цитаты `>`,
|
||||
- горизонтальные разделители,
|
||||
- fenced code-блоки,
|
||||
- таблицы (nowrap-режим),
|
||||
- обычные параграфы.
|
||||
- Для list/quote/plain используется общая логика сканирования с режимами склейки строк.
|
||||
|
||||
### 3) Рендер (`render_line`, `render_viewport`)
|
||||
- Для каждой видимой строки рисуется префикс (маркер списка, цитаты и т.д.) и текст.
|
||||
- Поддерживаются inline-стили:
|
||||
- `**жирный**`,
|
||||
- `*курсив*`,
|
||||
- `_подчёркнутый_`,
|
||||
- `~~зачёркнутый~~`,
|
||||
- `` `code` ``.
|
||||
- Табы расширяются до фиксированного шага.
|
||||
- Для длинных nowrap-строк применяется горизонтальный сдвиг и индикаторы `<`/`>`.
|
||||
|
||||
### 4) Навигация
|
||||
- `Up/Down` — прокрутка на 1 строку.
|
||||
- `PgUp/PgDn` — прокрутка на экран.
|
||||
- `Home/End` — начало/конец документа.
|
||||
- `Left/Right` — горизонтальная прокрутка nowrap-контента.
|
||||
- `Esc` или `F10` — выход.
|
||||
|
||||
## Сборка и запуск
|
||||
```bash
|
||||
make -C examples/mdview
|
||||
```
|
||||
|
||||
Запуск на целевой системе:
|
||||
```bash
|
||||
mdview.exe <путь_к_файлу.md>
|
||||
```
|
||||
|
||||
Если путь не указан, открывается `SAMPLE.MD`.
|
||||
|
||||
## Тестовые паттерны
|
||||
Ниже набор паттернов для ручной регрессии.
|
||||
|
||||
### P01 — Базовый smoke-тест открытия
|
||||
Вход:
|
||||
- валидный markdown-файл среднего размера.
|
||||
|
||||
Ожидается:
|
||||
- файл открывается без ошибок;
|
||||
- статус-бар и меню отображаются корректно;
|
||||
- прокрутка работает.
|
||||
|
||||
### P02 — Ошибка открытия файла
|
||||
Вход:
|
||||
- несуществующий путь к файлу.
|
||||
|
||||
Ожидается:
|
||||
- сообщение `mdview: cannot load file`;
|
||||
- корректный код причины (`open failed` и т.п.);
|
||||
- ожидание клавиши перед выходом.
|
||||
|
||||
### P03 — Пустой файл
|
||||
Вход:
|
||||
- пустой `.md`.
|
||||
|
||||
Ожидается:
|
||||
- сообщение `mdview: empty file`;
|
||||
- корректное завершение после нажатия клавиши.
|
||||
|
||||
### P04 — Обычный параграф и переносы
|
||||
Шаблон:
|
||||
```md
|
||||
Это длинный абзац для проверки переноса строк в обычном тексте.
|
||||
Вторая строка должна мягко склеиться с первой.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- строки склеиваются как единый параграф;
|
||||
- переносы происходят по ширине экрана без потери символов.
|
||||
|
||||
### P05 — Hard break в параграфе
|
||||
Шаблон:
|
||||
```md
|
||||
Первая строка с двумя пробелами в конце.
|
||||
Вторая строка после hard break.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- между строками сохраняется принудительный разрыв;
|
||||
- inline-стиль не ломается.
|
||||
|
||||
### P06 — Многострочный список с lazy continuation
|
||||
Шаблон:
|
||||
```md
|
||||
- Пункт списка, который продолжается
|
||||
на следующей строке с отступом.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- continuation-строка склеивается с пунктом через один пробел;
|
||||
- лишние ведущие отступы continuation не попадают в итоговый текст;
|
||||
- переносы не ломают маркер списка.
|
||||
|
||||
### P07 — Многострочная цитата
|
||||
Шаблон:
|
||||
```md
|
||||
> Первая строка цитаты
|
||||
> Вторая строка цитаты
|
||||
> Третья строка цитаты
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- для продолжений корректно повторяется quote-префикс;
|
||||
- сырой символ `>` из исходника не «просачивается» в середину текста.
|
||||
|
||||
### P08 — Пустая quoted-строка как разделитель
|
||||
Шаблон:
|
||||
```md
|
||||
> Первый параграф цитаты
|
||||
>
|
||||
> Второй параграф цитаты
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- пустая quoted-строка разделяет два параграфа;
|
||||
- рендер не объединяет их в один поток.
|
||||
|
||||
### P09 — Таблица в nowrap-режиме
|
||||
Шаблон:
|
||||
```md
|
||||
| col1 | col2 | col3 |
|
||||
|------|------|------|
|
||||
| очень длинное значение | очень длинное значение | очень длинное значение |
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- строка таблицы не переносится по словам;
|
||||
- работает горизонтальный сдвиг `Left/Right`;
|
||||
- индикаторы `<`/`>` показывают скрытый контент.
|
||||
|
||||
### P10 — Fenced code-block
|
||||
Шаблон:
|
||||
~~~md
|
||||
```text
|
||||
**это не жирный**
|
||||
_это не подчёркнутый_
|
||||
```
|
||||
~~~
|
||||
|
||||
Ожидается:
|
||||
- внутри code-блока inline-разметка не применяется;
|
||||
- текст отображается как литерал.
|
||||
|
||||
### P11 — Inline-стили на обычном тексте
|
||||
Шаблон:
|
||||
```md
|
||||
Текст с **жирным**, *курсивом*, _подчёркнутым_, ~~зачёркнутым~~ и `code`.
|
||||
```
|
||||
|
||||
Ожидается:
|
||||
- все перечисленные стили рендерятся корректно;
|
||||
- литеральные случаи вроде `2 * 3` не превращаются в стиль.
|
||||
|
||||
### P12 — Статус-бар и границы прокрутки
|
||||
Вход:
|
||||
- документ на несколько экранов.
|
||||
|
||||
Ожидается:
|
||||
- корректные `L x-y / total` и `%`;
|
||||
- `Home/End` приводят к ожидаемым позициям;
|
||||
- при выходе за границы документа top-line корректно clamp-ится.
|
||||
|
||||
## Рекомендация по регрессии
|
||||
После любых изменений в логике индексации/рендера прогонять минимум:
|
||||
- `P04`, `P05`, `P06`, `P07`, `P09`, `P10`, `P11`, `P12`.
|
||||
@@ -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
|
||||
|
||||
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
|
||||
|
||||
@@ -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 <spinner> <filename>` (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 `` → `[IMG: alt]`
|
||||
|
||||
### Phase 7 — Links и поиск (post-v1)
|
||||
|
||||
- `[text](url)` → отрисовать только `text` с ярко-синим FG (визуально подчёркнутое)
|
||||
- `` → `[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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
|
||||
#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 <file.md>"); 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.
|
||||
@@ -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 <spinner> <filename>` (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 `` -> `[IMG: alt]`
|
||||
|
||||
### Phase 7 - Links ¨ ¯®¨áª (post-v1)
|
||||
|
||||
- `[text](url)` -> ®âà¨á®¢ âì ⮫쪮 `text` á ïમ-ᨨ¬ FG (¢¨§ã «ì® ¯®¤çñàªã⮥)
|
||||
- `` -> `[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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
|
||||
#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 <file.md>"); 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.
|
||||
@@ -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 <stdio.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
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 Петров А.Г.*
|
||||
@@ -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.
|
||||
@@ -1,165 +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.
|
||||
|
||||
---
|
||||
|
||||
## Section: Inline emphasis
|
||||
|
||||
Plain words mixed with **bold words**, *italic words*, _underscore
|
||||
words_ and `code words` to verify all four styles render with their
|
||||
own colours.
|
||||
|
||||
A single **bold** stretch, then a single *italic* stretch, then a single
|
||||
_underscore_ stretch, then a single `code` stretch, all on the same line.
|
||||
|
||||
A *long italic run that spans multiple words and several columns before
|
||||
it closes here* and continues plain.
|
||||
|
||||
Inline code with punctuation: call `printf("%d\n", x)` then check the
|
||||
result; or use `argv[0]` to grab the program name.
|
||||
|
||||
Unclosed emphasis (open **bold left dangling) — should auto-close at the
|
||||
end of the line so the next line starts clean.
|
||||
|
||||
Conflict cases: **bold with a stray * inside** stays bold, and *italic
|
||||
with a stray _ inside* stays italic, and `code with **bold** inside`
|
||||
stays code.
|
||||
|
||||
## Section: Lorem ipsum
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
commodo consequat.
|
||||
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
|
||||
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
|
||||
## Section: Lists
|
||||
|
||||
Unordered (dash):
|
||||
|
||||
- alpha
|
||||
- bravo
|
||||
- charlie
|
||||
|
||||
Unordered (asterisk):
|
||||
|
||||
* delta
|
||||
* echo
|
||||
|
||||
Unordered (plus):
|
||||
|
||||
+ foxtrot
|
||||
+ golf
|
||||
|
||||
Numbered list (digits + period highlighted):
|
||||
|
||||
1. first
|
||||
2. second
|
||||
3. third
|
||||
10. tenth — verifies multi-digit numbering
|
||||
99. ninety-ninth
|
||||
|
||||
Numbered with parens:
|
||||
|
||||
1) parenthesised one
|
||||
2) parenthesised two
|
||||
|
||||
Nested lists (light v1 — leading spaces enable detection at non-zero col;
|
||||
no hanging indent in wrap continuation yet):
|
||||
|
||||
- top level alpha
|
||||
- nested level 1 bravo
|
||||
- nested level 1 charlie
|
||||
- nested level 2 delta
|
||||
- nested level 2 echo
|
||||
- nested level 3 foxtrot
|
||||
- nested level 1 golf
|
||||
- top level hotel
|
||||
|
||||
Mixed nested:
|
||||
|
||||
1. first top
|
||||
1. nested numeric
|
||||
2. nested numeric
|
||||
- mixed bullet under numeric
|
||||
- another bullet
|
||||
3. nested numeric
|
||||
2. second top
|
||||
|
||||
## Section: Blockquote
|
||||
|
||||
> Single-line blockquote.
|
||||
|
||||
> Multi-line blockquote starts here.
|
||||
> Each line keeps the > prefix highlighted
|
||||
> while the body stays plain text.
|
||||
|
||||
## Section: Code
|
||||
|
||||
Inline code: `printf("hello")`.
|
||||
|
||||
Fenced block (lines inside should render with code attribute on the
|
||||
full row, no inline parsing — note the **stars** and *italics* below
|
||||
remain literal):
|
||||
|
||||
```c
|
||||
int main(void) {
|
||||
/* **not bold**, *not italic*, `not nested` */
|
||||
return 42;
|
||||
}
|
||||
```
|
||||
|
||||
After the block, normal **bold** parsing resumes.
|
||||
|
||||
## Section: Long line truncation
|
||||
|
||||
This line is intentionally long to verify that the viewer truncates at 80 columns instead of wrapping or scrolling horizontally. Anything past the 80th visible column should simply not appear on screen.
|
||||
|
||||
## Section: Tabs
|
||||
|
||||
Indented with tabs:
|
||||
|
||||
tab-indent level 1
|
||||
tab-indent level 2
|
||||
tab-indent level 3
|
||||
|
||||
## Section: Blockquote
|
||||
|
||||
> Markdown blockquotes start with a greater-than sign at column zero.
|
||||
> Multiple lines look like this.
|
||||
|
||||
### Subsection: deeper heading (H3)
|
||||
|
||||
The line above is an H3 — it should render in a colour distinct from
|
||||
H1 and H2.
|
||||
|
||||
#### Subsubsection: H4 and below
|
||||
|
||||
H4 (and the rarely-seen H5/H6) all share the H4 colour slot.
|
||||
|
||||
***
|
||||
|
||||
## Section: Filler
|
||||
|
||||
The remaining content exists to make the document scroll past one viewport.
|
||||
|
||||
Line 50 ........... approx.
|
||||
Line 51 ........... .
|
||||
Line 52 ........... ..
|
||||
Line 53 ........... ...
|
||||
Line 54 ........... ....
|
||||
Line 55 ........... .....
|
||||
Line 56 ........... ......
|
||||
Line 57 ........... .......
|
||||
Line 58 ........... ........
|
||||
Line 59 ........... .........
|
||||
Line 60 ........... ..........
|
||||
|
||||
End-of-document marker. If you can see this line you can use Home / End
|
||||
to bounce between the start and finish of the file.
|
||||
+1202
-560
File diff suppressed because it is too large
Load Diff
+5
-1
@@ -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 \
|
||||
|
||||
@@ -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;
|
||||
|
||||
+4
-41
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
* <palette.h> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <stdint.h>
|
||||
#include <sprinter_mem.h>
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include <stdint.h>
|
||||
#include <sprinter.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -29,6 +31,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:");
|
||||
|
||||
uint8_t blk = mem_alloc_pages(3);
|
||||
|
||||
+15
-12
@@ -10,6 +10,8 @@
|
||||
#include <conio.h>
|
||||
#include <mouse.h>
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user