From 4bed9d3f3f91eb5c1c3124d83a0e436bb83d3fe7 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Sun, 7 Jun 2026 20:26:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(mdview):=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=BD=D1=8B=D0=B9=20multiline=20quote=20join=20?= =?UTF-8?q?=D0=B2=20render=5Fline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - исправлен rewind до первого non-cont сегмента для continuation - для quote-потока newline обрабатывается как soft join с пропуском сырого ' > ' маркера - восстановлен quote-префикс на continuation строках Co-Authored-By: Oz --- examples/mdview/mdview.c | 94 +++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/examples/mdview/mdview.c b/examples/mdview/mdview.c index 48882ac..01a3570 100644 --- a/examples/mdview/mdview.c +++ b/examples/mdview/mdview.c @@ -988,10 +988,16 @@ static void index_lines(void) prev_ch = ' '; continue; } if (ch == ' ') { - if (soft_break) last_space = q; - else last_space = q + 1; - seg_col++; - if (!soft_break) q++; else q++; + if (soft_break) { + /* Virtual separator space between joined lines: + * do NOT consume source byte at q here. */ + last_space = q; + seg_col++; + } else { + last_space = q + 1; + seg_col++; + q++; + } prev_ch = ' '; } else { seg_col++; @@ -1095,6 +1101,12 @@ static void index_lines(void) if (ws == ' ' || ws == '\t') next_content++; else break; } + /* Страховка: если в точке склейки остался маркер quote, + * поглощаем его (и один пробел после него). */ + if (next_content < next_line_end && fb(next_content) == '>') { + next_content++; + if (next_content < next_line_end && fb(next_content) == ' ') next_content++; + } q = next_content; soft_break = 1; ch = ' '; @@ -1166,10 +1178,16 @@ static void index_lines(void) prev_ch = ' '; continue; } if (ch == ' ') { - if (soft_break) last_space = q; - else last_space = q + 1; - seg_col++; - if (!soft_break) q++; else q++; + if (soft_break) { + /* Виртуальный пробел между склеенными строками: + * исходный байт по q не потребляем. */ + last_space = q; + seg_col++; + } else { + last_space = q + 1; + seg_col++; + q++; + } prev_ch = ' '; } else { seg_col++; @@ -1311,10 +1329,16 @@ static void index_lines(void) prev_ch = ' '; continue; } if (ch == ' ') { - if (soft_break) last_space = q; - else last_space = q + 1; - seg_col++; - if (!soft_break) q++; else q++; + if (soft_break) { + /* Виртуальный пробел soft-break: символ по q + * обработаем на следующей итерации. */ + last_space = q; + seg_col++; + } else { + last_space = q + 1; + seg_col++; + q++; + } prev_ch = ' '; } else { seg_col++; @@ -1372,17 +1396,23 @@ static void render_line(uint16_t line_idx, uint8_t row) uint32_t seg_end = seg_off((uint16_t)(line_idx + 1)); if (seg_end > file_size) seg_end = file_size; uint8_t kind; + uint8_t quote_stream = 0; /* 1: этот сегмент принадлежит quote-параграфу */ /* Continuation seg → repeat prefix/indent from the first non-CONT * segment of this logical line, then render plain text. */ if (cont) { uint16_t first = line_idx; - while (first > 0 && is_cont((uint16_t)(first - 1))) first--; + /* Для любого continuation-сегмента откатываемся к первому + * НЕ-cont сегменту этой логической строки. */ + while (first > 0 && is_cont(first)) first--; uint32_t p_start = seg_off(first); uint32_t content_off = p_start; - uint8_t kind = classify_line(p_start, &content_off); - uint8_t indent = marker_visible_col(kind, p_start, content_off); - if (kind == LK_QUOTE) { + uint8_t first_kind = classify_line(p_start, &content_off); + uint8_t indent = marker_visible_col(first_kind, p_start, content_off); + if (first_kind == LK_QUOTE) { + /* Для continuation quote нужно помнить тип потока, + * чтобы далее на newline корректно пропускать сырой `>`. */ + quote_stream = 1; uint8_t q = 0; while (q + 2 < content_off && col < SCREEN_W && fb(p_start + q) == ' ') { wrchar(col++, row, ' ', ATTR_TEXT); @@ -1390,7 +1420,7 @@ static void render_line(uint16_t line_idx, uint8_t row) } if (col < SCREEN_W) wrchar(col++, row, 0xB3, ATTR_QUOTE_MARKER); if (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT); - } else if (kind == LK_ULIST || kind == LK_OLIST) { + } else if (first_kind == LK_ULIST || first_kind == LK_OLIST) { uint8_t q = 0; while (q < indent && col < SCREEN_W) { wrchar(col++, row, ' ', ATTR_TEXT); @@ -1414,6 +1444,7 @@ static void render_line(uint16_t line_idx, uint8_t row) cur_attr = ATTR_TEXT_CODE; parse_inline = 0; kind = LK_PLAIN; + quote_stream = 0; } else if (cont) { /* Continuation row: the parent prefix/indent is already drawn * above. Render the wrapped content as plain text — do NOT @@ -1422,6 +1453,7 @@ static void render_line(uint16_t line_idx, uint8_t row) kind = LK_PLAIN; } else { kind = classify_line(p, &content_off); + if (kind == LK_QUOTE) quote_stream = 1; } if (kind == LK_HR) { @@ -1484,6 +1516,7 @@ static void render_line(uint16_t line_idx, uint8_t row) uint8_t cc = 0; char prev_ch = ' '; /* for `_` flanking check */ while (p < seg_end && col < SCREEN_W) { + uint8_t soft_join = 0; /* 1: вставлен виртуальный пробел при склейке quote-строк */ char ch = fb(p); /* A backslash immediately before this segment's end-of-line is a * wide-break marker (index_lines split here) — consume it without @@ -1492,9 +1525,28 @@ static void render_line(uint16_t line_idx, uint8_t row) char nb = fb(p + 1); if (nb == '\n' || nb == '\r') { p++; continue; } } - /* Soft break (newline inside a paragraph segment) — render as a - * single space. Hard breaks are already separate segments. */ - if (ch == '\n' || ch == '\r') ch = ' '; + /* Soft break (newline inside a paragraph segment). + * Для quote-потока нужно не только отрисовать пробел, но и + * пропустить сырой quote-маркер следующей физической строки. */ + if (parse_inline && quote_stream && (ch == '\n' || ch == '\r')) { + uint32_t next = p + 1; + if (next < seg_end) { + uint32_t next_content = next; + if (classify_line(next, &next_content) == LK_QUOTE) { + /* Мягкая склейка quote-строк: newline -> ' ', + * а `> ` следующей строки не попадает в вывод. */ + p = next_content; + ch = ' '; + soft_join = 1; + } else { + ch = ' '; + } + } else { + ch = ' '; + } + } else if (ch == '\n' || ch == '\r') { + ch = ' '; + } /* --- inline emphasis markers (consumed, not rendered) --- * Asterisk / double-asterisk / underscore are markers only @@ -1577,7 +1629,7 @@ static void render_line(uint16_t line_idx, uint8_t row) } } - p++; + if (!soft_join) p++; if (ch == '\t') { /* Tab expands in CONTENT-col space (cc), each generated * space is then conditionally rendered through hpan. */