016fedd94c
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>
9.9 KiB
9.9 KiB
mdview — унифицированный рендеринг (Unified Wrap + Paragraph Model)
Цель
Отказаться от двух режимов Wrap/Unwrap. Ввести единую модель отображения Markdown-документов, соответствующую стандарту CommonMark:
- Обычный текст: параграфы склеиваются по soft breaks (
\n→ пробел), перенос по словам на 80 колонок. - Hard break (
\n): принудительный перенос внутри параграфа. - Paragraph break (
\n\n): новый абзац (пустая строка между блоками). - Fenced code block и таблицы (будущее): не переносятся, работают как truncate + горизонтальный скроллинг + индикатор
>. - Горизонтальный скроллинг активен только когда в текущем viewport есть nowrap-строки.
Текущее состояние
mdview.cиспользуетwrap_mode(0 = truncate, 1 = wrap) иtoggle_wrap()(F2).index_lines()делает два разных прохода: truncate (1 строка файла = 1 строка экрана) и wrap (сегментация по SCREEN_W).render_line()имеет ветвление поwrap_modeиcont.line_offset[],cont_flag[],in_code[],init_style[]— существующие структуры.
Предлагаемые изменения
1. Новые структуры данных
- Добавить
nowrap_flag[](битмап, аналогичноcont_flag[]): строка не должна переноситься (code block, HR, таблица). - Добавить
line_kind[](2 бита на строку, 512 байт для 2048 строк): хранит классификацию (PLAIN, H1-H4, HR, ULIST, OLIST, QUOTE, CODE, TABLE). Нужен для continuation-сегментов, чтобы render_line знал, какой префикс/отступ повторять. - Убрать
wrap_modeиtoggle_wrap(). - Убрать truncate-ветку из
index_lines()иrender_line(). viewport_xостаётся глобальным, но применяется только кnowrapстрокам.
2. Новый index_lines() — paragraph scanner
Единый проход по файлу, без двух режимов.
2.1 Сканирование «сущностей»
Walk по файлу от p = 0 до file_size:
- Если
fb(p) == '' × 3 → Fenced code block. Все строки до закрывающего```—nowrap_flag = 1,in_code = 1(для body),line_kind = CODE. Длинные строки не wrap'аются. - Если строка начинается с
|...|(будущее) → Table.nowrap_flag = 1,line_kind = TABLE. - Если строка — HR (
---/***/___) →nowrap_flag = 1,line_kind = HR. 1 строка экрана. - Если строка — Header (
#...), List (-/*/+/N./N)), Quote (>) → начало Normal paragraph / block. Обрабатывается как единый параграф до\n\nили EOF или code block. - Иначе — Normal paragraph (plain text).
2.2 Normal paragraph — обработка
Внутри параграфа символы читаются как единый поток (не прерываясь на \n):
\n\n→ конец параграфа. Emit текущую сегментную строку (если есть). Добавить пустую строку (1 строка экрана с нулевой длиной, или offset указывающий на второй\n).\n(два пробела или таб + пробел перед\n) → Hard break. Emit текущую сегментную строку. Начать новую сегментную строку с того же абзаца, сохранивline_style.prev_ch = ' '.- Обычный
\n→ Soft break. Считаем как 1 пробел:visible col++,prev_ch = ' '. Продолжаем текущую сегментную строку. - Wrap at
SCREEN_W— так же как сейчас, поlast_space. При создании continuation-сегмента:set_cont(),set_init_style_raw()(carry emphasis),set_line_kind()(carry list/quote kind для отступа/префикса). - Emphasis tracking (
line_style) работает через весь параграф, через soft/hard breaks.
2.3 Fenced code block — обработка
- Delimiter строка (
):line_kind = CODE,nowrap_flag = 1, рендерится как пустая строка (или строка с— но сейчас пустая). - Body строки: каждая физическая строка = 1 строка экрана.
nowrap_flag = 1,in_code = 1,line_kind = CODE. Нет inline parsing, нет wrap. - Если строка длиннее
SCREEN_W— truncation (не wrap).render_line()рисует>в последней колонке, если есть content за пределамиSCREEN_W + viewport_x.
2.4 List continuation
- Первая строка list item:
line_kind = ULIST/OLIST,marker_visible_colопределяет отступ. Текст начинается после маркера. - Wrap внутри list item:
set_line_kind()= тот жеULIST/OLIST.render_line()для continuation-сегмента не рисует маркер, но добавляет отступ =marker_visible_col(т.е. количество пробелов до текста первой строки). - Для вложенных списков отступ растёт, потому что
marker_visible_colучитывает ведущие пробелы.
2.5 Quote continuation
- Первая строка quote:
line_kind = QUOTE,marker_visible_col= отступ + 2 (для│). - Wrap внутри quote:
set_line_kind()=QUOTE.render_line()для continuation рисует префикс│(или пробелы, если символ не нужен на всех кроме первой строки).
3. Изменения в render_line()
- Убрать ветку
if (!wrap_mode)(truncate mode больше не существует). - Добавить
if (is_nowrap(line_idx)): применяетсяviewport_x, отображается>в колонке 79 если контент выходит за пределы. - Для
contсегментов (wrap continuation):- Если
line_kind==QUOTE: повторить префикс отступа +│(или только отступ, если решено показывать│только на первой строке). - Если
line_kind==ULIST/OLIST: повторить отступ =marker_visible_col(без маркера/цифр). Текст начинается с той же колонки, что и на первой строке item. - Для plain text: просто продолжить текст без префиксов.
- Если
- Для
nowrapстрок: применятьviewport_xкак смещение. Текст сдвигается влево, скрытые символы не рисуются. Если за пределами видимой зоны есть ещё контент —>в колонке 79.
4. Горизонтальный скроллинг
scroll_h()проверяет: есть ли в текущем viewport (top_line .. top_line+VIEW_H-1) хотя бы одна строка сnowrap_flag == 1. Если нет — return (no-op).- Для
nowrapстрокrender_line()используетviewport_xпри отображении: символы сcc < viewport_xпропускаются,cc >= viewport_xрисуются. - Для wrap-строк
viewport_xигнорируется (эффективно = 0). - При
Home/End/PgUp/PgDn/Up/Down—viewport_xне сбрасывается (пользователь может скроллить по вертикали, оставаясь на горизонтальном смещении для code block).
5. Очистка и навигация
- Убрать
wrap_mode,toggle_wrap(). - Убрать F2 из меню и help.
scroll_up/scroll_down/scroll_h/clamp_top— обновить без ссылок наwrap_mode.calc_pctи статусная строка — обновить, убрать ссылку на wrap/unwrap.
6. Порядок реализации (по файлу mdview.c)
- Структуры данных: добавить
nowrap_flag[],line_kind[], убратьwrap_mode. index_lines(): полностью переписать в paragraph scanner. Это самый сложный и объёмный блок.render_line(): убрать truncate, добавить nowrap/quote/list continuation, добавитьviewport_xдля nowrap.scroll_h(): добавить проверку наличия nowrap в viewport.- Очистка: убрать toggle_wrap, обновить меню/help, обновить main() (убрать F2).
- Тестирование: soft breaks, hard breaks, paragraph breaks, emphasis через soft breaks, fenced code blocks, list wrap с отступом, quote wrap с префиксом, горизонтальный скролл только для code.
Orchestration
План реализуется одним агентом (последовательно в одном файле), параллелизм не требуется. Дочерние агенты не используются.