fix(mdview): корректный multiline quote join в render_line
- исправлен rewind до первого non-cont сегмента для continuation - для quote-потока newline обрабатывается как soft join с пропуском сырого ' > ' маркера - восстановлен quote-префикс на continuation строках Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
+73
-21
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user