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:
+70
-18
@@ -988,10 +988,16 @@ static void index_lines(void)
|
|||||||
prev_ch = ' '; continue;
|
prev_ch = ' '; continue;
|
||||||
}
|
}
|
||||||
if (ch == ' ') {
|
if (ch == ' ') {
|
||||||
if (soft_break) last_space = q;
|
if (soft_break) {
|
||||||
else last_space = q + 1;
|
/* Virtual separator space between joined lines:
|
||||||
|
* do NOT consume source byte at q here. */
|
||||||
|
last_space = q;
|
||||||
seg_col++;
|
seg_col++;
|
||||||
if (!soft_break) q++; else q++;
|
} else {
|
||||||
|
last_space = q + 1;
|
||||||
|
seg_col++;
|
||||||
|
q++;
|
||||||
|
}
|
||||||
prev_ch = ' ';
|
prev_ch = ' ';
|
||||||
} else {
|
} else {
|
||||||
seg_col++;
|
seg_col++;
|
||||||
@@ -1095,6 +1101,12 @@ static void index_lines(void)
|
|||||||
if (ws == ' ' || ws == '\t') next_content++;
|
if (ws == ' ' || ws == '\t') next_content++;
|
||||||
else break;
|
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;
|
q = next_content;
|
||||||
soft_break = 1;
|
soft_break = 1;
|
||||||
ch = ' ';
|
ch = ' ';
|
||||||
@@ -1166,10 +1178,16 @@ static void index_lines(void)
|
|||||||
prev_ch = ' '; continue;
|
prev_ch = ' '; continue;
|
||||||
}
|
}
|
||||||
if (ch == ' ') {
|
if (ch == ' ') {
|
||||||
if (soft_break) last_space = q;
|
if (soft_break) {
|
||||||
else last_space = q + 1;
|
/* Виртуальный пробел между склеенными строками:
|
||||||
|
* исходный байт по q не потребляем. */
|
||||||
|
last_space = q;
|
||||||
seg_col++;
|
seg_col++;
|
||||||
if (!soft_break) q++; else q++;
|
} else {
|
||||||
|
last_space = q + 1;
|
||||||
|
seg_col++;
|
||||||
|
q++;
|
||||||
|
}
|
||||||
prev_ch = ' ';
|
prev_ch = ' ';
|
||||||
} else {
|
} else {
|
||||||
seg_col++;
|
seg_col++;
|
||||||
@@ -1311,10 +1329,16 @@ static void index_lines(void)
|
|||||||
prev_ch = ' '; continue;
|
prev_ch = ' '; continue;
|
||||||
}
|
}
|
||||||
if (ch == ' ') {
|
if (ch == ' ') {
|
||||||
if (soft_break) last_space = q;
|
if (soft_break) {
|
||||||
else last_space = q + 1;
|
/* Виртуальный пробел soft-break: символ по q
|
||||||
|
* обработаем на следующей итерации. */
|
||||||
|
last_space = q;
|
||||||
seg_col++;
|
seg_col++;
|
||||||
if (!soft_break) q++; else q++;
|
} else {
|
||||||
|
last_space = q + 1;
|
||||||
|
seg_col++;
|
||||||
|
q++;
|
||||||
|
}
|
||||||
prev_ch = ' ';
|
prev_ch = ' ';
|
||||||
} else {
|
} else {
|
||||||
seg_col++;
|
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));
|
uint32_t seg_end = seg_off((uint16_t)(line_idx + 1));
|
||||||
if (seg_end > file_size) seg_end = file_size;
|
if (seg_end > file_size) seg_end = file_size;
|
||||||
uint8_t kind;
|
uint8_t kind;
|
||||||
|
uint8_t quote_stream = 0; /* 1: этот сегмент принадлежит quote-параграфу */
|
||||||
|
|
||||||
/* Continuation seg → repeat prefix/indent from the first non-CONT
|
/* Continuation seg → repeat prefix/indent from the first non-CONT
|
||||||
* segment of this logical line, then render plain text. */
|
* segment of this logical line, then render plain text. */
|
||||||
if (cont) {
|
if (cont) {
|
||||||
uint16_t first = line_idx;
|
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 p_start = seg_off(first);
|
||||||
uint32_t content_off = p_start;
|
uint32_t content_off = p_start;
|
||||||
uint8_t kind = classify_line(p_start, &content_off);
|
uint8_t first_kind = classify_line(p_start, &content_off);
|
||||||
uint8_t indent = marker_visible_col(kind, p_start, content_off);
|
uint8_t indent = marker_visible_col(first_kind, p_start, content_off);
|
||||||
if (kind == LK_QUOTE) {
|
if (first_kind == LK_QUOTE) {
|
||||||
|
/* Для continuation quote нужно помнить тип потока,
|
||||||
|
* чтобы далее на newline корректно пропускать сырой `>`. */
|
||||||
|
quote_stream = 1;
|
||||||
uint8_t q = 0;
|
uint8_t q = 0;
|
||||||
while (q + 2 < content_off && col < SCREEN_W && fb(p_start + q) == ' ') {
|
while (q + 2 < content_off && col < SCREEN_W && fb(p_start + q) == ' ') {
|
||||||
wrchar(col++, row, ' ', ATTR_TEXT);
|
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, 0xB3, ATTR_QUOTE_MARKER);
|
||||||
if (col < SCREEN_W) wrchar(col++, row, ' ', ATTR_TEXT);
|
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;
|
uint8_t q = 0;
|
||||||
while (q < indent && col < SCREEN_W) {
|
while (q < indent && col < SCREEN_W) {
|
||||||
wrchar(col++, row, ' ', ATTR_TEXT);
|
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;
|
cur_attr = ATTR_TEXT_CODE;
|
||||||
parse_inline = 0;
|
parse_inline = 0;
|
||||||
kind = LK_PLAIN;
|
kind = LK_PLAIN;
|
||||||
|
quote_stream = 0;
|
||||||
} else if (cont) {
|
} else if (cont) {
|
||||||
/* Continuation row: the parent prefix/indent is already drawn
|
/* Continuation row: the parent prefix/indent is already drawn
|
||||||
* above. Render the wrapped content as plain text — do NOT
|
* 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;
|
kind = LK_PLAIN;
|
||||||
} else {
|
} else {
|
||||||
kind = classify_line(p, &content_off);
|
kind = classify_line(p, &content_off);
|
||||||
|
if (kind == LK_QUOTE) quote_stream = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kind == LK_HR) {
|
if (kind == LK_HR) {
|
||||||
@@ -1484,6 +1516,7 @@ static void render_line(uint16_t line_idx, uint8_t row)
|
|||||||
uint8_t cc = 0;
|
uint8_t cc = 0;
|
||||||
char prev_ch = ' '; /* for `_` flanking check */
|
char prev_ch = ' '; /* for `_` flanking check */
|
||||||
while (p < seg_end && col < SCREEN_W) {
|
while (p < seg_end && col < SCREEN_W) {
|
||||||
|
uint8_t soft_join = 0; /* 1: вставлен виртуальный пробел при склейке quote-строк */
|
||||||
char ch = fb(p);
|
char ch = fb(p);
|
||||||
/* A backslash immediately before this segment's end-of-line is a
|
/* A backslash immediately before this segment's end-of-line is a
|
||||||
* wide-break marker (index_lines split here) — consume it without
|
* 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);
|
char nb = fb(p + 1);
|
||||||
if (nb == '\n' || nb == '\r') { p++; continue; }
|
if (nb == '\n' || nb == '\r') { p++; continue; }
|
||||||
}
|
}
|
||||||
/* Soft break (newline inside a paragraph segment) — render as a
|
/* Soft break (newline inside a paragraph segment).
|
||||||
* single space. Hard breaks are already separate segments. */
|
* Для quote-потока нужно не только отрисовать пробел, но и
|
||||||
if (ch == '\n' || ch == '\r') ch = ' ';
|
* пропустить сырой 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) ---
|
/* --- inline emphasis markers (consumed, not rendered) ---
|
||||||
* Asterisk / double-asterisk / underscore are markers only
|
* 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') {
|
if (ch == '\t') {
|
||||||
/* Tab expands in CONTENT-col space (cc), each generated
|
/* Tab expands in CONTENT-col space (cc), each generated
|
||||||
* space is then conditionally rendered through hpan. */
|
* space is then conditionally rendered through hpan. */
|
||||||
|
|||||||
Reference in New Issue
Block a user