# 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`:
1. Если `fb(p) == '`' × 3 → Fenced code block. Все строки до закрывающего ` ``` ` — `nowrap_flag = 1`, `in_code = 1` (для body), `line_kind = CODE`. Длинные строки не wrap'аются.
2. Если строка начинается с `|...|` (будущее) → Table. `nowrap_flag = 1`, `line_kind = TABLE`.
3. Если строка — HR (`---`/`***`/`___`) → `nowrap_flag = 1`, `line_kind = HR`. 1 строка экрана.
4. Если строка — Header (`#...`), List (`- ` / `* ` / `+ ` / `N. ` / `N) `), Quote (`> `) → начало **Normal paragraph / block**. Обрабатывается как единый параграф до `\n\n` или EOF или code block.
5. Иначе — 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)
1. **Структуры данных**: добавить `nowrap_flag[]`, `line_kind[]`, убрать `wrap_mode`.
2. **`index_lines()`**: полностью переписать в paragraph scanner. Это самый сложный и объёмный блок.
3. **`render_line()`**: убрать truncate, добавить nowrap/quote/list continuation, добавить `viewport_x` для nowrap.
4. **`scroll_h()`**: добавить проверку наличия nowrap в viewport.
5. **Очистка**: убрать toggle_wrap, обновить меню/help, обновить main() (убрать F2).
6. **Тестирование**: soft breaks, hard breaks, paragraph breaks, emphasis через soft breaks, fenced code blocks, list wrap с отступом, quote wrap с префиксом, горизонтальный скролл только для code.

## Orchestration
План реализуется одним агентом (последовательно в одном файле), параллелизм не требуется. Дочерние агенты не используются.