From 982af127107c6ef872a879b71217c13fddfabd10 Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Sun, 7 Jun 2026 19:13:02 +0300 Subject: [PATCH] mdview: multiline list items with lazy continuation and blank-line grouping - List items (UL/OL) can now span multiple source lines: non-marker lines are joined into the current item as lazy continuation. - Continuation-line leading indentation is trimmed before joining so wrapped item text is separated by a single space. - A single blank line between adjacent list markers is suppressed (same visual list), while 2+ blank lines still produce a separator. Co-Authored-By: Oz --- examples/mdview/mdview.c | 168 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 8 deletions(-) diff --git a/examples/mdview/mdview.c b/examples/mdview/mdview.c index 50209ad..46e8d93 100644 --- a/examples/mdview/mdview.c +++ b/examples/mdview/mdview.c @@ -841,15 +841,167 @@ static void index_lines(void) continue; } - /* ---- List item: single line, not merged ---- */ + /* ---- List item: may span multiple source lines ---- */ if (kind == LK_ULIST || kind == LK_OLIST) { - uint8_t col = marker_visible_col(kind, p, content_off); - emit_seg(p, INIT_STYLE_PLAIN, CK_LIST, 0); - uint32_t line_end = content_off; - while (line_end < file_size && fb(line_end) != '\n') line_end++; - (void)inline_scan(content_off, line_end, col, CK_LIST, INIT_STYLE_PLAIN); - while (p < file_size && fb(p) != '\n') p++; - if (p < file_size) p++; + uint8_t col = marker_visible_col(kind, p, content_off); + uint8_t line_style = INIT_STYLE_PLAIN; + uint32_t item_start = p; + uint32_t q = content_off; + uint32_t last_space = 0xFFFFFFFFu; + char prev_ch = ' '; + uint8_t seg_col = col; + + emit_seg(p, line_style, CK_LIST, 0); + + while (q < file_size && n_lines < max_lines) { + char ch = fb(q); + uint8_t soft_break = 0; + + if (ch == '\n') { + uint32_t next = q + 1; + if (next >= file_size) break; + + /* Blank line ends the current list item. */ + if (is_line_blank(next)) break; + /* Block boundaries also end the item. */ + if (is_fence_raw(next) || is_hr_raw(next) || is_table_raw(next)) break; + + /* Next list marker starts a new item; same for quote/header/hr. */ + uint32_t next_content = next; + uint8_t next_kind = classify_line(next, &next_content); + if (next_kind == LK_ULIST || next_kind == LK_OLIST || + next_kind == LK_QUOTE || + (next_kind >= LK_H1 && next_kind <= LK_H4) || + next_kind == LK_HR) { + break; + } + + /* Lazy continuation: join the next non-marker line with + * one space; trim its leading indentation first. */ + while (next < file_size) { + char ws = fb(next); + if (ws == ' ' || ws == '\t') next++; + else break; + } + q = next; + soft_break = 1; + ch = ' '; + } + + if (ch == '`') { + if (line_style == INIT_STYLE_CODE) line_style = INIT_STYLE_PLAIN; + else line_style = INIT_STYLE_CODE; + if (!soft_break) q++; + continue; + } + if (ch == '*' && (q + 1) < file_size && fb(q + 1) == '*') { + char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n'; + if (is_emph_flanked(prev_ch, next_ch)) { + if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { + line_style = INIT_STYLE_BOLD; + if (!soft_break) q += 2; else q++; + continue; + } else if (line_style == INIT_STYLE_BOLD && ws_or_eol_or_delim(next_ch)) { + line_style = INIT_STYLE_PLAIN; + if (!soft_break) q += 2; else q++; + continue; + } + } + seg_col += 2; + if (!soft_break) q += 2; else q++; + prev_ch = '*'; continue; + } + if (ch == '~' && (q + 1) < file_size && fb(q + 1) == '~') { + char next_ch = ((q + 2) < file_size) ? fb(q + 2) : '\n'; + if (is_emph_flanked(prev_ch, next_ch)) { + if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { + line_style = INIT_STYLE_STRIKE; + if (!soft_break) q += 2; else q++; + continue; + } else if (line_style == INIT_STYLE_STRIKE && ws_or_eol_or_delim(next_ch)) { + line_style = INIT_STYLE_PLAIN; + if (!soft_break) q += 2; else q++; + continue; + } + } + seg_col += 2; + if (!soft_break) q += 2; else q++; + prev_ch = '~'; continue; + } + if (ch == '*' || ch == '_') { + char next_ch = ((q + 1) < file_size) ? fb(q + 1) : '\n'; + if (is_emph_flanked(prev_ch, next_ch)) { + uint8_t new_style = (ch == '*') ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER; + if (line_style == INIT_STYLE_PLAIN && ws_or_eol(prev_ch)) { + line_style = new_style; + if (!soft_break) q++; else q++; + continue; + } else if (line_style == new_style && ws_or_eol_or_delim(next_ch)) { + line_style = INIT_STYLE_PLAIN; + if (!soft_break) q++; else q++; + continue; + } + } + seg_col++; + if (!soft_break) q++; else q++; + prev_ch = ch; continue; + } + if (ch == '\t') { + uint8_t tgt = (uint8_t)((seg_col & (uint8_t)~(TAB_STOP - 1)) + TAB_STOP); + if (tgt > SCREEN_W) tgt = SCREEN_W; + seg_col = tgt; + if (!soft_break) q++; else q++; + prev_ch = ' '; continue; + } + if (ch == ' ') { + if (soft_break) last_space = q; + else last_space = q + 1; + seg_col++; + if (!soft_break) q++; else q++; + prev_ch = ' '; + } else { + seg_col++; + if (!soft_break) q++; else q++; + prev_ch = ch; + } + if (seg_col >= SCREEN_W) { + uint32_t wrap_at = (last_space != 0xFFFFFFFFu && last_space > cur_seg_off) + ? last_space : q; + if (wrap_at >= file_size || wrap_at == cur_seg_off) break; + emit_seg(wrap_at, line_style, CK_LIST, 1); + seg_col = col; last_space = 0xFFFFFFFFu; prev_ch = ' '; q = wrap_at; + } + } + + /* Move to start of the next physical line after the item. */ + if (q < file_size) { + while (q < file_size && fb(q) != '\n') q++; + if (q < file_size) q++; + } + + /* One blank line between adjacent list markers is suppressed + * (same list). Two+ blank lines keep a visible separator. */ + if (q < file_size && is_line_blank(q)) { + uint32_t t = q; + uint8_t blanks = 0; + while (t < file_size && is_line_blank(t)) { + while (t < file_size && fb(t) != '\n') t++; + if (t < file_size) t++; + blanks++; + if (blanks >= 2) break; + } + if (blanks == 1 && t < file_size) { + uint32_t d = t; + uint8_t nk = classify_line(t, &d); + if (nk == LK_ULIST || nk == LK_OLIST) { + p = t; /* suppress the single blank row */ + continue; + } + } + } + + p = q; + if (p < item_start) p = item_start; continue; }