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>