Add mdview markdown viewer, reorganize tests/examples and libc layout
- Split tests/ (libc feature tests) and examples/ (real apps); shared
app.mk in repo root, was examples/example.mk
- libc/io/* split into libc/{conio,env,errno,file,mouse,string,sys,
time,video}/ — clearer module boundaries
- New examples/mdview/: markdown viewer (Phases 1-5 + light nested
lists). Headers (H1-H4), HR, ulist/olist/quote with nesting via
leading spaces, fenced code blocks, inline emphasis (bold/italic/
underscore/code), wrap/unwrap mode with soft wrap (F2), horizontal
pan (← →) with '>' truncation indicator
- libc additions: scroll() in conio (ESTEX SCROLL), strlwr/strupr,
gets() test
- Makefile updates across tests/ for the new shared app.mk path
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+15
-1
@@ -9,7 +9,8 @@ build/
|
||||
# sprinter-cc per-example intermediate directory
|
||||
.sprinter-cc-*/
|
||||
|
||||
# Per-example final/intermediate outputs landing alongside the source
|
||||
# Per-program final/intermediate outputs landing alongside the source
|
||||
# (real apps under examples/ and libc feature tests under tests/).
|
||||
examples/*/*.exe
|
||||
examples/*/*.asm
|
||||
examples/*/*.lst
|
||||
@@ -23,6 +24,19 @@ examples/*/*.cdb
|
||||
examples/*/*.mem
|
||||
examples/*/*.rst
|
||||
|
||||
tests/*/*.exe
|
||||
tests/*/*.asm
|
||||
tests/*/*.lst
|
||||
tests/*/*.lk
|
||||
tests/*/*.ihx
|
||||
tests/*/*.noi
|
||||
tests/*/*.sym
|
||||
tests/*/*.map
|
||||
tests/*/*.rel
|
||||
tests/*/*.cdb
|
||||
tests/*/*.mem
|
||||
tests/*/*.rst
|
||||
|
||||
# libc archive (built from libc/, see lib/Makefile)
|
||||
lib/*.lib
|
||||
|
||||
|
||||
@@ -1,58 +1,41 @@
|
||||
# Sprinter C Compiler — top-level Makefile
|
||||
#
|
||||
# make build host tools, libc archive, and all examples
|
||||
# make build host tools, libc archive, all tests, all apps
|
||||
# make tools build only host tools (mkexe)
|
||||
# make lib build lib/sprinter.lib (libc archive used by sprinter-cc)
|
||||
# make examples build all examples
|
||||
# make floppy package every example + test files into mame/v306/IMG/mc.img
|
||||
# make tests build all libc feature tests under tests/
|
||||
# make examples build all real applications under examples/
|
||||
# make floppy package every .exe + test fixtures into mame/v306/IMG/mc.img
|
||||
# make check run mkexe unit tests
|
||||
# make clean remove all build artefacts
|
||||
# make sdcc download/extract vendored SDCC
|
||||
#
|
||||
# Most heavy lifting is delegated to sub-Makefiles.
|
||||
|
||||
EXAMPLES := hello banked bankedbg strtest cat seek malloc mem_test argv errno rt_test openenv ls conio attrprob timedir mouse banklocl stdlib assrtest ptime stattest filetest gfx_demo gfx_d16 gfx_text gfx_mous
|
||||
# Small libc-feature tests (one program per .c-language feature or libc API).
|
||||
TESTS := hello banked bankedbg strtest cat seek malloc mem_test argv errno \
|
||||
rt_test openenv ls conio attrprob timedir mouse banklocl stdlib \
|
||||
assrtest ptime stattest filetest gfx_demo gfx_d16 gfx_text gfx_mous
|
||||
|
||||
# Larger end-user applications under examples/.
|
||||
APPS := mdview
|
||||
|
||||
MAME_DIR := mame/v306
|
||||
FLOPPY_IMG := $(MAME_DIR)/IMG/mc.img
|
||||
MAKE_DISK := $(MAME_DIR)/make_disk.py
|
||||
|
||||
EXE_FILES := \
|
||||
examples/hello/hello.exe \
|
||||
examples/banked/banked.exe \
|
||||
examples/bankedbg/bankedbg.exe \
|
||||
examples/strtest/strtest.exe \
|
||||
examples/cat/cat.exe \
|
||||
examples/seek/seek.exe \
|
||||
examples/malloc/malloc.exe \
|
||||
examples/mem_test/mem_test.exe \
|
||||
examples/argv/argv.exe \
|
||||
examples/errno/errno.exe \
|
||||
examples/rt_test/rt_test.exe \
|
||||
examples/openenv/openenv.exe \
|
||||
examples/ls/ls.exe \
|
||||
examples/conio/conio.exe \
|
||||
examples/attrprob/attrprob.exe \
|
||||
examples/timedir/timedir.exe \
|
||||
examples/mouse/mouse.exe \
|
||||
examples/banklocl/banklocl.exe \
|
||||
examples/stdlib/stdlib.exe \
|
||||
examples/assrtest/assrtest.exe \
|
||||
examples/ptime/ptime.exe \
|
||||
examples/stattest/stattest.exe \
|
||||
examples/filetest/filetest.exe \
|
||||
examples/gfx_demo/gfx_demo.exe \
|
||||
examples/gfx_d16/gfx_d16.exe \
|
||||
examples/gfx_text/gfx_text.exe \
|
||||
examples/gfx_mous/gfx_mous.exe
|
||||
TEST_EXES := $(foreach t,$(TESTS),tests/$(t)/$(t).exe)
|
||||
APP_EXES := $(foreach a,$(APPS),examples/$(a)/$(a).exe)
|
||||
ALL_EXES := $(TEST_EXES) $(APP_EXES)
|
||||
|
||||
DATA_FILES := \
|
||||
examples/cat/test.txt \
|
||||
examples/seek/big.txt
|
||||
tests/cat/test.txt \
|
||||
tests/seek/big.txt \
|
||||
examples/mdview/SAMPLE.MD
|
||||
|
||||
.PHONY: all tools lib examples check clean sdcc floppy $(EXAMPLES)
|
||||
.PHONY: all tools lib tests examples check clean sdcc floppy $(TESTS) $(APPS)
|
||||
|
||||
all: tools lib examples
|
||||
all: tools lib tests examples
|
||||
|
||||
tools:
|
||||
$(MAKE) -C toolchain/mkexe
|
||||
@@ -63,18 +46,22 @@ lib:
|
||||
check: tools
|
||||
$(MAKE) -C toolchain/mkexe check
|
||||
|
||||
examples: $(EXAMPLES)
|
||||
tests: $(TESTS)
|
||||
examples: $(APPS)
|
||||
|
||||
$(EXAMPLES): tools lib
|
||||
$(TESTS): tools lib
|
||||
$(MAKE) -C tests/$@
|
||||
|
||||
$(APPS): tools lib
|
||||
$(MAKE) -C examples/$@
|
||||
|
||||
# Generate big.txt if missing (gen_bigfile.py creates 100 KB marker file).
|
||||
examples/seek/big.txt:
|
||||
cd examples/seek && python3 gen_bigfile.py big.txt 102400
|
||||
tests/seek/big.txt:
|
||||
cd tests/seek && python3 gen_bigfile.py big.txt 102400
|
||||
|
||||
# Re-pack the MAME floppy image with every built example + needed data files.
|
||||
floppy: examples examples/seek/big.txt
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXE_FILES) $(DATA_FILES)
|
||||
# Re-pack the MAME floppy image with every built exe + needed data files.
|
||||
floppy: tests examples tests/seek/big.txt
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(ALL_EXES) $(DATA_FILES)
|
||||
@echo
|
||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
||||
@@ -82,7 +69,8 @@ floppy: examples examples/seek/big.txt
|
||||
clean:
|
||||
$(MAKE) -C toolchain/mkexe clean
|
||||
$(MAKE) -C lib clean
|
||||
@for e in $(EXAMPLES); do $(MAKE) -C examples/$$e clean; done
|
||||
@for t in $(TESTS); do $(MAKE) -C tests/$$t clean; done
|
||||
@for a in $(APPS); do $(MAKE) -C examples/$$a clean; done
|
||||
|
||||
sdcc:
|
||||
bash third_party/setup-sdcc.sh
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# example.mk — shared Makefile fragment for simple Sprinter ESTEX examples.
|
||||
# app.mk — shared Makefile fragment for any standalone Sprinter ESTEX
|
||||
# program — used both by libc feature tests under tests/ and by real
|
||||
# applications under examples/.
|
||||
#
|
||||
# Usage in an example's Makefile:
|
||||
# Usage in a per-program Makefile:
|
||||
#
|
||||
# PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
# EXAMPLE := my_example # base name (matches my_example.c)
|
||||
# EXAMPLE := my_program # base name (matches my_program.c)
|
||||
#
|
||||
# # Optional overrides (any combination):
|
||||
# # MEMORY := small # tiny | small | big | huge | manual
|
||||
@@ -12,7 +14,7 @@
|
||||
# # EXTRA_FLAGS := --crt0=minimal # passed through to sprinter-cc
|
||||
# # EXTRA_DATA := test.txt # extra files to add to `make floppy`
|
||||
#
|
||||
# include $(PROJ_ROOT)/examples/example.mk
|
||||
# include $(PROJ_ROOT)/app.mk
|
||||
#
|
||||
# Pipeline (all driven by sprinter-cc):
|
||||
# crt0 + EXAMPLE.c + EXTRA_SRCS --sdcc--> .ihx
|
||||
@@ -61,9 +63,9 @@ $(LIB):
|
||||
clean:
|
||||
rm -rf .sprinter-cc-* $(EXAMPLE).exe
|
||||
|
||||
# `make floppy` packs ONLY this example (+ optional EXTRA_DATA files) into
|
||||
# `make floppy` packs ONLY this program (+ optional EXTRA_DATA files) into
|
||||
# the MAME floppy image, replacing whatever was there. Handy for trying a
|
||||
# single program without rebuilding all 27 examples.
|
||||
# single program without rebuilding everything.
|
||||
floppy: $(EXAMPLE).exe
|
||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(EXTRA_DATA)
|
||||
@echo
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mdview
|
||||
MEMORY := small
|
||||
EXTRA_DATA := SAMPLE.MD
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,527 @@
|
||||
# План: текстовый 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*/fread/fgets — не использовать
|
||||
- Максимум 16 KB на один файл (одна EMM-страница); многостраничный режим — в v2
|
||||
- Подсветка через цвет: **размер заголовка** → цвет шрифта; **bold/italic** → цвет фона (моноширинный фонт без жирного/курсивного начертания)
|
||||
|
||||
---
|
||||
|
||||
## Архитектура
|
||||
|
||||
### Раскладка экрана (80×32, текст mode 0x03)
|
||||
|
||||
```
|
||||
Row 0: ┃ mdview.md L 1-30 / 142 21% ┃ ← status bar (BG=blue, FG=white)
|
||||
Row 1: ┃ ┃
|
||||
... ┃ document viewport (30 rows) ┃
|
||||
Row 30: ┃ ┃
|
||||
Row 31: ┃ F1 Help 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-страницами:
|
||||
├── file page: исходный текст .md файла (16 KB)
|
||||
└── cache page: кэш отформатированных строк (Phase 3+)
|
||||
```
|
||||
|
||||
Почему small + W3:
|
||||
- 32 KB на код+данные с большим запасом → нет банкинга
|
||||
- W3 — стандартный paged window, под него у нас уже есть `bank_io_w3` API
|
||||
- v2-расширение (multi-page файлы >16 KB): просто `mem_alloc_pages(N)` и переключение страниц в W3 через `sprinter_page_w3()`
|
||||
|
||||
Статики (в 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: один активный стиль одновременно (без вложенности); конфликтующий маркер при чужом активном стиле уходит литералом. Состояние сбрасывается на каждой строке.
|
||||
|
||||
> **Кэш отформатированных строк** — отложен в самый конец, см. "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" когда выключен).
|
||||
|
||||
**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: многостраничное хранение через `mem_alloc_pages(N)`, ленивая загрузка страниц в W3 при доступе.
|
||||
- Search (Find / Find next) — F3 / F4.
|
||||
- Toggle highlight on/off — F2.
|
||||
- Word wrap.
|
||||
- Image alt-text rendering.
|
||||
- Tables.
|
||||
@@ -0,0 +1,527 @@
|
||||
# �« : ⥪áâ®¢ë© 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*/fread/fgets - ¥ ¨á¯®«ì§®¢ âì
|
||||
- Œ ªá¨¬ã¬ 16 KB ®¤¨ ä ©« (®¤ EMM-áâà ¨æ ); ¬®£®áâà ¨çë© à¥¦¨¬ - ¢ v2
|
||||
- �®¤á¢¥âª ç¥à¥§ 梥â: **à §¬¥à § £®«®¢ª ** > 梥â èà¨äâ ; **bold/italic** > 梥â ä® (¬®®è¨à¨ë© ä®â ¡¥§ ¦¨à®£®/ªãàᨢ®£® ç¥àâ ¨ï)
|
||||
|
||||
---
|
||||
|
||||
## €àå¨â¥ªâãà
|
||||
|
||||
### � ᪫ ¤ª íªà (80x32, ⥪áâ mode 0x03)
|
||||
|
||||
```
|
||||
Row 0: ³ mdview.md L 1-30 / 142 21% ³ < status bar (BG=blue, FG=white)
|
||||
Row 1: ³ ³
|
||||
... ³ document viewport (30 rows) ³
|
||||
Row 30: ³ ³
|
||||
Row 31: ³ F1 Help 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-áâà ¨æ ¬¨:
|
||||
ÃÄÄ file page: ¨áå®¤ë© â¥ªáâ .md ä ©« (16 KB)
|
||||
ÀÄÄ cache page: ªíè ®âä®à¬ â¨à®¢ ëå áâப (Phase 3+)
|
||||
```
|
||||
|
||||
�®ç¥¬ã small + W3:
|
||||
- 32 KB ª®¤+¤ ë¥ á ¡®«ì訬 § ¯ ᮬ > ¥â ¡ ª¨£
|
||||
- W3 - áâ ¤ àâë© paged window, ¯®¤ ¥£® ã á 㦥 ¥áâì `bank_io_w3` API
|
||||
- v2-à áè¨à¥¨¥ (multi-page ä ©«ë >16 KB): ¯à®áâ® `mem_alloc_pages(N)` ¨ ¯¥à¥ª«î票¥ áâà ¨æ ¢ W3 ç¥à¥§ `sprinter_page_w3()`
|
||||
|
||||
‘â ⨪¨ (¢ 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: ®¤¨ ªâ¨¢ë© áâ¨«ì ®¤®¢à¥¬¥® (¡¥§ ¢«®¦¥®áâ¨); ª®ä«¨ªâãî騩 ¬ àª¥à ¯à¨ ç㦮¬ ªâ¨¢®¬ á⨫¥ ã室¨â «¨â¥à «®¬. ‘®áâ®ï¨¥ á¡à áë¢ ¥âáï ª ¦¤®© áâப¥.
|
||||
|
||||
> **Šíè ®âä®à¬ â¨à®¢ ëå áâப** - ®â«®¦¥ ¢ á ¬ë© ª®¥æ, á¬. "Phase ?: ®¯â¨¬¨§ 樨".
|
||||
> ‘ª®à®á⨠⥪ã饣® ¨¢®£® ।¥à å¢ â ¥â 80x30 = 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" ª®£¤ ¢ëª«îç¥).
|
||||
|
||||
**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: ¬®£®áâà ¨ç®¥ åà ¥¨¥ ç¥à¥§ `mem_alloc_pages(N)`, «¥¨¢ ï § £à㧪 áâà ¨æ ¢ W3 ¯à¨ ¤®áâ㯥.
|
||||
- Search (Find / Find next) - F3 / F4.
|
||||
- Toggle highlight on/off - F2.
|
||||
- Word wrap.
|
||||
- Image alt-text rendering.
|
||||
- Tables.
|
||||
@@ -0,0 +1,165 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,909 @@
|
||||
/*
|
||||
* mdview — markdown text viewer for Sprinter (Phase 1: plain text).
|
||||
*
|
||||
* Layout (80x32):
|
||||
* row 0 — status bar (filename / line range / total / %)
|
||||
* rows 1..30 — viewport (30 lines of document text)
|
||||
* row 31 — menu bar (F1 Help ... F10 Exit)
|
||||
*
|
||||
* Navigation: arrows, PgUp/PgDn, Home/End, F1 help, F10/Esc exit.
|
||||
*
|
||||
* Memory:
|
||||
* CODE → W1 (sprinter-cc --memory small)
|
||||
* STACK/HEAP/DATA → W2
|
||||
* File buffer → EMM page mapped into W3 at 0xC000 (≤ 16 KB)
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sprinter.h>
|
||||
#include <sprinter_mem.h>
|
||||
|
||||
/* ---- screen geometry ---------------------------------------------- */
|
||||
|
||||
#define SCREEN_W 80
|
||||
#define SCREEN_H 32
|
||||
#define VIEW_TOP_ROW 1
|
||||
#define VIEW_H 30 /* rows 1..30 inclusive */
|
||||
#define MENU_ROW 31
|
||||
#define TAB_STOP 4
|
||||
|
||||
/* ---- file/memory layout ------------------------------------------- */
|
||||
|
||||
#define MAX_FILE 16384
|
||||
#define MAX_LINES 2048
|
||||
#define FILE_BUF ((char *)0xC000) /* W3-mapped EMM page */
|
||||
|
||||
/* ---- attribute palette -------------------------------------------- */
|
||||
|
||||
#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||
#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||
#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLACK)
|
||||
#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLACK)
|
||||
#define ATTR_TEXT_TITLE4 COLOR(COLOR_DARKGRAY, COLOR_BLACK)
|
||||
#define ATTR_TEXT_BOLD COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BROWN)
|
||||
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTBLUE, COLOR_GREEN)
|
||||
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_LIGHTGRAY)
|
||||
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLACK)
|
||||
#define ATTR_QUOTE_MARKER COLOR(COLOR_LIGHTMAGENTA,COLOR_BLACK)
|
||||
#define ATTR_HR COLOR(COLOR_DARKGRAY, COLOR_BLACK)
|
||||
#define ATTR_TRUNC COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||
#define ATTR_BAR COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||
#define ATTR_MENU_T COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||
|
||||
/* ---- global state ------------------------------------------------- */
|
||||
|
||||
/* line_offset[] semantics depend on wrap_mode:
|
||||
* wrap_mode == 0 (truncate): one entry per logical line, value = byte
|
||||
* offset inside FILE_BUF.
|
||||
* wrap_mode == 1 (wrap): one entry per VISIBLE viewport row. Low
|
||||
* 14 bits = byte offset of segment start;
|
||||
* bit 15 (OFF_CONT) marks a continuation of
|
||||
* the previous logical line — rendered as
|
||||
* plain text without markdown classification.
|
||||
* MAX_FILE = 16 KB so offsets fit in 14 bits.
|
||||
*/
|
||||
#define OFF_MASK 0x3FFFu
|
||||
#define OFF_CONT 0x8000u
|
||||
|
||||
/* NOTE: explicit `= 0` initialisers are MANDATORY on SDCC z80.
|
||||
* Uninitialised static storage (`static T x;`) doesn't actually allocate
|
||||
* space — multiple such declarations end up at the SAME address and
|
||||
* silently overwrite each other. See memory/sdcc_static_storage_gotcha. */
|
||||
static uint16_t line_offset[MAX_LINES] = {0};
|
||||
/* Bitmap: seg i (non-CONT) is inside a fenced code block (between two
|
||||
* ``` lines). The fence delimiter lines themselves are NOT marked here —
|
||||
* they are detected dynamically via a 3-byte prefix check. */
|
||||
static uint8_t in_code[MAX_LINES / 8] = {0};
|
||||
static uint16_t n_lines = 0;
|
||||
static uint16_t top_line = 0;
|
||||
static uint16_t file_size = 0;
|
||||
static uint8_t file_blk = 0;
|
||||
static uint8_t wrap_mode = 1; /* default: wrap on */
|
||||
static uint8_t viewport_x = 0; /* horizontal pan (truncate mode only) */
|
||||
static char filename[64] = {0};
|
||||
|
||||
#define HPAN_STEP 8u
|
||||
|
||||
/* ==================================================================
|
||||
* tiny helpers
|
||||
* ================================================================== */
|
||||
|
||||
static void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr)
|
||||
{
|
||||
while (*s && x < SCREEN_W) {
|
||||
wrchar(x++, y, *s++, attr);
|
||||
}
|
||||
}
|
||||
|
||||
static void put_str(uint8_t x, uint8_t y, const char *s)
|
||||
{
|
||||
gotoxy(x, y);
|
||||
puts(s);
|
||||
}
|
||||
|
||||
static void fill_row(uint8_t y, uint8_t attr)
|
||||
{
|
||||
for (uint8_t c = 0; c < SCREEN_W; c++) {
|
||||
wrchar(c, y, ' ', attr);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* file loading
|
||||
* ================================================================== */
|
||||
|
||||
static int load_file(const char *path)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) return -1;
|
||||
|
||||
long sz = lseek(fd, 0, SEEK_END);
|
||||
if (sz < 0 || sz > MAX_FILE) {
|
||||
close(fd);
|
||||
return -2;
|
||||
}
|
||||
file_size = (uint16_t)sz;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
file_blk = mem_alloc_pages(1);
|
||||
if (file_blk == 0) {
|
||||
close(fd);
|
||||
return -3;
|
||||
}
|
||||
uint8_t phys = mem_get_page(file_blk, 0);
|
||||
sprinter_page_w3(phys);
|
||||
|
||||
int n = read(fd, FILE_BUF, file_size);
|
||||
close(fd);
|
||||
if (n != (int)file_size) {
|
||||
return -4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void unload_file(void)
|
||||
{
|
||||
if (file_blk) {
|
||||
mem_free_block(file_blk);
|
||||
file_blk = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* line indexing
|
||||
* ================================================================== */
|
||||
|
||||
/* Returns 1 iff the seg at index `idx` is the start of a logical line
|
||||
* that begins with ``` (a fenced-code block delimiter). Continuation
|
||||
* segs are never fence delimiters. */
|
||||
static uint8_t is_fence_delim(uint16_t idx)
|
||||
{
|
||||
if (idx >= n_lines) return 0;
|
||||
uint16_t raw = line_offset[idx];
|
||||
if (raw & OFF_CONT) return 0;
|
||||
uint16_t off = raw & OFF_MASK;
|
||||
if ((uint32_t)off + 2 >= (uint32_t)file_size) return 0;
|
||||
return (uint8_t)(FILE_BUF[off] == '`' &&
|
||||
FILE_BUF[off+1] == '`' &&
|
||||
FILE_BUF[off+2] == '`');
|
||||
}
|
||||
|
||||
static uint8_t is_code_body(uint16_t idx)
|
||||
{
|
||||
if (idx >= n_lines) return 0;
|
||||
return (uint8_t)((in_code[idx >> 3] >> (idx & 7)) & 1u);
|
||||
}
|
||||
|
||||
/* Returns 1 iff the logical line at file offset `p_start` should NOT be
|
||||
* wrapped — it must render on a single seg even if longer than SCREEN_W.
|
||||
* Currently covers fence delimiters and horizontal rules; tables will be
|
||||
* added when Phase 4 tables / Phase 6 table layout lands. */
|
||||
static uint8_t is_nowrap_line(uint16_t p_start)
|
||||
{
|
||||
if ((uint32_t)p_start + 2 < (uint32_t)file_size &&
|
||||
FILE_BUF[p_start] == '`' &&
|
||||
FILE_BUF[p_start + 1] == '`' &&
|
||||
FILE_BUF[p_start + 2] == '`') {
|
||||
return 1;
|
||||
}
|
||||
char c0 = FILE_BUF[p_start];
|
||||
if (c0 == '-' || c0 == '*' || c0 == '_') {
|
||||
char marker = 0;
|
||||
uint8_t count = 0;
|
||||
uint8_t ok = 1;
|
||||
uint16_t p = p_start;
|
||||
while (p < file_size) {
|
||||
char ch = FILE_BUF[p++];
|
||||
if (ch == '\n' || ch == '\r') break;
|
||||
if (ch == ' ' || ch == '\t') continue;
|
||||
if (ch != '-' && ch != '*' && ch != '_') { ok = 0; break; }
|
||||
if (marker == 0) marker = ch;
|
||||
else if (ch != marker) { ok = 0; break; }
|
||||
count++;
|
||||
}
|
||||
if (ok && count >= 3) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Line kind returned by classify_line(). */
|
||||
#define LK_PLAIN 0
|
||||
#define LK_H1 1
|
||||
#define LK_H2 2
|
||||
#define LK_H3 3
|
||||
#define LK_H4 4 /* also used for H5/H6 */
|
||||
#define LK_HR 5
|
||||
#define LK_ULIST 6 /* "- ", "* ", "+ " bullet */
|
||||
#define LK_OLIST 7 /* "12. " or "12) " number */
|
||||
#define LK_QUOTE 8 /* "> " blockquote */
|
||||
|
||||
/* Classify a logical line.
|
||||
* - Headers: 1..4 leading '#' followed by space/tab (H5+/H6 collapse to H4).
|
||||
* - Horizontal rule: line containing only '-', '*' or '_' (one kind, >=3
|
||||
* of them), optionally separated by spaces/tabs. Matches `---`, `***`,
|
||||
* `___`, `- - -`, etc. Checked BEFORE ulist so that `- - -` wins.
|
||||
* - Unordered list: "- ", "* " or "+ " followed by content.
|
||||
* - Ordered list: one or more digits followed by '.' or ')' then space.
|
||||
* - Blockquote: '>' optionally followed by space.
|
||||
* - Otherwise plain text.
|
||||
* For headers/lists/quotes, *out_content is updated to the offset of the
|
||||
* first content byte (past the marker and its trailing space).
|
||||
*/
|
||||
static uint8_t classify_line(uint16_t p_start, uint16_t *out_content)
|
||||
{
|
||||
if (p_start >= file_size) return LK_PLAIN;
|
||||
|
||||
char c0 = FILE_BUF[p_start];
|
||||
|
||||
/* Header? */
|
||||
if (c0 == '#') {
|
||||
uint8_t lvl = 0;
|
||||
uint16_t p = p_start;
|
||||
while (p < file_size && FILE_BUF[p] == '#' && lvl < 6) {
|
||||
lvl++; p++;
|
||||
}
|
||||
if (p < file_size && (FILE_BUF[p] == ' ' || FILE_BUF[p] == '\t')) {
|
||||
p++;
|
||||
*out_content = p;
|
||||
if (lvl > 4) lvl = 4;
|
||||
return lvl; /* LK_H1 .. LK_H4 */
|
||||
}
|
||||
/* Bare '#' with no space → not a header, fall through. */
|
||||
}
|
||||
|
||||
/* Horizontal rule? (must precede ulist so `- - -` wins over `- `) */
|
||||
if (c0 == '-' || c0 == '*' || c0 == '_') {
|
||||
char marker = 0;
|
||||
uint8_t count = 0;
|
||||
uint8_t ok = 1;
|
||||
uint16_t p = p_start;
|
||||
while (p < file_size) {
|
||||
char ch = FILE_BUF[p++];
|
||||
if (ch == '\n' || ch == '\r') break;
|
||||
if (ch == ' ' || ch == '\t') continue;
|
||||
if (ch != '-' && ch != '*' && ch != '_') { ok = 0; break; }
|
||||
if (marker == 0) marker = ch;
|
||||
else if (ch != marker) { ok = 0; break; }
|
||||
count++;
|
||||
}
|
||||
if (ok && count >= 3) return LK_HR;
|
||||
}
|
||||
|
||||
/* For lists and blockquotes we additionally allow leading spaces
|
||||
* (light nested-list support). HR / header / fence delim stay
|
||||
* strict col-0 — done above with `c0`. */
|
||||
uint16_t lp = p_start;
|
||||
while (lp < file_size && FILE_BUF[lp] == ' ') lp++;
|
||||
char cl = (lp < file_size) ? FILE_BUF[lp] : 0;
|
||||
|
||||
/* Unordered list? */
|
||||
if ((cl == '-' || cl == '*' || cl == '+') &&
|
||||
(lp + 1) < file_size &&
|
||||
FILE_BUF[lp + 1] == ' ') {
|
||||
*out_content = (uint16_t)(lp + 2);
|
||||
return LK_ULIST;
|
||||
}
|
||||
|
||||
/* Ordered list? */
|
||||
if (cl >= '0' && cl <= '9') {
|
||||
uint16_t p = lp;
|
||||
while (p < file_size && FILE_BUF[p] >= '0' && FILE_BUF[p] <= '9') p++;
|
||||
if (p < file_size && (FILE_BUF[p] == '.' || FILE_BUF[p] == ')') &&
|
||||
(p + 1) < file_size && FILE_BUF[p + 1] == ' ') {
|
||||
*out_content = (uint16_t)(p + 2);
|
||||
return LK_OLIST;
|
||||
}
|
||||
}
|
||||
|
||||
/* Blockquote? */
|
||||
if (cl == '>') {
|
||||
uint16_t p = (uint16_t)(lp + 1);
|
||||
if (p < file_size && FILE_BUF[p] == ' ') p++;
|
||||
*out_content = p;
|
||||
return LK_QUOTE;
|
||||
}
|
||||
|
||||
return LK_PLAIN;
|
||||
}
|
||||
|
||||
/* Visible column count for the marker prefix when rendered, including
|
||||
* any leading indent for nested lists/quotes (the render side keeps the
|
||||
* 1 byte = 1 visible col invariant for those, so content_off - p_start
|
||||
* is the visible width). */
|
||||
static uint8_t marker_visible_col(uint8_t kind, uint16_t p_start, uint16_t content_off)
|
||||
{
|
||||
switch (kind) {
|
||||
case LK_ULIST:
|
||||
case LK_QUOTE:
|
||||
case LK_OLIST: return (uint8_t)(content_off - p_start);
|
||||
default: return 0; /* PLAIN / headers (skipped) */
|
||||
}
|
||||
}
|
||||
|
||||
static void index_lines(void)
|
||||
{
|
||||
n_lines = 0;
|
||||
memset(in_code, 0, sizeof(in_code));
|
||||
if (file_size == 0) return;
|
||||
|
||||
if (!wrap_mode) {
|
||||
/* Truncate: one entry per logical line. */
|
||||
line_offset[n_lines++] = 0;
|
||||
for (uint16_t p = 0; p < file_size; p++) {
|
||||
if (FILE_BUF[p] == '\n' &&
|
||||
p + 1 < file_size && n_lines < MAX_LINES) {
|
||||
line_offset[n_lines++] = (uint16_t)(p + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Wrap: walk logical lines, split each into one or more segs. */
|
||||
uint16_t p = 0;
|
||||
while (p < file_size && n_lines < MAX_LINES) {
|
||||
/* Find end of logical line (\n or EOF). */
|
||||
uint16_t line_end = p;
|
||||
while (line_end < file_size && FILE_BUF[line_end] != '\n') line_end++;
|
||||
|
||||
/* Emit the first seg of this logical line (no CONT). */
|
||||
line_offset[n_lines++] = p;
|
||||
|
||||
if (!is_nowrap_line(p)) {
|
||||
/* Determine marker prefix and starting visible column. */
|
||||
uint16_t content_off = p;
|
||||
uint8_t kind = classify_line(p, &content_off);
|
||||
uint8_t col = marker_visible_col(kind, p, content_off);
|
||||
uint16_t q;
|
||||
if (kind == LK_H1 || kind == LK_H2 || kind == LK_H3 || kind == LK_H4 ||
|
||||
kind == LK_ULIST || kind == LK_OLIST || kind == LK_QUOTE) {
|
||||
q = content_off;
|
||||
} else {
|
||||
q = p;
|
||||
}
|
||||
|
||||
uint16_t last_space = 0xFFFFu; /* byte pos AFTER last space */
|
||||
while (q < line_end && n_lines < MAX_LINES) {
|
||||
char ch = FILE_BUF[q];
|
||||
/* Inline emphasis markers are zero-width. */
|
||||
if (ch == '*' || ch == '_' || ch == '`') {
|
||||
q++;
|
||||
continue;
|
||||
}
|
||||
if (ch == '\t') {
|
||||
uint8_t tgt = (uint8_t)((col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
|
||||
if (tgt > SCREEN_W) tgt = SCREEN_W;
|
||||
col = tgt;
|
||||
q++;
|
||||
} else {
|
||||
if (ch == ' ') last_space = (uint16_t)(q + 1);
|
||||
col++;
|
||||
q++;
|
||||
}
|
||||
if (col >= SCREEN_W && q < line_end) {
|
||||
uint16_t wrap_at = (last_space != 0xFFFFu &&
|
||||
last_space > (line_offset[n_lines - 1] & OFF_MASK))
|
||||
? last_space : q;
|
||||
if (wrap_at >= line_end) break;
|
||||
line_offset[n_lines++] = (uint16_t)(wrap_at | OFF_CONT);
|
||||
col = 0;
|
||||
last_space = 0xFFFFu;
|
||||
q = wrap_at;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p = (line_end < file_size) ? (uint16_t)(line_end + 1) : line_end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fence/code-body bitmap pass — only non-CONT segs participate. */
|
||||
{
|
||||
uint8_t in_block = 0;
|
||||
for (uint16_t i = 0; i < n_lines; i++) {
|
||||
if (line_offset[i] & OFF_CONT) continue;
|
||||
if (is_fence_delim(i)) {
|
||||
in_block = (uint8_t)!in_block;
|
||||
} else if (in_block) {
|
||||
in_code[i >> 3] |= (uint8_t)(1u << (i & 7));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* rendering
|
||||
* ================================================================== */
|
||||
|
||||
/* Inline emphasis tracking (one style active at a time, no nesting). */
|
||||
#define EM_NONE 0
|
||||
#define EM_BOLD 1
|
||||
#define EM_ITALIC 2
|
||||
#define EM_UNDER 3
|
||||
#define EM_CODE 4
|
||||
|
||||
static void render_line(uint16_t line_idx, uint8_t row)
|
||||
{
|
||||
uint8_t col = 0;
|
||||
uint8_t base_attr = ATTR_TEXT;
|
||||
uint8_t cur_attr = ATTR_TEXT;
|
||||
uint8_t emph = EM_NONE;
|
||||
uint8_t parse_inline = 1; /* skip inline parsing for fenced code body */
|
||||
|
||||
if (line_idx < n_lines) {
|
||||
uint16_t raw = line_offset[line_idx];
|
||||
uint16_t p = raw & OFF_MASK;
|
||||
uint16_t content_off = p;
|
||||
uint8_t is_cont = (raw & OFF_CONT) ? 1 : 0;
|
||||
/* Right bound for this seg: start of next seg, or file end. */
|
||||
uint16_t seg_end = (line_idx + 1 < n_lines)
|
||||
? (uint16_t)(line_offset[line_idx + 1] & OFF_MASK)
|
||||
: file_size;
|
||||
if (seg_end > file_size) seg_end = file_size;
|
||||
uint8_t kind;
|
||||
|
||||
/* Continuation seg → render plain text from the seg's offset up to
|
||||
* either the next seg's offset or end-of-line. No markdown
|
||||
* classification, no marker prefixes, inline markers stay literal
|
||||
* (we drop them silently so the visible output matches the wrap
|
||||
* algorithm's column count). */
|
||||
if (is_cont) {
|
||||
while (p < seg_end && col < SCREEN_W) {
|
||||
char ch = FILE_BUF[p++];
|
||||
if (ch == '\n' || ch == '\r') break;
|
||||
if (ch == '*' || ch == '_' || ch == '`') continue;
|
||||
if (ch == '\t') {
|
||||
uint8_t tgt = (uint8_t)((col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
|
||||
if (tgt > SCREEN_W) tgt = SCREEN_W;
|
||||
while (col < tgt) wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
} else {
|
||||
wrchar(col++, row, ch, ATTR_TEXT);
|
||||
}
|
||||
}
|
||||
while (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Fenced code block: delimiter line → blank row; body lines render
|
||||
* verbatim in ATTR_TEXT_CODE without any inline-emphasis parsing. */
|
||||
if (is_fence_delim(line_idx)) {
|
||||
while (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
return;
|
||||
}
|
||||
if (is_code_body(line_idx)) {
|
||||
base_attr = ATTR_TEXT_CODE;
|
||||
cur_attr = ATTR_TEXT_CODE;
|
||||
parse_inline = 0;
|
||||
kind = LK_PLAIN;
|
||||
} else {
|
||||
kind = classify_line(p, &content_off);
|
||||
}
|
||||
|
||||
if (kind == LK_HR) {
|
||||
while (col < SCREEN_W) wrchar(col++, row, 0xC4, ATTR_HR);
|
||||
return;
|
||||
}
|
||||
if (kind >= LK_H1 && kind <= LK_H4) {
|
||||
switch (kind) {
|
||||
case LK_H1: base_attr = ATTR_TEXT_TITLE1; break;
|
||||
case LK_H2: base_attr = ATTR_TEXT_TITLE2; break;
|
||||
case LK_H3: base_attr = ATTR_TEXT_TITLE3; break;
|
||||
default: base_attr = ATTR_TEXT_TITLE4; break;
|
||||
}
|
||||
cur_attr = base_attr;
|
||||
p = content_off;
|
||||
} else if (kind == LK_ULIST) {
|
||||
/* Light nested-list support: any leading spaces before '-' / '*'
|
||||
* / '+' render as plain spaces (preserves nesting visually);
|
||||
* then a bullet + a content space. */
|
||||
uint16_t q = p;
|
||||
while (q + 2 < content_off && col < SCREEN_W && FILE_BUF[q] == ' ') {
|
||||
wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
q++;
|
||||
}
|
||||
if (col < SCREEN_W) wrchar(col++, row, 0x07, ATTR_LIST_MARKER); /* • */
|
||||
if (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
p = content_off;
|
||||
} else if (kind == LK_OLIST) {
|
||||
/* Indent (if any) → spaces; digits and '.' or ')' in marker
|
||||
* colour; trailing space in plain. */
|
||||
uint16_t q = p;
|
||||
while (q < content_off && col < SCREEN_W && FILE_BUF[q] == ' ') {
|
||||
wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
q++;
|
||||
}
|
||||
while (q < content_off - 1 && col < SCREEN_W) {
|
||||
wrchar(col++, row, FILE_BUF[q++], ATTR_LIST_MARKER);
|
||||
}
|
||||
if (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
p = content_off;
|
||||
} else if (kind == LK_QUOTE) {
|
||||
/* Indent (if any) → spaces; '│' (0xB3) prefix in marker
|
||||
* colour, then a space, then content. */
|
||||
uint16_t q = p;
|
||||
while (q + 1 < content_off && col < SCREEN_W && FILE_BUF[q] == ' ') {
|
||||
wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
q++;
|
||||
}
|
||||
if (col < SCREEN_W) wrchar(col++, row, 0xB3, ATTR_QUOTE_MARKER);
|
||||
if (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT);
|
||||
p = content_off;
|
||||
}
|
||||
|
||||
/* `cc` is the VISIBLE column inside the content area (the part
|
||||
* after any fixed marker). When viewport_x > 0 (only possible in
|
||||
* truncate mode), chars at cc < viewport_x are skipped silently
|
||||
* so the user can pan past the marker. The marker itself was
|
||||
* already rendered above at fixed screen cols 0..(col-1). */
|
||||
uint8_t cc = 0;
|
||||
while (p < seg_end && col < SCREEN_W) {
|
||||
char ch = FILE_BUF[p];
|
||||
if (ch == '\n' || ch == '\r') break;
|
||||
|
||||
/* --- inline emphasis markers (consumed, not rendered) ---
|
||||
* Only one style active at a time. A marker that would conflict
|
||||
* with the currently active style is rendered as a literal char.
|
||||
* Skipped entirely inside fenced code blocks (parse_inline=0).
|
||||
*/
|
||||
if (parse_inline) {
|
||||
if (ch == '*' && (p + 1) < file_size && FILE_BUF[p + 1] == '*') {
|
||||
if (emph == EM_NONE) {
|
||||
emph = EM_BOLD; cur_attr = ATTR_TEXT_BOLD;
|
||||
p += 2; continue;
|
||||
}
|
||||
if (emph == EM_BOLD) {
|
||||
emph = EM_NONE; cur_attr = base_attr;
|
||||
p += 2; continue;
|
||||
}
|
||||
/* conflict (italic/under active) — fall through, treat
|
||||
* the first '*' as literal. */
|
||||
}
|
||||
if (ch == '*') {
|
||||
if (emph == EM_NONE) {
|
||||
emph = EM_ITALIC; cur_attr = ATTR_TEXT_ITALIC;
|
||||
p++; continue;
|
||||
}
|
||||
if (emph == EM_ITALIC) {
|
||||
emph = EM_NONE; cur_attr = base_attr;
|
||||
p++; continue;
|
||||
}
|
||||
}
|
||||
if (ch == '_') {
|
||||
if (emph == EM_NONE) {
|
||||
emph = EM_UNDER; cur_attr = ATTR_TEXT_UNDERSORE;
|
||||
p++; continue;
|
||||
}
|
||||
if (emph == EM_UNDER) {
|
||||
emph = EM_NONE; cur_attr = base_attr;
|
||||
p++; continue;
|
||||
}
|
||||
}
|
||||
if (ch == '`') {
|
||||
if (emph == EM_NONE) {
|
||||
emph = EM_CODE; cur_attr = ATTR_TEXT_CODE;
|
||||
p++; continue;
|
||||
}
|
||||
if (emph == EM_CODE) {
|
||||
emph = EM_NONE; cur_attr = base_attr;
|
||||
p++; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p++;
|
||||
if (ch == '\t') {
|
||||
/* Tab expands in CONTENT-col space (cc), each generated
|
||||
* space is then conditionally rendered through hpan. */
|
||||
uint8_t tgt = (uint8_t)((cc & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP);
|
||||
while (cc < tgt && col < SCREEN_W) {
|
||||
if (cc >= viewport_x) wrchar(col++, row, ' ', cur_attr);
|
||||
cc++;
|
||||
}
|
||||
} else {
|
||||
if (cc >= viewport_x && col < SCREEN_W) {
|
||||
wrchar(col++, row, ch, cur_attr);
|
||||
}
|
||||
cc++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Right-edge truncation indicator: when in truncate mode and the
|
||||
* main loop stopped because the screen ran out (col == SCREEN_W),
|
||||
* peek forward past any zero-width inline markers — if there's
|
||||
* still real visible content before the end of the line, overlay
|
||||
* the last screen cell with a bright '>'. */
|
||||
if (!wrap_mode && col >= SCREEN_W) {
|
||||
uint16_t pp = p;
|
||||
while (pp < seg_end) {
|
||||
char c = FILE_BUF[pp];
|
||||
if (c == '\n' || c == '\r') break;
|
||||
if (c == '*' || c == '_' || c == '`') { pp++; continue; }
|
||||
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Pad rest of the row so previous render content is wiped. For code-
|
||||
* body rows use the code attribute so the block extends to the right
|
||||
* edge — visually distinguishes the block from surrounding text. */
|
||||
{
|
||||
uint8_t pad_attr = (parse_inline ? ATTR_TEXT : ATTR_TEXT_CODE);
|
||||
while (col < SCREEN_W) wrchar(col++, row, ' ', pad_attr);
|
||||
}
|
||||
}
|
||||
|
||||
static void render_viewport(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
render_line((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t calc_pct(void)
|
||||
{
|
||||
uint8_t pct;
|
||||
if (n_lines <= VIEW_H)
|
||||
pct = 100;
|
||||
else
|
||||
pct = (uint8_t)((uint32_t)top_line * 100UL / (uint32_t)(n_lines - VIEW_H));
|
||||
return pct;
|
||||
}
|
||||
|
||||
static void render_full_status(void)
|
||||
{
|
||||
uint16_t last = top_line + VIEW_H;
|
||||
if (last > n_lines) last = n_lines;
|
||||
uint8_t pct = calc_pct();
|
||||
|
||||
fill_row(0, ATTR_BAR);
|
||||
gotoxy(1, 0);
|
||||
printf("MDVIEW %s", filename);
|
||||
gotoxy(45, 0);
|
||||
printf("\xB3 L %5u-%u / %u", (top_line + 1), last, n_lines);
|
||||
gotoxy(70, 0);
|
||||
printf("\xB3 %3u%%", pct);
|
||||
}
|
||||
|
||||
static void render_updated_status(void)
|
||||
{
|
||||
uint16_t last = top_line + VIEW_H;
|
||||
if (last > n_lines) last = n_lines;
|
||||
uint8_t pct = calc_pct();
|
||||
|
||||
gotoxy(45, 0);
|
||||
printf("\xB3 L %5u-%u / %u", (top_line + 1), last, n_lines);
|
||||
gotoxy(70, 0);
|
||||
printf("\xB3 %3u%%", pct);
|
||||
}
|
||||
|
||||
static void render_menu(void)
|
||||
{
|
||||
fill_row(MENU_ROW, ATTR_MENU_T);
|
||||
put_str_attr(1, MENU_ROW, "F1", ATTR_MENU_K);
|
||||
put_str_attr(4, MENU_ROW, "Help", ATTR_MENU_T);
|
||||
put_str_attr(10, MENU_ROW, "F2", ATTR_MENU_K);
|
||||
/* Label reflects what F2 will DO: when wrap is on, F2 turns it off. */
|
||||
put_str_attr(13, MENU_ROW, wrap_mode ? "Unwrap" : "Wrap ", ATTR_MENU_T);
|
||||
put_str_attr(SCREEN_W - 10, MENU_ROW, "F10", ATTR_MENU_K);
|
||||
put_str_attr(SCREEN_W - 5, MENU_ROW, "Exit", ATTR_MENU_T);
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* scrolling
|
||||
* ================================================================== */
|
||||
|
||||
static void clamp_top(void)
|
||||
{
|
||||
if (n_lines <= VIEW_H) {
|
||||
top_line = 0;
|
||||
} else if (top_line > n_lines - VIEW_H) {
|
||||
top_line = (uint16_t)(n_lines - VIEW_H);
|
||||
}
|
||||
}
|
||||
|
||||
static void scroll_up(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
|
||||
if( new_top_line != top_line) {
|
||||
top_line = new_top_line;
|
||||
if (n == 1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);
|
||||
render_line((uint16_t)(top_line), (uint8_t)(VIEW_TOP_ROW));
|
||||
} else {
|
||||
render_viewport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void scroll_down(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
|
||||
(uint16_t)(top_line + n) : n_lines - VIEW_H;
|
||||
if( new_top_line != top_line) {
|
||||
top_line = new_top_line;
|
||||
if (n == 1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);
|
||||
render_line((uint16_t)(top_line + VIEW_H - 1), (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||
} else {
|
||||
clamp_top();
|
||||
render_viewport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Horizontal pan (truncate mode only). Each step is HPAN_STEP visible
|
||||
* columns; clamped to [0, 240] — enough for any real-world long line
|
||||
* within a 16 KB file. */
|
||||
static void scroll_h(int8_t delta)
|
||||
{
|
||||
if (wrap_mode) return;
|
||||
int16_t nx = (int16_t)viewport_x + delta;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx > 240) nx = 240;
|
||||
if ((uint8_t)nx != viewport_x) {
|
||||
viewport_x = (uint8_t)nx;
|
||||
render_viewport();
|
||||
}
|
||||
}
|
||||
|
||||
/* Toggle wrap/truncate and rebuild the seg index. Tries to preserve
|
||||
* the user's scroll position by remembering the byte offset of the
|
||||
* current top seg and finding the seg that contains that offset after
|
||||
* the rebuild. Also resets horizontal pan — wrap mode doesn't use it. */
|
||||
static void toggle_wrap(void)
|
||||
{
|
||||
uint16_t top_off = (top_line < n_lines)
|
||||
? (uint16_t)(line_offset[top_line] & OFF_MASK) : 0;
|
||||
wrap_mode = wrap_mode ? 0u : 1u;
|
||||
viewport_x = 0;
|
||||
index_lines();
|
||||
|
||||
/* Largest i such that the i-th seg's offset is ≤ top_off. */
|
||||
uint16_t i = 0;
|
||||
while ((uint16_t)(i + 1) < n_lines &&
|
||||
(uint16_t)(line_offset[i + 1] & OFF_MASK) <= top_off) {
|
||||
i++;
|
||||
}
|
||||
top_line = i;
|
||||
clamp_top();
|
||||
|
||||
render_menu();
|
||||
render_full_status();
|
||||
render_viewport();
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* help screen
|
||||
* ================================================================== */
|
||||
|
||||
static const char * const help_lines[] = {
|
||||
"",
|
||||
" MDVIEW v0.1 - Markdown text viewer for Sprinter",
|
||||
" =================================================",
|
||||
"",
|
||||
" Navigation:",
|
||||
" Up / Down - scroll one line",
|
||||
" PgUp / PgDn - scroll one page",
|
||||
" Home - go to top of document",
|
||||
" End - go to bottom of document",
|
||||
" Left / Right - pan horizontally (Unwrap mode only)",
|
||||
"",
|
||||
" Other:",
|
||||
" F1 - show this help",
|
||||
" F2 - toggle wrap / unwrap of long lines",
|
||||
" F10 / Esc - exit",
|
||||
"",
|
||||
" Press any key to return...",
|
||||
(const char *)0
|
||||
};
|
||||
|
||||
static void help_screen(void)
|
||||
{
|
||||
clrscr_attr(ATTR_TEXT);
|
||||
for (uint8_t i = 0; help_lines[i]; i++) {
|
||||
put_str_attr(0, (uint8_t)(i + 1), help_lines[i], ATTR_TEXT);
|
||||
}
|
||||
(void)getkey();
|
||||
clrscr_attr(ATTR_TEXT);
|
||||
render_menu();
|
||||
render_full_status();
|
||||
render_viewport();
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* error
|
||||
* ================================================================== */
|
||||
|
||||
static void die(const char *msg)
|
||||
{
|
||||
clrscr_attr(ATTR_TEXT);
|
||||
puts(msg);
|
||||
puts("Press any key to exit...");
|
||||
(void)getkey();
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* main
|
||||
* ================================================================== */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *path;
|
||||
if (argc >= 2) path = argv[1];
|
||||
else path = "SAMPLE.MD";
|
||||
|
||||
/* Copy path → static filename[] for the status bar. */
|
||||
{
|
||||
uint8_t i = 0;
|
||||
while (i < (uint8_t)(sizeof(filename) - 1) && path[i]) {
|
||||
filename[i] = path[i];
|
||||
i++;
|
||||
}
|
||||
filename[i] = 0;
|
||||
}
|
||||
|
||||
int rc = load_file(path);
|
||||
if (rc < 0) {
|
||||
clrscr_attr(ATTR_TEXT);
|
||||
puts("mdview: cannot load file");
|
||||
puts(path);
|
||||
switch (rc) {
|
||||
case -1: puts("(open failed)"); break;
|
||||
case -2: puts("(size > 16K or seek failed)"); break;
|
||||
case -3: puts("(mem_alloc_pages failed)"); break;
|
||||
case -4: puts("(short read)"); break;
|
||||
}
|
||||
puts("Press any key to exit...");
|
||||
(void)getkey();
|
||||
return 1;
|
||||
}
|
||||
index_lines();
|
||||
if (n_lines == 0) {
|
||||
die("mdview: empty file");
|
||||
unload_file();
|
||||
return 1;
|
||||
}
|
||||
|
||||
set_videotextmode(TEXT_MODE_80x32);
|
||||
clrscr_attr(ATTR_TEXT);
|
||||
render_menu();
|
||||
render_full_status();
|
||||
render_viewport();
|
||||
|
||||
for (;;) {
|
||||
uint16_t k = getkey();
|
||||
uint8_t ascii = (uint8_t)(k & 0xFF);
|
||||
uint8_t scan = (uint8_t)((k >> 8) & 0x7F);
|
||||
|
||||
if (ascii) {
|
||||
if (ascii == 0x1B) break; /* Esc */
|
||||
continue;
|
||||
}
|
||||
switch (scan) {
|
||||
case KEY_F10: goto exit_loop;
|
||||
case KEY_F1: help_screen(); break;
|
||||
case KEY_F2: toggle_wrap(); break;
|
||||
case KEY_UP: scroll_up(1); break;
|
||||
case KEY_DOWN: scroll_down(1); break;
|
||||
case KEY_LEFT: scroll_h(-(int8_t)HPAN_STEP); break;
|
||||
case KEY_RIGHT: scroll_h(+(int8_t)HPAN_STEP); 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: top_line = (n_lines > VIEW_H)
|
||||
? (uint16_t)(n_lines - VIEW_H) : 0;
|
||||
render_viewport(); break;
|
||||
default: continue;
|
||||
}
|
||||
render_updated_status();
|
||||
}
|
||||
exit_loop:
|
||||
unload_file();
|
||||
clrscr_attr(ATTR_TEXT);
|
||||
return 0;
|
||||
}
|
||||
+35
-16
@@ -22,25 +22,44 @@ BUILD := $(PROJ_ROOT)/lib/build
|
||||
|
||||
# All libc C modules.
|
||||
LIBC_C := \
|
||||
libc/io/atexit.c libc/io/conio.c libc/io/cprintf.c libc/io/dir.c \
|
||||
libc/io/videomode_raw.c \
|
||||
libc/io/_errno_set.c \
|
||||
libc/io/env.c libc/io/errno.c libc/io/fsdir.c \
|
||||
libc/io/lseek.c libc/io/mouse.c libc/io/open.c \
|
||||
libc/io/read.c libc/io/sleep.c \
|
||||
libc/io/time.c libc/io/posix_time.c libc/io/unlink.c \
|
||||
libc/sys/atexit.c \
|
||||
libc/conio/conio.c \
|
||||
libc/conio/cprintf.c \
|
||||
libc/io/dir.c \
|
||||
libc/video/videomode_raw.c \
|
||||
libc/errno/_errno_set.c \
|
||||
libc/env/env.c \
|
||||
libc/errno/errno.c \
|
||||
libc/io/fsdir.c \
|
||||
libc/io/lseek.c \
|
||||
libc/mouse/mouse.c \
|
||||
libc/io/open.c \
|
||||
libc/io/read.c \
|
||||
libc/time/sleep.c \
|
||||
libc/time/time.c \
|
||||
libc/time/posix_time.c \
|
||||
libc/io/unlink.c \
|
||||
libc/io/stat.c \
|
||||
libc/mem/bank_io_w3.c libc/mem/bank_io_w1.c libc/mem/mem_alloc.c \
|
||||
libc/gfx/gfx_core.c libc/gfx/gfx_raw_common.c \
|
||||
libc/gfx/gfx_raw_256.c libc/gfx/gfx_raw_16.c \
|
||||
libc/gfx/gfx_256.c libc/gfx/gfx_16.c \
|
||||
libc/gfx/gfx_font.c libc/gfx/gfx_text_256.c \
|
||||
libc/mem/bank_io_w3.c \
|
||||
libc/mem/bank_io_w1.c \
|
||||
libc/mem/mem_alloc.c \
|
||||
libc/gfx/gfx_core.c \
|
||||
libc/gfx/gfx_raw_common.c \
|
||||
libc/gfx/gfx_raw_256.c \
|
||||
libc/gfx/gfx_raw_16.c \
|
||||
libc/gfx/gfx_256.c \
|
||||
libc/gfx/gfx_16.c \
|
||||
libc/gfx/gfx_font.c \
|
||||
libc/gfx/gfx_text_256.c \
|
||||
libc/gfx/gfx_text_16.c \
|
||||
libc/stdio/getchar.c \
|
||||
libc/stdio/putchar.c libc/stdio/puts.c libc/stdio/file.c \
|
||||
libc/stdio/hex_print.c libc/stdio/dec_print.c \
|
||||
libc/stdio/solid_helpers.c \
|
||||
libc/io/solid_compat.c
|
||||
libc/stdio/putchar.c \
|
||||
libc/stdio/puts.c \
|
||||
libc/file/file.c \
|
||||
libc/stdio/hex_print.c \
|
||||
libc/stdio/dec_print.c \
|
||||
libc/string/strlwr.c \
|
||||
libc/string/strupr.c
|
||||
|
||||
# Runtime modules to bundle (pulled by symbol references from libc-using code).
|
||||
# NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking
|
||||
|
||||
@@ -58,6 +58,30 @@ char getche(void) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* getkey — like getch() but exposes BOTH the ASCII value and the
|
||||
* positional scan code, so callers can distinguish extended keys
|
||||
* (arrows, F1..F12, PgUp/PgDn, Home/End, Ins/Del — all of which carry
|
||||
* ASCII == 0 from ESTEX) from plain ASCII keys.
|
||||
*
|
||||
* return = (scan << 8) | ascii
|
||||
*
|
||||
* For plain keys: ascii holds the character, scan holds the positional
|
||||
* code (bit 7 set when Ctrl/Alt/Shift is held).
|
||||
* For extended keys: ascii == 0, scan identifies the key (see KEY_* in
|
||||
* <conio.h>).
|
||||
*/
|
||||
uint16_t getkey(void) __naked
|
||||
{
|
||||
__asm
|
||||
push ix
|
||||
ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII
|
||||
rst #0x10
|
||||
pop ix
|
||||
ld e, a ; E = ASCII (defensive: A is the canonical copy)
|
||||
ret ; __sdcccall(1) returns uint16_t in DE
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* ---- putch / cputs: Turbo-C conio convention ---------------------- *
|
||||
* Both APPLY the current text attribute (g_text_attr). When attr is
|
||||
* KEEP_EXIST_ATTR (>0xFF), they short-circuit to the FAST stdio path
|
||||
@@ -462,6 +486,37 @@ uint16_t wherexy(void) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
||||
*
|
||||
* SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
|
||||
* packed and pushed on the stack as a single 16-bit value (caller does
|
||||
* `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL:
|
||||
* [SP+0..1] = return address
|
||||
* [SP+2] = ch (low half of pushed pair)
|
||||
* [SP+3] = attr (high half)
|
||||
* Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
|
||||
*/
|
||||
void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked
|
||||
{
|
||||
(void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear;
|
||||
__asm
|
||||
pop iy ; return address
|
||||
ld d, l ; D = row (y)
|
||||
ld e, a ; E = col (x)
|
||||
pop hl ; H = heigth(h), L = width(w)
|
||||
pop bc ; C = direction, B = clear
|
||||
ld a, b ; A = clear(B)
|
||||
ld b, c ; B = direction(C)
|
||||
push ix
|
||||
ld c, #0x55 ; ESTEX SCROLL
|
||||
rst #0x10
|
||||
pop ix
|
||||
jp (iy)
|
||||
__endasm;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
|
||||
*
|
||||
Vendored
@@ -143,7 +143,8 @@ int fputc(int c, FILE *fp)
|
||||
{
|
||||
if (!fp) { errno = EBADF; return EOF; }
|
||||
if (fp->flags & _F_CONOUT) {
|
||||
return putchar(c);
|
||||
putchar(c);
|
||||
return (int)c;
|
||||
}
|
||||
if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; }
|
||||
uint8_t ch = (uint8_t)c;
|
||||
@@ -175,7 +176,7 @@ int fputs(const char *s, FILE *fp)
|
||||
if (!fp || !s) { errno = EBADF; return EOF; }
|
||||
if (fp->flags & _F_CONOUT) {
|
||||
while (*s) {
|
||||
if (putchar((unsigned char)*s++) == EOF) return EOF;
|
||||
putchar((unsigned char)*s++);;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -239,7 +240,7 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp)
|
||||
const char *p = (const char *)ptr;
|
||||
size_t total = size * nmemb;
|
||||
for (size_t i = 0; i < total; i++) {
|
||||
if (putchar((unsigned char)p[i]) == EOF) return i / size;
|
||||
putchar((unsigned char)p[i]);
|
||||
}
|
||||
return nmemb;
|
||||
}
|
||||
+54
-4
@@ -38,6 +38,42 @@
|
||||
char kbhit (void);
|
||||
char getch (void);
|
||||
char getche(void);
|
||||
|
||||
/* Extended-key reader. Returns (scan << 8) | ascii. Plain ASCII keys
|
||||
* have ascii in the low byte; extended keys (arrows / F1..F12 /
|
||||
* PgUp/PgDn / Home / End / Ins / Del) carry ascii == 0 and the
|
||||
* KEY_* code in the high byte. */
|
||||
uint16_t getkey(void);
|
||||
|
||||
/* Scan codes returned in the high byte of getkey() when the low byte
|
||||
* (ASCII) is 0. Empirically verified in MAME — the ProgrammerManual.txt
|
||||
* "positional code" column is misleading; BIOS returns IBM-style codes
|
||||
* for the F-keys and a "5N + numpad-position" pattern for the cursor /
|
||||
* editing keys.
|
||||
*
|
||||
* Verified 2026-06-04 by reading raw getkey() output. */
|
||||
#define KEY_F1 0x3B
|
||||
#define KEY_F2 0x3C
|
||||
#define KEY_F3 0x3D
|
||||
#define KEY_F4 0x3E
|
||||
#define KEY_F5 0x3F
|
||||
#define KEY_F6 0x40
|
||||
#define KEY_F7 0x41
|
||||
#define KEY_F8 0x42
|
||||
#define KEY_F9 0x43
|
||||
#define KEY_F10 0x44
|
||||
#define KEY_F11 0x45 /* not verified */
|
||||
#define KEY_F12 0x46 /* not verified */
|
||||
#define KEY_END 0x51
|
||||
#define KEY_DOWN 0x52
|
||||
#define KEY_PGDN 0x53
|
||||
#define KEY_LEFT 0x54
|
||||
#define KEY_RIGHT 0x56
|
||||
#define KEY_HOME 0x57
|
||||
#define KEY_UP 0x58
|
||||
#define KEY_PGUP 0x59
|
||||
#define KEY_INS 0x50 /* numpad 0; not verified */
|
||||
#define KEY_DEL 0x55 /* numpad 5/.; not verified */
|
||||
char putch (char c);
|
||||
char cputs (const char *s);
|
||||
int cprintf(const char *fmt, ...);
|
||||
@@ -63,6 +99,8 @@ uint8_t wherex (void);
|
||||
uint8_t wherey (void);
|
||||
uint16_t wherexy(void); // high byte = Y, low byte = X coords.
|
||||
|
||||
void scroll(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
|
||||
|
||||
/* Direct character/attribute screen access (ESTEX $57 / $58).
|
||||
* wrchar — write char + attribute at (x, y); does NOT advance the cursor
|
||||
* and does NOT interpret control characters. Useful for
|
||||
@@ -143,10 +181,22 @@ uint8_t get_putch_raw_mode(void);
|
||||
* Colour order is standard CGA / Borland-conio.h. Constants 0..7 are
|
||||
* usable for both fg and bg; 8..15 are foreground-only. */
|
||||
enum {
|
||||
COLOR_BLACK = 0, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN,
|
||||
COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_LIGHTGRAY,
|
||||
COLOR_DARKGRAY, COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN,
|
||||
COLOR_LIGHTRED, COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE
|
||||
COLOR_BLACK = 0,
|
||||
COLOR_BLUE,
|
||||
COLOR_GREEN,
|
||||
COLOR_CYAN,
|
||||
COLOR_RED,
|
||||
COLOR_MAGENTA,
|
||||
COLOR_BROWN,
|
||||
COLOR_LIGHTGRAY,
|
||||
COLOR_DARKGRAY,
|
||||
COLOR_LIGHTBLUE,
|
||||
COLOR_LIGHTGREEN,
|
||||
COLOR_LIGHTCYAN,
|
||||
COLOR_LIGHTRED,
|
||||
COLOR_LIGHTMAGENTA,
|
||||
COLOR_YELLOW,
|
||||
COLOR_WHITE
|
||||
};
|
||||
#define COLOR_BLINK 0x80u
|
||||
#define COLOR(fg, bg) ((uint8_t)((((bg) & 0x07) << 4) | ((fg) & 0x0F)))
|
||||
|
||||
+15
-7
@@ -16,14 +16,22 @@
|
||||
#ifndef FCNTL_H
|
||||
#define FCNTL_H
|
||||
|
||||
#define O_RDONLY 0
|
||||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
#ifndef _STD_SEEK_
|
||||
#define _STD_SEEK_
|
||||
/* constants to be used as 3rd argument for "fseek" function */
|
||||
#define SEEK_SET 0
|
||||
#define SEEK_CUR 1
|
||||
#define SEEK_END 2
|
||||
#endif
|
||||
|
||||
#define O_CREAT 0x040
|
||||
#define O_EXCL 0x080
|
||||
#define O_TRUNC 0x200
|
||||
#define O_APPEND 0x400
|
||||
/* Definition "open flags" */
|
||||
#define O_WRONLY 0x01 /* 0 file write only */
|
||||
#define O_RDONLY 0x02 /* 1 file read only */
|
||||
#define O_RDWR 0x03 /* 1,0 file read/write */
|
||||
#define O_TRUNC 0x04 /* 2 open with truncation */
|
||||
#define O_CREAT 0x08 /* 3 create and open file */
|
||||
#define O_EXCL 0x10 /* 4 exclusive open */
|
||||
#define O_APPEND 0x20 /* 5 to end of file */
|
||||
|
||||
int open (const char *path, int flags);
|
||||
int creat(const char *path, int mode); /* mode arg ignored on Sprinter */
|
||||
|
||||
@@ -30,7 +30,7 @@ int vprintf(const char *, va_list);
|
||||
int vsprintf(char *, const char *, va_list);
|
||||
|
||||
/* puts / putchar / getchar — overridden by our libc to use ESTEX. */
|
||||
int puts (const char *);
|
||||
char puts (const char *);
|
||||
int putchar(int);
|
||||
int getchar(void);
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strlwr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if (*p >= 'A' && *p <= 'Z') *p += 'a' - 'A';
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
char *strupr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if (*p >= 'a' && *p <= 'z') *p -= 'a' - 'A';
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* div() comes from SDCC's z80.lib. */
|
||||
@@ -17,13 +17,7 @@ int getchar(void) __naked
|
||||
ld c, #0x30 ; ESTEX WAITKEY
|
||||
rst #0x10
|
||||
pop ix
|
||||
ld a, e ; E = ASCII (already the low byte of our return DE)
|
||||
or a, a
|
||||
jr Z, no_ascii
|
||||
ld d, #0
|
||||
ret
|
||||
no_ascii:
|
||||
ld de, #-1
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
+6
-14
@@ -19,23 +19,15 @@ int putchar(int c) __naked
|
||||
{
|
||||
(void)c;
|
||||
__asm
|
||||
ld a, l ; SDCC __sdcccall(1) int → HL
|
||||
push ix
|
||||
ld a, l
|
||||
cp #0x0A
|
||||
jr nz, _pc_emit
|
||||
ld a, #0x0D ; CR before LF
|
||||
push af
|
||||
jr nz, cputc
|
||||
call cputc
|
||||
ld a, #0x0D
|
||||
cputc:
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
pop af
|
||||
ld a, #0x0A
|
||||
_pc_emit:
|
||||
push af
|
||||
ld c, #0x5B
|
||||
rst #0x10
|
||||
pop af
|
||||
pop ix
|
||||
ld e, a
|
||||
ld e, l
|
||||
ld d, #0
|
||||
ret
|
||||
__endasm;
|
||||
|
||||
+21
-34
@@ -14,17 +14,11 @@
|
||||
* - Avoid trailing PUTCHAR after PCHARS — empirically that sometimes
|
||||
* drops the next char. Embed the line ending inside the PCHARS
|
||||
* buffer instead.
|
||||
* - Strings longer than the buffer fall back to per-char putchar so
|
||||
* we never silently truncate.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PUTS_BUF_SIZE 256 /* body bytes before CR expansion */
|
||||
|
||||
static char puts_buf[PUTS_BUF_SIZE + 3]; /* +3 for trailing CR LF NUL */
|
||||
|
||||
static void pchars(const char *s) __naked
|
||||
{
|
||||
(void)s;
|
||||
@@ -37,33 +31,26 @@ static void pchars(const char *s) __naked
|
||||
__endasm;
|
||||
}
|
||||
|
||||
int puts(const char *s)
|
||||
char puts(const char *s) __naked
|
||||
{
|
||||
uint16_t n = 0;
|
||||
uint16_t i = 0;
|
||||
|
||||
while (s[i] && n < PUTS_BUF_SIZE - 1) {
|
||||
char c = s[i++];
|
||||
if (c == '\n') {
|
||||
puts_buf[n++] = '\r';
|
||||
puts_buf[n++] = '\n';
|
||||
} else {
|
||||
puts_buf[n++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (s[i]) {
|
||||
/* Overflow — char-by-char fallback so we never truncate. */
|
||||
for (uint16_t k = 0; s[k]; k++)
|
||||
putchar((unsigned char)s[k]);
|
||||
putchar('\n');
|
||||
return 0;
|
||||
}
|
||||
|
||||
puts_buf[n++] = '\r';
|
||||
puts_buf[n++] = '\n';
|
||||
puts_buf[n] = 0;
|
||||
|
||||
pchars(puts_buf);
|
||||
return 0;
|
||||
(void)s;
|
||||
__asm
|
||||
puts_:
|
||||
ld a, (hl)
|
||||
or a
|
||||
jr z, fin_
|
||||
push hl
|
||||
ld l, a
|
||||
ld h, #0
|
||||
call _putchar
|
||||
pop hl
|
||||
inc hl
|
||||
jp puts_
|
||||
;
|
||||
fin_:
|
||||
ld l, #0x0A
|
||||
ld h, #0
|
||||
call _putchar
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* solid_helpers.c — small Solid-C compatibility helpers.
|
||||
*
|
||||
* dec8 / dec16 / dec32 / hex8 / hex16 / hex32 are in dec_hex.c (compact
|
||||
* asm port from solid-c's STDLIB.ASM, ~150 bytes total — vs ~3-5 KB if
|
||||
* routed through printf). This file now only holds gets().
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* ---- gets — dangerous but Solid-C provides it ---------------------- */
|
||||
char *gets(char *buf)
|
||||
{
|
||||
int i = 0;
|
||||
int c;
|
||||
for (;;) {
|
||||
c = getchar();
|
||||
if (c == EOF) {
|
||||
if (i == 0) return 0;
|
||||
break;
|
||||
}
|
||||
if (c == '\n' || c == '\r') break;
|
||||
buf[i++] = (char)c;
|
||||
}
|
||||
buf[i] = 0;
|
||||
return buf;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*
|
||||
* CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase
|
||||
* conversion for both Latin and Cyrillic characters in CP866 code page
|
||||
* (bytes 0x80–0xFF).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strlwr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if ((*p >= 'A' && *p <= 'Z') || (*p >= 0x80 && *p <= 0x8F)) *p += 'a' - 'A';
|
||||
else if ((*p >= 0x90 && *p <= 0x9F)) *p += 0x50;
|
||||
else if ((*p == 0xF0)) *p = 0xF1;
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* solid_compat.c — Solid-C compatibility helpers that need real code
|
||||
* (rather than just header macros).
|
||||
*
|
||||
* CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase
|
||||
* conversion for both Latin and Cyrillic characters in CP866 code page
|
||||
* (bytes 0x80–0xFF).
|
||||
*/
|
||||
|
||||
#include <sprinter_compat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
char *strupr(char *s)
|
||||
{
|
||||
char *p = s;
|
||||
while (*p) {
|
||||
if ((*p >= 'a' && *p <= 'z') || (*p >= 0xA0 && *p <= 0xAF)) *p -= 'a' - 'A';
|
||||
else if ((*p >= 0xE0 && *p <= 0xEF)) *p -= 0x50;
|
||||
else if ((*p == 0xF1)) *p = 0xF0;
|
||||
p++;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := argv
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := assrtest
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := attrprob
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -8,4 +8,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := banked
|
||||
MEMORY := huge
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --bank 2=bank2.c
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -9,4 +9,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := bankedbg
|
||||
MEMORY := big
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --bank 2=bank2.c
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -9,4 +9,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := banklocl
|
||||
MEMORY := huge
|
||||
EXTRA_FLAGS := --bank 1=bank1.c --mkexe -p --mkexe 0
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -3,4 +3,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := cat
|
||||
EXTRA_DATA := test.txt
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := conio
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := conio2
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := dec_test
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := errno
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := filetest
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,5 @@
|
||||
# Build cat.exe — uses lib/sprinter.lib in TINY memory mode.
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gets
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char buff [256];
|
||||
|
||||
puts("--- gets test ---");
|
||||
|
||||
gets(buff);
|
||||
|
||||
puts("");
|
||||
puts("loaded string:");
|
||||
puts(buff);
|
||||
puts("done");
|
||||
(void)getchar();
|
||||
return 0;
|
||||
}
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_d16
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_dbuf
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_demo
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_mous
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := gfx_text
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := hello
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := ls
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := malloc
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mem_test
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := mouse
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -4,4 +4,4 @@ PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := openenv
|
||||
MEMORY := big
|
||||
EXTRA_FLAGS :=
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := ptime
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := rt_test
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -3,4 +3,4 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := seek
|
||||
EXTRA_DATA := big.txt
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := stattest
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := stdlib
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
@@ -1,3 +1,3 @@
|
||||
PROJ_ROOT := $(abspath $(CURDIR)/../..)
|
||||
EXAMPLE := stest2
|
||||
include $(PROJ_ROOT)/examples/example.mk
|
||||
include $(PROJ_ROOT)/app.mk
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user