B2 stage2: общий цикл inline-форматирования/переносов/склейки в одном
scan_stream (mode: NONE / LIST / QUOTE / PLAIN); дублировавшиеся блоки
эмиссии пробела/символа, переноса с усечением и отката стиля — в одном
экземпляре. Старые имена — тонкие обёртки, API inline_scan для
table-модуля не изменился. styles_map умерла: emph_to_attr(ls, ATTR_TEXT)
тождественна ей.
Индексатор 9839 → 9315 Б. Проверено в MAME: переносы заголовков/списков/
цитат, таблицы, inline-маркеры на границе переноса, жёсткие переносы.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Все четыре crt0 (default/small/minimal/banked): gsinit теперь зануляет
_DATA и _BSS через общий zero_area, затем копирует _INITIALIZER.
Явные `= 0` у глобалов/статиков больше не нужны (они жгли байты
_INITIALIZER в образе). crt0-приватные переменные, записываемые ДО
gsinit (_estex_startup_ix и др.), перенесены из _DATA в _CODE (RAM).
sprinter-cc: новая опция --max-allocs N → SDCC --max-allocs-per-node
(агрессивнее аллокация регистров, меньше/быстрее код ценой времени
компиляции).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
render_full_status форсирует перерисовку чисел через local_loading=UCHAR_MAX
(сентинел), а не отдельным force_redraw в условии. Отдельный 4-й терм + запись
флага опрокидывали render_md_status_numbers в IX-стек-фрейм (все локали в
память, +68 Б). Вынос local_* в file-scope разгрузил регистровый аллокатор
SDCC — функция осталась на регистрах. Итог даже меньше базы (28215 Б).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
calc_md_pct/calc_raw_pct тянули 32-битное деление __divulong (+__muluint2ulong)
ради показа процента в статусе. Оба дают операнды ≤16 бит (≤18432 / ≤1024),
переполняет только *100. Новый pct16() масштабирует оба вниз и считает долю
циклом-вычитанием — 66 Б, НОЛЬ подтянутых арифм-хелперов. Точность ±1%
(на границах точно), для индикатора прокрутки незаметно.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Тест-файлы лимита 256 КБ (TABLES/LINES/HUGE) проверены в MAME и сняты с
диска. Сами файлы и генератор остаются в testfiles/ как архив для повтора.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Лимит строк не показывал предупреждение: g_trunc_cause=TRUNC_LINES
ставился в emit_seg, но главный цикл выходит по n_lines<max_lines ДО
вызова emit_seg в переполненном состоянии (для code-block — один
emit_seg на строку). Теперь ловим после цикла по признаку p<file_size
(остановились, файл не кончился).
2. Файл >256 КБ больше не отвергаем экраном ошибки, а КЛАМПим: читаем
первые 256 КБ, индексатор дописывает строку TRUNC_FILE (File too large
- truncated at 256 KB). Приоритет ниже content/lines. Текст ошибки -2
поправлен (был 'size > 128K').
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Вьювер склеивает подряд идущие непустые строки в один абзац (markdown
soft-wrap), из-за чего простые строки сворачивались в ~2752 экранных и
лимит 18432 не достигался. Завернул содержимое в code fence (verbatim,
1:1 строка-источник = экранная строка) -> 22000 строк > 18432.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make run пересобирает образ через floppy, затирая прежний диск. Теперь
floppy генерирует BIG.MD (README+READMEBG ×2 ~216 КБ → CP866) и кладёт
его рядом с README/UTF8TEST — образ всегда содержит файл >128 КБ для
проверки лимита 256 КБ. Состав переопределяется: make floppy BIG_SRCS=...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- status-bar: dirty-tracking (числа/процент/кодировка перерисовываются
только при изменении), поле кодировки сдвинуто к DIV1_X-10 (8 симв.)
- md_key: HOME/END не перерисовывают экран, если позиция не меняется
- help: версия v1.0(a3), добавлены F2/F3 (RAW/Wrap), компактные секции
- enc: детекция по 5 частотным буквам и сэмплу 1КБ; ENC_UNSUPPORTED (UTF16/32)
- libc: добавлены min()/max() (naked, <stdlib.h>) + сборка в lib/Makefile
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MD-навигация после загрузки (стрелки/PgUp/PgDn/Home/End/←/→) вынесена из
switch в main() в md_key(scan) — peer к raw_key(): возвращает 1/0, сама
перерисовывает область+статус. main() теперь симметричен для обоих видов:
F-клавиши (F1/F8/F10, для RAW ещё F2/F3) разбираются в цикле, навигация
делегируется md_key()/raw_key().
HPAN_STEP вынесен в mdview2.h (был продублирован в ядре и raw). load_key()
(навигация во время прогрессивной загрузки, bounded по drawable_lines)
остаётся в ядре — у неё нет RAW-аналога.
Поведение сохранено (F1 теперь без лишнего render_updated_status — show_help
и так перерисовывает всё). Размер: exe 28115→28173 (+58 Б — стоимость
границы функции, как у raw_key). Сборка чистая.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- mdview2_view.c → mdview2_md.c: модуль уже содержит отрисовку MD-документа
из рендер-кэша + скролл + статус-бар, т.е. это парный к mdview2_raw.c вид
(MD ↔ RAW). Переименование делает пару явной.
- mdview2.c: обновлён устаревший заголовок-комментарий («Фаза 0 — копия
mdview.c») на описание ядра + карту модулей; убраны осиротевшие после
выноса комментарии; нормализованы баннеры секций (рендер-кэш / примитивы
экрана+EMM / загрузка файла / doc-slots / loading-loop / точка входа).
- mdview2.h: освежён заголовок-комментарий под текущую раскладку модулей.
Только переименование и комментарии/баннеры — поведение и размер не
меняются (exe 28115, как до). Сборка чистая.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В RAW-режиме offset ряда под экраном (raw_bot) велся инкрементально в
предположении, что экран всегда полон контентом. После Wrap → конец файла
→ Unwrap контент кончается в середине экрана, raw_bot рассинхронизировался
(raw_scroll_up1 делал raw_bot = raw_prev(raw_bot)), и guard raw_bot >=
file_size в scroll-down ложно проходил → дубликат последней строки внизу.
Фикс: убран хрупкий raw_bot. raw_scroll_down1 проходит VIEW_H рядов от
raw_top и скроллит вниз только если контент реально уходит за нижний край
(иначе внизу была бы пустая строка). Кнопка «вниз» теперь работает лишь
когда под экраном есть контент; иначе доступна только «вверх».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Новый модуль mdview2_raw.c (+ mdview2.h с общими константами/атрибутами и
extern'ами) — первый шаг разбиения монолита. Опционален через mdview2_conf.h
(#define WITH_RAW): при 0 — пустой объектник, нулевой расход (проверено:
размер как до RAW).
RAW-просмотр (без markdown-форматирования):
- работает по активному буферу (8-бит как есть / UTF-8 декодированный), 1 байт
= 1 ячейка, \t→пробел, ремап CP1251/KOI8 на отрисовке;
- два под-режима: wrap (перенос кратно 80) и hscroll (одна строка + ←/→);
- прокрутка на 1 строку через аппаратный scroll + отрисовка одной строки;
вывод char-буфером (bios_write_until по фону ATTR_TEXT), без win_rest/scratch;
- индекс/кэш markdown не используются, 0 доп. EMM.
Клавиши/меню:
- F2 — тумблер RAW↔MD (запоминает под-режим RAW);
- F3 — Wrap/Unwrap (только в RAW), метка показывает целевой режим;
- меню перестроено: блоки по 8 кол (col i*8), номера всех 10 клавиш без 'F'
(' 1'..' 9','10'), текст-функция 6 симв. сразу за номером и только когда
функция доступна; F8 сокращён до CodePg.
Инкрементальная UTF-8→CP866 конвертация: вместо полного прохода перед
индексацией — чанками впереди позиции чтения (CONV_MARGIN), первый экран
появляется быстро. Конвертер читает оригинал через cv_read (своя W3-страница),
index_lines докручивает конвертацию; progress_tick рисует по текущему виду
(MD/RAW), без мелькания чужого вида.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A.1: таблицы ремапа cp1251/koi8r 256→128 (старшие байты; младшие в
ремапе не используются — win_rest_remap трогает только ch>=0x80).
A.2: conv_emit_cp switch → таблица структур utf_sym_t {utf8, cp866}
(читаемо, добавление символа = одна строка; … и BOM — спецветки).
A.3: common_* цепочки сравнений → таблицы детекции + in_set10.
B1: удалён мёртвый код в scan_join_stream — условия
`if(!soft_break)...else q++` во всех непробельных ветках
(там soft_break всегда 0, т.к. ch!=' ').
_CODE (mdview2.c): 23284 → 22246 байт. Поведение не менялось.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Детекция при открытии (BOM + эвристика по первым 4 КБ).
- 8-битные (CP866/CP1251/KOI8-R) — общий индекс/кэш, переключение
мгновенным ремапом глифов [128-255] на отрисовке по attr (структурные
глифы — рамка/HR/маркеры — не ремапятся).
- UTF-8 — отдельный набор: декодирование в CP866 (кириллица + ходовые
символы: стрелки/галка/буллет/тире/кавычки/box), свой индекс/кэш.
- Два набора (docset_t g_doc[2]) со свапом «живых» глобалов; второй
строится ЛЕНИВО при первом F8-переходе в него (build-on-demand).
- F8: цикл CP866→CP1251→KOI8R→UTF8; метка в меню видна только когда
переключение возможно; во время сборки 8-бит первичного F8 крутит 8-бит.
- F1-справка: секция Encoding; меню разбито на блоки (F1/F8/F10).
- Фикс: g_doc обязан быть инициализирован (SDCC z80 не обнуляет статики
надёжно) — иначе мусорный built вёл к показу неинициализированного набора.
- Убрана отладка времени обработки из статус-бара.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Таблица обрабатывается как блок в два прохода:
- проход 1: границы блока + ОТРЕНДЕРЕННЫЕ ширины колонок (мерим тем же
inline_scan, что и при отрисовке — невидимые маркеры стиля **/код не
раздувают столбцы);
- проход 2: верхняя рамка ┌┬┐ → строки данных │ ячейка<pad> │ → разделитель
заголовка ├┼┤ (из строки |---|) → нижняя рамка └┴┘.
Колонки выровнены по содержимому, рамка CP866 box-drawing. Широкая таблица
остаётся nowrap+hscroll. Ячейки разбиваются cell-итератором (без массивов
на стеке); пустой g_cells под измерение освобождается ручным флашем
pending-сегмента (флаг g_skip_flush в emit_seg, чтобы не флашить повторно).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1. Экранирование пунктуации: \* \_ \` \[ … → литерал, маркером НЕ считается
(CommonMark ASCII-punctuation). Напр. "**...FILE\***" → болд "...FILE*".
Не действует внутри инлайн-кода.
2. [x] / [ ] — ровно один символ в квадратных скобках → болд (нестандартно,
для читабельного отображения task-list checkbox-ов). Не внутри кода.
3. Соседние пункты списка: если следующий имеет МЕНЬШИЙ отступ (dedent),
пустую строку между ними больше не подавляем — пункты визуально разделены.
4. Абзац с ведущим отступом: все его перенесённые строки получают такой же
отступ (continuation-префикс из col пробелов, как у списка/цитаты).
Реализация — в обоих inline-сканерах (inline_scan/scan_join_stream) + ветках
index_lines; новый helper is_escapable() и leading_spaces().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Препроцессинг 52КБ занимает ~10с; теперь UI не ждёт его завершения.
index_lines() кооперативно вызывает progress_tick() (без прерываний —
IM2 отложен):
- первый экран рисуется, как только готово ≥ VIEW_H строк (~0.3с);
- статус-бар показывает растущее число обработанных строк с многоточием
("L 1-30 / 247...");
- ↑↓ PgUp PgDn Home End (=последняя готовая страница) и F1 работают по
УЖЕ готовым строкам (drawable_lines = [0..n_lines-2], т.к. последний
сегмент ещё в g_cells до flush);
- Esc/F10 во время загрузки — корректный выход (прерывание индексации
флагом g_abort + штатный unload_file/pal_reset/clrscr).
После каждого тика форсируем ре-маппинг W3 (cur_page=0xFF), т.к.
отрисовка/WINREST/BIOS могли сбить страницу, на которую опирается fb().
Временно: debug-индикатор времени обработки "t=Ns" в зоне имени файла
(по просьбе — следить за временем при дальнейших оптимизациях).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
После полной миграции на merge флаги g_cur_merged/g_next_merged всегда
равны 1 (все типы блоков строит форвард-сканер). Убраны сами флаги и ~30
гвардов `if (g_cur_merged)` в горячем посимвольном цикле — это и небольшое
ускорение (ветка на символ), и чистка.
Проверено замером: инлайн gc_put (макрос/inline) в этот регистро-нагруженный
цикл, наоборот, ЗАМЕДЛЯЕТ (~11→13с) из-за роста спиллов — оставлен функцией.
Вывод: per-char микрооптимизации здесь исчерпаны (asm-fb не помог, инлайн
навредил, снятие гвардов дало ~1с).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раньше препроцессинг сканировал файл ДВАЖДЫ: index_lines считал точки
переноса (forward-сканеры scan_join_stream/inline_scan), а отдельный
render_line_to_cache заново сканировал каждый сегмент для сборки
(char,attr)-ячеек. Профилирование показало, что всё время — в этих двух
посимвольных проходах (bank I/O и cont-walk ≈ 0).
Теперь forward-сканер собирает ячейки в g_cells ПО ХОДУ единственного
прохода; emit_seg флашит ячейки предыдущего сегмента в кэш (lag-1),
последний — после цикла. Перенос строки усекает буфер до снимка на
последнем пробеле (g_ncells_at_space), continuation-сегменты получают
префикс (отступ списка / маркер цитаты 0xB3 / title только в 1-й строке
заголовка). Прямые типы (код verbatim, HR, таблица через nowrap-inline,
blank/fence) строят ячейки на месте.
Миграция шла по типам блоков с dual-verify (старый render строил эталон,
merge сверял ячейки) — найдены и согласованы расхождения forward-сканера
со старым рендером: backtick внутри эмфазиса = литерал; одиночный маркер
закрывает ЛЮБОЙ активный эмфазис; soft-join пропускает ведущие пробелы
строки-продолжения; хвостовые пробелы нерелевантны. Все типы дали 0
расхождений, после чего render_line_to_cache / cc_put / cc_fill /
handle_inline_marker и verify-каркас удалены (−185 строк).
Итог на 52КБ README: 22→11с (2×). Совокупно с прошлым коммитом 24→11с.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Профилирование (52КБ README, раздельный замер): препроцессинг 24с, из них
bank I/O ~0с, cont-walk ~1с — всё время в посимвольных скан-циклах
(индексация 8с + рендер 13с, два прохода по файлу).
- fb() переписана на ассемблере (была закомментированная заготовка): убран
вызов функции и 32-битная арифметика на каждый байт. Раскладка __sdcccall(1)
для uint32 аргумента (p=HLDE) и возврат char в A сверены через sdcc -S;
координация маппинга с bank_read держится на том, что _io_page_w3 — порт
(__sfr 0xE2), пишем OUT — bank_read читает IN.
- render_line_to_cache: горячий путь обычного текста больше не вызывает
handle_inline_marker (6 аргументов) на каждый символ — только на маркерах
` * _ ~; cc_put заинлайнен. Поведение идентично.
Итого 24→18с. Индекс-скан (8с) лёгкого инлайна не имеет (нет per-char
вызовов). Следующий шаг — merge двух проходов в один.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Горизонтальный скролл (Фаза 5, финал):
- Скроллим по ТИПУ, а не по длине: новый флаг IF_HSCROLL ставится только
на код и таблицы; HR и границы fence (IF_NOWRAP без IF_HSCROLL) не
двигаются. Блок едет целиком, включая строки короче 80.
- Обход бага кодогенерации SDCC z80: `if (n!=g) g=n;` пишет (n-g) вместо n
(SUB сравнения затирает A, store переиспользует испорченный A). Лечится
записью viewport_x ДО сравнения. Минимальный репродьюсер и оба описания
для трекера — в docs/bugs/sdcc-z80-cmp-store-a/ (воспроизводится на чистом
sdcc 4.5, в т.ч. с --no-peep → это кодогенератор, не peephole).
Рендеринг:
- Отступленный fence (```c внутри списка) теперь распознаётся: is_fence_raw
пропускает ведущие пробелы/табы; то же в рендере прячет строку-границу.
- Строки-разделители (HR, ровно 80) больше не участвуют в скролле.
Чистка: удалён мёртвый код (is_fence_delim, get_init_style[_raw], is_cont,
seg_flags). Makefile (mdview/mdview2): iconv UTF-8→CP866 завершается ненулевым
кодом при отбрасывании символов — игнорируем (|| true).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New examples/mdview2 — render-cache version of mdview: each logical
line is rendered into an EMM (char,attr) buffer once during file load
(interleaved with index_lines()), then scrolling draws straight from
the cache via ESTEX WINREST, with no re-parsing or fb() access in the
steady state. Horizontal scroll still uses the old live-render path
(Phase 5, not yet migrated).
Format and budget were derisked empirically first (tests/winrest):
confirmed ESTEX WINCOPY/WINREST buffer layout and measured EMM free
space, both folded into the implementation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hello2 — stdio vs conio output API comparison (fast/no-attr vs
slower/attributed), also exercises textcolor/textbackground.
simple — packed 2-bit style array bit-twiddling smoke test.
banktest — __banked function calls across two explicit banks in huge
memory mode.
hello2.c updated to call the renamed gettextmode/settextmode (see the
conio rename in 5e5e70d).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mem_alloc_pages/mem_free_block/mem_get_page/mem_info, implemented twice:
mem_estex.c via ESTEX $3C-$3E (INFOMEM/GETMEM/FREEMEM), mem_bios.c via
the BIOS $C0/$C2-$C4/$C6 EMM_FN* calls. lib/Makefile already listed both
under LIBC_C.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
palette.h/video/palette.c wrap BIOS $A4 PIC_SET_PAL/PIC_GET_PAL and $A6
SET_PAL_INIT for the 8 palette pages (0-3 graphics, 4-7 text planes).
gfx_palette.c and conio/text_palette.c are thin per-domain wrappers;
text_palette.c maps the text "plane" 0..3 (paper/ink/blink-paper/
blink-ink) onto BIOS pages 4..7. Covered by tests/text_palette.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_get_cursor/_set_cursor now use BIOS GetCursor/SetCursor (RST 8, 08Eh/084h)
instead of ESTEX CURSOR/LOCATE; cursor position packed into a two_bytes
union. get_videotextmode/set_videotextmode renamed to gettextmode/settextmode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace per-character wrchar() loops in render_line/fill_row/help screens
with batched BIOS calls (bios_writeattr/bios_fillcharattr), and cache the
per-line index record (idx_get) instead of refetching it ~13 times per
rendered line — each refetch cost two W3 bank switches via bank_read().
Also fixes a pre-existing indexer bug: when word-wrap pushes a token that
starts with an emphasis/code marker (e.g. `_text_`) onto a new line, the
marker got re-scanned a second time during the wrap continuation, flipping
line_style back off and corrupting the rendered attribute of the next
word. Fixed by snapshotting line_style at the last seen space and rolling
back to that snapshot (both the saved continuation style and the live
scan state) when a wrap is taken.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Direct __naked z80 wrappers over BIOS LP_PRINT_*/LP_SET_PLACE/LP_GET_PLACE
(RST 8, opcodes 081h-08Eh) for fast text rendering — a single BIOS call
replaces a wrchar() loop for constant-attribute/constant-character runs.
Covered by tests/bios_text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- исправлен rewind до первого non-cont сегмента для continuation
- для quote-потока newline обрабатывается как soft join с пропуском сырого ' > ' маркера
- восстановлен quote-префикс на continuation строках
Co-Authored-By: Oz <oz-agent@warp.dev>
- Quote blocks now join consecutive quote lines into one paragraph with
soft breaks and wrap continuation under quote prefix.
- Empty quote lines ('>' / '> ') act as quote-paragraph separators.
- Nested quote starts ('> > ...') are not merged into the parent text and
stay separate rows.
- Keeps existing inline emphasis handling inside quote content.
Co-Authored-By: Oz <oz-agent@warp.dev>
Replace status-line printf formatting with compact manual decimal rendering
(u16/u8 right-aligned helpers + wrchar/put_str_attr). This removes runtime
printf usage from mdview.c and keeps fixed status columns without stale digits.
Co-Authored-By: Oz <oz-agent@warp.dev>
- List items (UL/OL) can now span multiple source lines: non-marker
lines are joined into the current item as lazy continuation.
- Continuation-line leading indentation is trimmed before joining so
wrapped item text is separated by a single space.
- A single blank line between adjacent list markers is suppressed
(same visual list), while 2+ blank lines still produce a separator.
Co-Authored-By: Oz <oz-agent@warp.dev>
- Paragraph scanning no longer swallows the blank line separating it from
the next block; runs of blank lines collapse to one row. Restores blank
separation between paragraphs, headers and horizontal rules.
- Detect table rows (first non-space char '|') as nowrap segments; they are
no longer merged into surrounding text or each other.
- Continuation (wrapped) rows render content as plain text and are no longer
re-classified, so a wrapped word starting with '-'/'#'/'>' is not mis-drawn
as a list/heading/quote marker.
Co-Authored-By: Oz <oz-agent@warp.dev>
- A trailing backslash before a newline now forces an in-paragraph line
break (like two trailing spaces); render consumes the marker (non-code).
- Add ~~strikethrough~~ inline style (INIT_STYLE_STRIKE / EM_STRIKE),
parsed in inline_scan, the paragraph merger and render, mirroring **.
- Horizontal pan is bounded by the widest nowrap segment on screen, and a
'<' indicator marks hidden content off the left edge.
Co-Authored-By: Oz <oz-agent@warp.dev>