From e3b444ad6b6feea04a51707453625a6912025fdd Mon Sep 17 00:00:00 2001 From: RivoLink Date: Sat, 27 Jun 2026 23:29:49 +0300 Subject: [PATCH] feat: toggle code line numbers --- config.toml | 7 +++ src/app/content.rs | 2 + src/app/mod.rs | 6 +++ src/app/theme_picker.rs | 2 + src/config.rs | 2 + src/main.rs | 33 ++++++++++-- src/markdown/blocks.rs | 97 +++++++++++++++++++++------------- src/markdown/mod.rs | 49 ++++++++++++----- src/markdown/syntax.rs | 13 ++++- src/tests/app.rs | 67 +++++++++++++++++------ src/tests/markdown_blocks.rs | 23 ++++---- src/tests/markdown_embedded.rs | 27 +++++++--- src/tests/markdown_links.rs | 10 ++-- src/tests/markdown_lists.rs | 34 ++++++++---- src/tests/markdown_tables.rs | 22 ++++---- src/tests/render.rs | 18 +++++-- src/tests/theme.rs | 5 +- src/tests/toc.rs | 5 +- 18 files changed, 297 insertions(+), 125 deletions(-) diff --git a/config.toml b/config.toml index d5bfe42..5310d72 100644 --- a/config.toml +++ b/config.toml @@ -65,3 +65,10 @@ watch = false # Text files (txt, csv, log...) are rendered as plain Markdown. # Default: [] (Markdown only) # extras = ["txt", "csv", "rs", "java", "json", "yaml"] + +# Show line numbers inside fenced code blocks +# Only affects code block gutters, the main document line numbers are independent. +# Priority: LEAF_CODE_LINE_NUMBERS > this setting > default (true) +# Accepted env values: 1 = true, 0 = false (other values ignored) +# Default: true +# code-line-numbers = true diff --git a/src/app/content.rs b/src/app/content.rs index 4ea8b90..b639247 100644 --- a/src/app/content.rs +++ b/src/app/content.rs @@ -101,6 +101,7 @@ impl App { self.render_width, &at.markdown, self.file_mode, + self.code_line_numbers, ); let first_load = self.filepath.is_none(); @@ -139,6 +140,7 @@ impl App { self.render_width, &at.markdown, self.file_mode, + self.code_line_numbers, ); let new_total = parsed.lines.len(); diff --git a/src/app/mod.rs b/src/app/mod.rs index 84ebba9..26d6ed0 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -138,6 +138,7 @@ pub(crate) struct App { numkey_cycle: Option, reverse_mode: bool, pub(super) file_mode: bool, + pub(super) code_line_numbers: bool, max_width: Option, } @@ -287,6 +288,7 @@ impl App { numkey_cycle: None, reverse_mode: false, file_mode: false, + code_line_numbers: true, max_width: None, }; app.store_current_theme_preview(); @@ -302,6 +304,10 @@ impl App { self.file_mode = file_mode; } + pub(crate) fn set_code_line_numbers(&mut self, value: bool) { + self.code_line_numbers = value; + } + pub(crate) fn set_max_width(&mut self, max_width: Option) { self.max_width = max_width; } diff --git a/src/app/theme_picker.rs b/src/app/theme_picker.rs index 9d96a2e..e93669d 100644 --- a/src/app/theme_picker.rs +++ b/src/app/theme_picker.rs @@ -130,6 +130,7 @@ impl App { self.render_width, &at.markdown, self.file_mode, + self.code_line_numbers, ); self.store_theme_preview(preset, &parsed.lines, &parsed.toc); self.replace_content(parsed); @@ -153,6 +154,7 @@ impl App { self.render_width, &at.markdown, self.file_mode, + self.code_line_numbers, ); self.replace_content(parsed); } diff --git a/src/config.rs b/src/config.rs index ad8234b..1687e46 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,6 +19,8 @@ pub(crate) struct LeafConfig { pub(crate) watch: Option, pub(crate) width: Option, pub(crate) extras: Vec, + #[serde(rename = "code-line-numbers")] + pub(crate) code_line_numbers: Option, pub(crate) themes: BTreeMap, #[serde(skip)] pub(crate) config_dir: Option, diff --git a/src/main.rs b/src/main.rs index 11588e4..4393b81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,6 +96,17 @@ fn resolve_configured_width( config_width.map(|w| w.max(20)) } +fn resolve_code_line_numbers(config_value: Option) -> bool { + if let Ok(val) = std::env::var("LEAF_CODE_LINE_NUMBERS") { + match val.as_str() { + "1" => return true, + "0" => return false, + _ => {} + } + } + config_value.unwrap_or(true) +} + fn append_config_warning(warning: &mut Option, next: Option) { let Some(next) = next else { return; @@ -176,6 +187,7 @@ fn main() -> Result<()> { let watch_from_config = user_config.watch.unwrap_or(false); let max_width = resolve_configured_width(cli_width, user_config.width); + let code_line_numbers = resolve_code_line_numbers(user_config.code_line_numbers); if let Some(ref mut spec) = inline_spec { if spec.width.is_none() { @@ -293,8 +305,15 @@ fn main() -> Result<()> { let format = inline::resolve_format(spec, is_tty); let at = app_theme(); - let mut parsed = - parse_markdown_with_width(&src, &ss, &theme, width, &at.markdown, file_mode); + let mut parsed = parse_markdown_with_width( + &src, + &ss, + &theme, + width, + &at.markdown, + file_mode, + code_line_numbers, + ); while parsed.lines.last().is_some_and(|l| { l.spans.is_empty() || l.spans.iter().all(|s| s.content.trim().is_empty()) @@ -310,7 +329,14 @@ fn main() -> Result<()> { } let at = app_theme(); - let parsed = parse_markdown(&src, &ss, &theme, &at.markdown, file_mode); + let parsed = parse_markdown( + &src, + &ss, + &theme, + &at.markdown, + file_mode, + code_line_numbers, + ); let crate::markdown::ParseResult { lines, toc, @@ -338,6 +364,7 @@ fn main() -> Result<()> { app.set_extras(user_config.extras); app.set_file_mode(file_mode); app.set_editor_config(Some(resolved_editor)); + app.set_code_line_numbers(code_line_numbers); app.set_config_warning(config_warning); if let Some(dir) = dir_arg { app.set_dir_arg(dir); diff --git a/src/markdown/blocks.rs b/src/markdown/blocks.rs index 1c98365..38be28a 100644 --- a/src/markdown/blocks.rs +++ b/src/markdown/blocks.rs @@ -149,6 +149,9 @@ pub(super) fn push_heading_lines( } } +pub(super) const CODE_BLOCK_GUTTER: &str = "│"; +pub(super) const CODE_BLOCK_GUTTER_WRAP: &str = "┊"; + pub(super) struct CodeBlockRenderContext<'a> { pub(super) ss: &'a SyntaxSet, pub(super) theme: &'a Theme, @@ -157,6 +160,7 @@ pub(super) struct CodeBlockRenderContext<'a> { pub(super) blockquote_depth: usize, pub(super) list_stack: &'a [ListKind], pub(super) file_mode: bool, + pub(super) code_line_numbers: bool, } pub(super) fn push_code_block_lines( @@ -188,6 +192,7 @@ pub(super) fn push_code_block_lines( } else { code_lang.clone() }; + let show_line_numbers = ctx.code_line_numbers; let available_width = ctx.render_width.saturating_sub(prefix_width); let (code_lines, inner_width, digit_width) = highlight_code( code_buf, @@ -196,8 +201,13 @@ pub(super) fn push_code_block_lines( ctx.theme, available_width, ctx.file_mode, + show_line_numbers, ); - let gutter_width = digit_width + 2; + let gutter_width = if show_line_numbers { + digit_width + 2 + } else { + 1 + }; let gutter_style = Style::default().fg(ctx.theme_colors.code_gutter); let content_width = inner_width.saturating_sub(gutter_width + 1); @@ -220,16 +230,25 @@ pub(super) fn push_code_block_lines( ]); lines.push(Line::from(header)); + let plain_first_gutter = Span::styled(CODE_BLOCK_GUTTER, gutter_style); + let plain_cont_gutter = Span::styled(CODE_BLOCK_GUTTER_WRAP, gutter_style); + for (i, code_line) in code_lines.into_iter().enumerate() { - let line_num = i + 1; - let num_gutter = Span::styled(format!("│{:>w$}│", line_num, w = digit_width), gutter_style); - let blank_gutter = Span::styled(format!("│{:>w$}│", "", w = digit_width), gutter_style); + let (first_gutter, cont_gutter) = if show_line_numbers { + let line_num = i + 1; + ( + Span::styled(format!("│{:>w$}│", line_num, w = digit_width), gutter_style), + Span::styled(format!("│{:>w$}│", "", w = digit_width), gutter_style), + ) + } else { + (plain_first_gutter.clone(), plain_cont_gutter.clone()) + }; let mut first_prefix = prefix.clone(); - first_prefix.push(num_gutter); + first_prefix.push(first_gutter); let mut cont_prefix = prefix.clone(); - cont_prefix.push(blank_gutter); + cont_prefix.push(cont_gutter); push_wrapped_code_lines( lines, @@ -242,12 +261,17 @@ pub(super) fn push_code_block_lines( } let mut footer = prefix; - footer.push(Span::styled( + let footer_text = if show_line_numbers { format!( "└{}┴{}┘", "─".repeat(gutter_width - 2), "─".repeat(inner_width.saturating_sub(gutter_width - 1)) - ), + ) + } else { + format!("└{}┘", "─".repeat(inner_width)) + }; + footer.push(Span::styled( + footer_text, Style::default().fg(ctx.theme_colors.code_frame), )); lines.push(Line::from(footer)); @@ -332,11 +356,8 @@ pub(super) fn push_special_block_lines Vec>>( } else { String::new() }; - let border_only = if !show_line_numbers { - Some(Span::styled("│".to_string(), gutter_style)) - } else { - None - }; + let plain_first_gutter = Span::styled(CODE_BLOCK_GUTTER, gutter_style); + let plain_cont_gutter = Span::styled(CODE_BLOCK_GUTTER_WRAP, gutter_style); for (i, content_line) in content_lines.iter().enumerate() { let mut content_spans = (ctx.make_spans)(content_line); @@ -346,9 +367,9 @@ pub(super) fn push_special_block_lines Vec>>( let mut first_prefix = prefix.clone(); let mut cont_prefix = prefix.clone(); - if let Some(ref border) = border_only { - first_prefix.push(border.clone()); - cont_prefix.push(border.clone()); + if !show_line_numbers { + first_prefix.push(plain_first_gutter.clone()); + cont_prefix.push(plain_cont_gutter.clone()); } else { let line_num = i + 1; first_prefix.push(Span::styled( @@ -391,13 +412,18 @@ pub(super) fn push_special_block_lines Vec>>( lines.push(Line::from("")); } +pub(super) struct EmbeddedBlockCtx<'a> { + pub(super) render_width: usize, + pub(super) theme: &'a MarkdownTheme, + pub(super) blockquote_depth: usize, + pub(super) list_stack: &'a [ListKind], + pub(super) code_line_numbers: bool, +} + pub(super) fn push_latex_block_lines( lines: &mut Vec>, content: &str, - render_width: usize, - theme: &MarkdownTheme, - blockquote_depth: usize, - list_stack: &[ListKind], + ctx: EmbeddedBlockCtx<'_>, item_stack: &mut [ItemState], ) { let rendered = latex::to_unicode(content); @@ -410,18 +436,18 @@ pub(super) fn push_latex_block_lines( .iter() .rposition(|l| !l.trim().is_empty()) .map_or(start, |e| e + 1); - let content_style = Style::default().fg(theme.latex_block_fg); + let content_style = Style::default().fg(ctx.theme.latex_block_fg); push_special_block_lines( lines, - render_width, - theme, - blockquote_depth, - list_stack, + ctx.render_width, + ctx.theme, + ctx.blockquote_depth, + ctx.list_stack, item_stack, SpecialBlockCtx { label: "latex", content_lines: &all_lines[start..end], - show_line_numbers: true, + show_line_numbers: ctx.code_line_numbers, center: false, make_spans: |line| vec![Span::styled(line.to_string(), content_style)], }, @@ -431,10 +457,7 @@ pub(super) fn push_latex_block_lines( pub(super) fn push_mermaid_block_lines( lines: &mut Vec>, content: &str, - render_width: usize, - theme: &MarkdownTheme, - blockquote_depth: usize, - list_stack: &[ListKind], + ctx: EmbeddedBlockCtx<'_>, item_stack: &mut [ItemState], ) { let rendered = mermaid::render(content); @@ -444,24 +467,24 @@ pub(super) fn push_mermaid_block_lines( } else { content.lines().collect() }; - let content_style = Style::default().fg(theme.mermaid_block_fg); + let content_style = Style::default().fg(ctx.theme.mermaid_block_fg); push_special_block_lines( lines, - render_width, - theme, - blockquote_depth, - list_stack, + ctx.render_width, + ctx.theme, + ctx.blockquote_depth, + ctx.list_stack, item_stack, SpecialBlockCtx { label: "mermaid", content_lines: &content_lines, - show_line_numbers: !use_rendered, + show_line_numbers: !use_rendered && ctx.code_line_numbers, center: use_rendered, make_spans: |line| { if use_rendered { vec![Span::styled(line.to_string(), content_style)] } else { - mermaid::colorize_line(line, theme) + mermaid::colorize_line(line, ctx.theme) } }, }, diff --git a/src/markdown/mod.rs b/src/markdown/mod.rs index 925ab4b..b865ade 100644 --- a/src/markdown/mod.rs +++ b/src/markdown/mod.rs @@ -41,7 +41,7 @@ use toc::{normalize_toc, TocEntry}; use blocks::{ flush_wrapped_spans, push_code_block_lines, push_heading_lines, push_latex_block_lines, push_mermaid_block_lines, push_rule_line, trim_paragraph_gap_before_block, - CodeBlockRenderContext, + CodeBlockRenderContext, EmbeddedBlockCtx, CODE_BLOCK_GUTTER, }; use fences::normalize_code_fences; use links::build_link_spans; @@ -253,8 +253,17 @@ pub(crate) fn parse_markdown( theme: &syntect::highlighting::Theme, md_theme: &MarkdownTheme, file_mode: bool, + code_line_numbers: bool, ) -> ParseResult { - parse_markdown_with_width(src, ss, theme, DEFAULT_RENDER_WIDTH, md_theme, file_mode) + parse_markdown_with_width( + src, + ss, + theme, + DEFAULT_RENDER_WIDTH, + md_theme, + file_mode, + code_line_numbers, + ) } pub(crate) fn parse_markdown_with_width( @@ -264,6 +273,7 @@ pub(crate) fn parse_markdown_with_width( render_width: usize, theme_colors: &MarkdownTheme, file_mode: bool, + code_line_numbers: bool, ) -> ParseResult { let original_src = src; let (src, fm_pairs) = frontmatter::extract_frontmatter(src); @@ -377,10 +387,13 @@ pub(crate) fn parse_markdown_with_width( push_latex_block_lines( &mut lines, &code_buf, - render_width, - theme_colors, - blockquote_depth, - &list_stack, + EmbeddedBlockCtx { + render_width, + theme: theme_colors, + blockquote_depth, + list_stack: &list_stack, + code_line_numbers, + }, &mut item_stack, ); code_buf.clear(); @@ -390,10 +403,13 @@ pub(crate) fn parse_markdown_with_width( push_mermaid_block_lines( &mut lines, &code_buf, - render_width, - theme_colors, - blockquote_depth, - &list_stack, + EmbeddedBlockCtx { + render_width, + theme: theme_colors, + blockquote_depth, + list_stack: &list_stack, + code_line_numbers, + }, &mut item_stack, ); code_buf.clear(); @@ -411,6 +427,7 @@ pub(crate) fn parse_markdown_with_width( blockquote_depth, list_stack: &list_stack, file_mode, + code_line_numbers, }, &mut item_stack, ); @@ -525,10 +542,13 @@ pub(crate) fn parse_markdown_with_width( push_latex_block_lines( &mut lines, text.as_ref(), - render_width, - theme_colors, - blockquote_depth, - &list_stack, + EmbeddedBlockCtx { + render_width, + theme: theme_colors, + blockquote_depth, + list_stack: &list_stack, + code_line_numbers, + }, &mut item_stack, ); wraps = true; @@ -615,6 +635,7 @@ impl LineMapState { || (c.starts_with('│') && c.ends_with('│') && c.chars().any(|ch| ch.is_ascii_digit())) + || c == CODE_BLOCK_GUTTER }); self.push(is_new); } diff --git a/src/markdown/syntax.rs b/src/markdown/syntax.rs index 54e78cc..4836a1a 100644 --- a/src/markdown/syntax.rs +++ b/src/markdown/syntax.rs @@ -89,6 +89,7 @@ pub(super) fn highlight_code( theme: &Theme, render_width: usize, full_width: bool, + show_line_numbers: bool, ) -> (Vec, usize, usize) { let syntax = resolve_syntax(lang, ss); let mut hl = HighlightLines::new(syntax, theme); @@ -130,14 +131,22 @@ pub(super) fn highlight_code( let label = if lang.is_empty() { "text" } else { lang }; let total_lines = raw.len(); - let digit_width = total_lines.max(1).to_string().len(); + let digit_width = if show_line_numbers { + total_lines.max(1).to_string().len() + } else { + 0 + }; + let gutter_width = if show_line_numbers { + digit_width + 2 + } else { + 1 + }; let max_inner_width = render_width .saturating_sub(2) .max(UnicodeWidthStr::width(label) + 3); let inner_width = if full_width { max_inner_width } else { - let gutter_width = digit_width + 2; let max_text = raw.iter().map(|(_, w)| *w).max().unwrap_or(0); let min_inner = (UnicodeWidthStr::width(label) + 3) .max(44) diff --git a/src/tests/app.rs b/src/tests/app.rs index bc380c8..f113214 100644 --- a/src/tests/app.rs +++ b/src/tests/app.rs @@ -13,8 +13,15 @@ use syntect::highlighting::ThemeSet; #[test] fn search_matches_across_span_boundaries() { let (ss, theme) = test_assets(); - let (lines, toc, _, _) = - parse_markdown("hello **world**", &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = parse_markdown( + "hello **world**", + &ss, + &theme, + &test_md_theme(), + false, + true, + ) + .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); app.set_search_query("hello world"); @@ -175,6 +182,7 @@ fn cancelling_search_clears_query_and_matches() { &theme, &test_md_theme(), false, + true, ) .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); @@ -196,8 +204,15 @@ fn cancelling_search_clears_query_and_matches() { #[test] fn confirm_search_uses_draft_and_updates_matches() { let (ss, theme) = test_assets(); - let (lines, toc, _, _) = - parse_markdown("alpha\nbeta\nbeta\n", &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = parse_markdown( + "alpha\nbeta\nbeta\n", + &ss, + &theme, + &test_md_theme(), + false, + true, + ) + .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); app.begin_search(); @@ -219,6 +234,7 @@ fn confirm_search_with_new_query_restarts_from_first_match() { &theme, &test_md_theme(), false, + true, ) .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); @@ -245,6 +261,7 @@ fn enter_in_normal_mode_advances_active_search() { &theme, &test_md_theme(), false, + true, ) .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); @@ -263,7 +280,7 @@ fn enter_in_normal_mode_advances_active_search() { fn ctrl_c_cancels_search_prompt_and_clears_active_query() { let (ss, theme) = test_assets(); let (lines, toc, _, _) = - parse_markdown("alpha\nbeta\n", &ss, &theme, &test_md_theme(), false).into(); + parse_markdown("alpha\nbeta\n", &ss, &theme, &test_md_theme(), false, true).into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); app.set_search_query("alpha"); @@ -282,8 +299,15 @@ fn ctrl_c_cancels_search_prompt_and_clears_active_query() { #[test] fn esc_clears_active_search_from_normal_mode() { let (ss, theme) = test_assets(); - let (lines, toc, _, _) = - parse_markdown("alpha\nbeta alpha\n", &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = parse_markdown( + "alpha\nbeta alpha\n", + &ss, + &theme, + &test_md_theme(), + false, + true, + ) + .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); app.set_search_query("alpha"); @@ -300,8 +324,15 @@ fn esc_clears_active_search_from_normal_mode() { #[test] fn ctrl_c_clears_active_search_before_exit() { let (ss, theme) = test_assets(); - let (lines, toc, _, _) = - parse_markdown("alpha\nbeta alpha\n", &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = parse_markdown( + "alpha\nbeta alpha\n", + &ss, + &theme, + &test_md_theme(), + false, + true, + ) + .into(); let mut app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); app.set_search_query("alpha"); @@ -317,7 +348,7 @@ fn ctrl_c_clears_active_search_before_exit() { fn active_highlight_line_is_none_without_search_matches() { let (ss, theme) = test_assets(); let (lines, toc, _, _) = - parse_markdown("alpha\nbeta\n", &ss, &theme, &test_md_theme(), false).into(); + parse_markdown("alpha\nbeta\n", &ss, &theme, &test_md_theme(), false, true).into(); let app = App::new(lines, toc, "stdin".to_string(), false, false, None, None); assert_eq!(app.active_highlight_line(), None); @@ -334,7 +365,8 @@ fn check_modified_detects_file_metadata_change() { fs::write(&path, "# Before\n").unwrap(); let src = fs::read_to_string(&path).unwrap(); - let (lines, toc, _, _) = parse_markdown(&src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = + parse_markdown(&src, &ss, &theme, &test_md_theme(), false, true).into(); let state = read_file_state(&path).unwrap(); let mut app = App::new_with_source( lines, @@ -404,7 +436,7 @@ fn sync_render_width_preserves_scroll_proportion() { .collect::>() .join("\n\n"); let (lines, toc, _, _) = - parse_markdown_with_width(&source, &ss, &theme, 80, &test_md_theme(), false).into(); + parse_markdown_with_width(&source, &ss, &theme, 80, &test_md_theme(), false, true).into(); let mut app = App::new_with_source( lines, toc, @@ -439,7 +471,8 @@ fn check_modified_reports_metadata_when_no_previous_file_state() { fs::write(&path, "# Initial\n").unwrap(); let src = fs::read_to_string(&path).unwrap(); - let (lines, toc, _, _) = parse_markdown(&src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = + parse_markdown(&src, &ss, &theme, &test_md_theme(), false, true).into(); let mut app = App::new_with_source( lines, toc, @@ -468,7 +501,7 @@ fn sync_render_width_returns_false_when_clamped_width_is_unchanged() { let ts = ThemeSet::load_defaults(); let source = "One paragraph that does not matter much for this width clamp test."; let (lines, toc, _, _) = - parse_markdown_with_width(source, &ss, &theme, 20, &test_md_theme(), false).into(); + parse_markdown_with_width(source, &ss, &theme, 20, &test_md_theme(), false, true).into(); let mut app = App::new_with_source( lines, toc, @@ -486,7 +519,7 @@ fn sync_render_width_returns_false_when_clamped_width_is_unchanged() { assert!(!app.sync_render_width(10, &ss, &ts)); assert_eq!( app.total(), - parse_markdown_with_width(source, &ss, &theme, 20, &test_md_theme(), false) + parse_markdown_with_width(source, &ss, &theme, 20, &test_md_theme(), false, true) .lines .len() ); @@ -495,7 +528,7 @@ fn sync_render_width_returns_false_when_clamped_width_is_unchanged() { #[test] fn initial_mode_has_no_content() { let (ss, theme) = test_assets(); - let (lines, toc, _, _) = parse_markdown("", &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = parse_markdown("", &ss, &theme, &test_md_theme(), false, true).into(); let app = App::new_with_source( lines, toc, @@ -515,7 +548,7 @@ fn initial_mode_has_no_content() { fn preview_mode_has_content() { let src = "# Hello"; let (ss, theme) = test_assets(); - let (lines, toc, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, toc, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let app = App::new_with_source( lines, toc, diff --git a/src/tests/markdown_blocks.rs b/src/tests/markdown_blocks.rs index b5827db..1bdc41c 100644 --- a/src/tests/markdown_blocks.rs +++ b/src/tests/markdown_blocks.rs @@ -5,7 +5,8 @@ use crate::*; #[test] fn h1_headings_render_double_rule_without_bottom_spacing() { let (ss, theme) = test_assets(); - let (lines, _, _, _) = parse_markdown("# 東京\n", &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = + parse_markdown("# 東京\n", &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert_eq!(rendered[0], "東京"); @@ -16,7 +17,7 @@ fn h1_headings_render_double_rule_without_bottom_spacing() { fn paragraph_and_following_code_block_have_no_blank_gap() { let (ss, theme) = test_assets(); let src = "Intro paragraph\n\n```rs\nfn main() {}\n```\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered: Vec = lines.iter().map(line_plain_text).collect(); let intro_idx = rendered .iter() @@ -30,7 +31,7 @@ fn paragraph_and_following_code_block_have_no_blank_gap() { fn nested_blockquotes_keep_quote_prefix_after_inner_quote_ends() { let (ss, theme) = test_assets(); let src = "> outer\n> > inner\n> outer again\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!(rendered.iter().any(|line| line == "▏ outer")); @@ -43,7 +44,7 @@ fn long_blockquotes_wrap_into_multiple_prefixed_lines() { let (ss, theme) = test_assets(); let src = "> This is a long blockquote line that should wrap into multiple quoted lines at narrow widths.\n"; let (lines, _, _, _) = - parse_markdown_with_width(src, &ss, &theme, 28, &test_md_theme(), false).into(); + parse_markdown_with_width(src, &ss, &theme, 28, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let quoted: Vec<_> = rendered .into_iter() @@ -58,7 +59,7 @@ fn long_blockquotes_wrap_into_multiple_prefixed_lines() { fn frontmatter_is_ignored_in_preview() { let (ss, theme) = test_assets(); let src = "---\ntitle: Demo\nowner: me\n---\n# Visible\nBody\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!(!rendered.iter().any(|line| line.contains("title: Demo"))); @@ -75,6 +76,7 @@ fn h2_headings_are_underlined_and_compact() { 40, &test_md_theme(), false, + true, ) .into(); let rendered = rendered_non_empty_lines(&lines); @@ -93,6 +95,7 @@ fn rules_use_render_width_without_extra_blank_after() { 24, &test_md_theme(), false, + true, ) .into(); let rendered = rendered_non_empty_lines(&lines); @@ -110,7 +113,7 @@ fn rules_use_render_width_without_extra_blank_after() { fn source_line_map_plain_document_is_aligned_with_first_event() { let (ss, theme) = test_assets(); let src = "# Title\n\nfirst paragraph\n"; - let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false); + let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true); let first = parsed.source_line_map[0]; let last_content_idx = parsed.lines.len().saturating_sub(6); assert_eq!(first, 1); @@ -121,7 +124,7 @@ fn source_line_map_plain_document_is_aligned_with_first_event() { fn source_line_map_skips_code_block_fence_drift() { let (ss, theme) = test_assets(); let src = "intro\n\n```rs\nfn main() {}\n```\nend\n"; - let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false); + let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true); let rendered: Vec = parsed.lines.iter().map(line_plain_text).collect(); let code_row = rendered .iter() @@ -139,7 +142,7 @@ fn source_line_map_skips_code_block_fence_drift() { fn source_line_map_frontmatter_lines_point_to_line_one() { let (ss, theme) = test_assets(); let src = "---\ntitle: Hello\nauthor: Me\n---\nbody\n"; - let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false); + let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true); assert_eq!(parsed.source_line_map[0], 1); let rendered: Vec = parsed.lines.iter().map(line_plain_text).collect(); let body_idx = rendered @@ -153,7 +156,7 @@ fn source_line_map_frontmatter_lines_point_to_line_one() { fn source_line_map_file_mode_first_content_line_is_one() { let (ss, theme) = test_assets(); let wrapped = App::fence_wrap("fn main() {}\n", "rs"); - let parsed = parse_markdown(&wrapped, &ss, &theme, &test_md_theme(), true); + let parsed = parse_markdown(&wrapped, &ss, &theme, &test_md_theme(), true, true); let rendered: Vec = parsed.lines.iter().map(line_plain_text).collect(); let code_row = rendered .iter() @@ -166,7 +169,7 @@ fn source_line_map_file_mode_first_content_line_is_one() { fn source_line_map_padding_repeats_last_event_source_line() { let (ss, theme) = test_assets(); let src = "first\n\nmiddle\n\nlast\n"; - let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false); + let parsed = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true); let total = parsed.lines.len(); let last = parsed.source_line_map[total - 1]; assert_eq!(last, 5); diff --git a/src/tests/markdown_embedded.rs b/src/tests/markdown_embedded.rs index f43cf0b..22d8b92 100644 --- a/src/tests/markdown_embedded.rs +++ b/src/tests/markdown_embedded.rs @@ -11,6 +11,7 @@ fn inline_latex_renders_with_latex_style() { &theme, &test_md_theme(), false, + true, ) .into(); @@ -35,7 +36,7 @@ fn inline_latex_renders_with_latex_style() { fn display_latex_renders_in_framed_block() { let (ss, theme) = test_assets(); let (lines, _, _, _) = - parse_markdown("$$E = mc^2$$\n", &ss, &theme, &test_md_theme(), false).into(); + parse_markdown("$$E = mc^2$$\n", &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!( @@ -61,6 +62,7 @@ fn inline_latex_is_searchable() { &theme, &test_md_theme(), false, + true, ) .into(); let searchable: Vec = lines.iter().map(line_plain_text).collect(); @@ -75,7 +77,7 @@ fn inline_latex_is_searchable() { fn display_latex_in_blockquote_has_quote_prefix() { let (ss, theme) = test_assets(); let (lines, _, _, _) = - parse_markdown("> $$F = ma$$\n", &ss, &theme, &test_md_theme(), false).into(); + parse_markdown("> $$F = ma$$\n", &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let header = rendered @@ -97,6 +99,7 @@ fn mermaid_block_renders_in_framed_block() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered = rendered_non_empty_lines(&lines); @@ -128,6 +131,7 @@ fn mermaid_block_in_blockquote_has_quote_prefix() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered = rendered_non_empty_lines(&lines); @@ -151,6 +155,7 @@ fn mermaid_content_is_searchable() { &theme, &test_md_theme(), false, + true, ) .into(); let searchable = markdown::width::build_searchable_lines(&lines); @@ -174,6 +179,7 @@ fn mermaid_rendered_block_has_no_gutter() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered = rendered_non_empty_lines(&lines); @@ -192,7 +198,7 @@ fn mermaid_rendered_block_has_no_gutter() { fn mermaid_fallback_has_numbered_gutter() { let (ss, theme) = test_assets(); let src = "```mermaid\ngantt\n title Schedule\n section Dev\n```\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!( @@ -205,7 +211,7 @@ fn mermaid_fallback_has_numbered_gutter() { fn mermaid_pie_renders_bar_chart() { let (ss, theme) = test_assets(); let src = "```mermaid\npie title Languages\n \"Rust\" : 65\n \"Go\" : 35\n```\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!( @@ -234,7 +240,7 @@ fn mermaid_pie_renders_bar_chart() { fn mermaid_unsupported_type_falls_back_to_colored_source() { let (ss, theme) = test_assets(); let src = "```mermaid\ngantt\n title Schedule\n section Phase 1\n```\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!( @@ -250,8 +256,15 @@ fn mermaid_unsupported_type_falls_back_to_colored_source() { #[test] fn mermaid_empty_block_renders_without_crash() { let (ss, theme) = test_assets(); - let (lines, _, _, _) = - parse_markdown("```mermaid\n```\n", &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown( + "```mermaid\n```\n", + &ss, + &theme, + &test_md_theme(), + false, + true, + ) + .into(); let rendered = rendered_non_empty_lines(&lines); assert!( diff --git a/src/tests/markdown_links.rs b/src/tests/markdown_links.rs index 08cb5c8..6594957 100644 --- a/src/tests/markdown_links.rs +++ b/src/tests/markdown_links.rs @@ -11,7 +11,7 @@ use syntect::parsing::SyntaxSet; fn blockquote_bold_link_preserves_link_color() { let (ss, theme) = test_assets(); let src = "> text [**lien bold**](https://rivolink.mg)\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let app_theme = app_theme(); let theme_colors = &app_theme.markdown; @@ -46,7 +46,8 @@ fn link_spans_detected_for_all_link_types() { [A](https://example.com/a) and [B](https://example.com/b) "; - let (_, _, link_spans, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (_, _, link_spans, _) = + parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let urls: Vec<&str> = link_spans.iter().map(|ls| ls.url.as_str()).collect(); @@ -106,7 +107,8 @@ fn link_spans_in_table_are_detected() { |------|------| | Test | [example](https://example.com/table) | "; - let (_, _, link_spans, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (_, _, link_spans, _) = + parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let urls: Vec<&str> = link_spans.iter().map(|ls| ls.url.as_str()).collect(); assert!( @@ -263,7 +265,7 @@ fn resolve_syntax_php_uses_php_source() { fn php_code_block_without_open_tag_is_highlighted() { let (ss, theme) = test_assets(); let md = "```php\nforeach ($map as $item) {\n $scores[] = $item ?? 0;\n}\n```\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let span_fg = |needle: &str| { lines diff --git a/src/tests/markdown_lists.rs b/src/tests/markdown_lists.rs index e8d018f..4183540 100644 --- a/src/tests/markdown_lists.rs +++ b/src/tests/markdown_lists.rs @@ -12,6 +12,7 @@ fn loose_list_items_keep_their_markers() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered: Vec = lines.iter().map(line_plain_text).collect(); @@ -29,6 +30,7 @@ fn ordered_lists_render_numeric_markers() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered: Vec = lines.iter().map(line_plain_text).collect(); @@ -46,6 +48,7 @@ fn multiline_list_items_keep_marker_only_on_first_line() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered: Vec = lines.iter().map(line_plain_text).collect(); @@ -67,8 +70,15 @@ fn multiline_list_items_keep_marker_only_on_first_line() { #[test] fn ordered_lists_preserve_non_default_start_numbers() { let (ss, theme) = test_assets(); - let (lines, _, _, _) = - parse_markdown("7. seven\n8. eight\n", &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown( + "7. seven\n8. eight\n", + &ss, + &theme, + &test_md_theme(), + false, + true, + ) + .into(); let rendered: Vec = lines.iter().map(line_plain_text).collect(); assert!(rendered.iter().any(|line| line.contains("7. seven"))); @@ -79,7 +89,7 @@ fn ordered_lists_preserve_non_default_start_numbers() { fn loose_list_items_render_expected_lines() { let (ss, theme) = test_assets(); let src = "- first loose item\n\n- second loose item after a blank line\n\n- third loose item\n\n continuation paragraph\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert_eq!( @@ -97,7 +107,7 @@ fn loose_list_items_render_expected_lines() { fn ordered_loose_lists_render_expected_lines() { let (ss, theme) = test_assets(); let src = "7. seventh item\n\n8. eighth item\n\n continuation paragraph\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert_eq!( @@ -119,6 +129,7 @@ fn ordered_lists_render_expected_lines() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered = rendered_non_empty_lines(&lines); @@ -135,6 +146,7 @@ fn paragraph_and_following_list_have_no_blank_gap() { &theme, &test_md_theme(), false, + true, ) .into(); let rendered: Vec = lines.iter().map(line_plain_text).collect(); @@ -151,7 +163,7 @@ fn wrapped_list_items_align_continuation_under_text() { let (ss, theme) = test_assets(); let src = "- First item with enough text to wrap when the terminal is narrow and show continuation alignment.\n8. Eighth item with enough text to wrap and keep numeric alignment readable.\n"; let (lines, _, _, _) = - parse_markdown_with_width(src, &ss, &theme, 36, &test_md_theme(), false).into(); + parse_markdown_with_width(src, &ss, &theme, 36, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!(rendered.iter().any(|line| line.starts_with("• First item"))); @@ -170,7 +182,7 @@ fn wrapped_list_items_align_continuation_under_text() { fn tight_nested_list_separates_parent_and_children() { let (ss, theme) = test_assets(); let src = "- parent\n - child 1\n - child 2\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert_eq!(rendered, vec!["• parent", " ◦ child 1", " ◦ child 2"]); @@ -180,7 +192,7 @@ fn tight_nested_list_separates_parent_and_children() { fn tight_nested_list_three_levels_uses_correct_markers() { let (ss, theme) = test_assets(); let src = "- level 1\n - level 2\n - level 3\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert_eq!(rendered, vec!["• level 1", " ◦ level 2", " ▸ level 3"]); @@ -190,7 +202,7 @@ fn tight_nested_list_three_levels_uses_correct_markers() { fn tight_nested_list_unordered_parent_with_ordered_children() { let (ss, theme) = test_assets(); let src = "- parent\n 1. first\n 2. second\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert_eq!(rendered, vec!["• parent", " 1. first", " 2. second"]); @@ -200,7 +212,7 @@ fn tight_nested_list_unordered_parent_with_ordered_children() { fn tight_nested_list_multiline_parent_with_softbreak() { let (ss, theme) = test_assets(); let src = "- parent line one\n parent line two\n - child\n"; - let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!(rendered.iter().any(|line| line == "• parent line one")); @@ -215,7 +227,7 @@ fn wrapped_list_inline_code_keeps_left_padding_in_rendered_line() { let (ss, theme) = test_assets(); let source = "- `leaf --theme ocean README.md` exercises wrapping inside a list item.\n"; let (lines, _, _, _) = - parse_markdown_with_width(source, &ss, &theme, 22, &test_md_theme(), false).into(); + parse_markdown_with_width(source, &ss, &theme, 22, &test_md_theme(), false, true).into(); let target = lines .iter() @@ -242,7 +254,7 @@ fn task_markers_have_uniform_width() { fn code_block_inside_list_item_is_indented_and_has_no_blank_gap_before() { let (ss, theme) = test_assets(); let md = "To put a code block within a list item, the code block needs\nto be indented *twice* -- 8 spaces or two tabs:\n\n* A list item with a code block:\n\n \n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let item_idx = rendered diff --git a/src/tests/markdown_tables.rs b/src/tests/markdown_tables.rs index 8a1c4a1..e242269 100644 --- a/src/tests/markdown_tables.rs +++ b/src/tests/markdown_tables.rs @@ -8,7 +8,7 @@ fn narrow_tables_fit_render_width_and_wrap_cells() { let (ss, theme) = test_assets(); let md = "| Column | Description | Value |\n| --- | --- | ---: |\n| Width | Terminal-dependent layout behavior | 80 |\n"; let (lines, _, _, _) = - parse_markdown_with_width(md, &ss, &theme, 36, &test_md_theme(), false).into(); + parse_markdown_with_width(md, &ss, &theme, 36, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!(rendered.len() >= 6); @@ -19,7 +19,7 @@ fn narrow_tables_fit_render_width_and_wrap_cells() { fn table_inline_code_has_code_style() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| `code` |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let app_theme = app_theme(); let theme_colors = &app_theme.markdown; @@ -38,7 +38,7 @@ fn table_inline_code_has_code_style() { fn table_inline_code_has_padding() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| `x` |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let app_theme = app_theme(); let theme_colors = &app_theme.markdown; @@ -57,7 +57,7 @@ fn table_inline_code_has_padding() { fn table_inline_math_renders_with_latex_style() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| $\\alpha$ |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let app_theme = app_theme(); let theme_colors = &app_theme.markdown; @@ -76,7 +76,7 @@ fn table_inline_math_renders_with_latex_style() { fn table_mixed_text_and_code_renders_both_styles() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| hello `world` bye |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let app_theme = app_theme(); let theme_colors = &app_theme.markdown; @@ -99,7 +99,7 @@ fn table_mixed_text_and_code_renders_both_styles() { fn table_without_inline_styles_renders_normally() { let (ss, theme) = test_assets(); let md = "| A | B |\n|---|---|\n| one | two |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); assert!(rendered.iter().any(|line| line.contains("one"))); @@ -110,7 +110,7 @@ fn table_without_inline_styles_renders_normally() { fn table_inline_code_col_width_includes_padding() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| `longcode` |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let top_border = rendered.iter().find(|l| l.contains('┌')).unwrap(); @@ -127,7 +127,7 @@ fn table_inline_code_col_width_includes_padding() { fn table_code_adjacent_text_no_extra_space() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| `code`:text |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let cell_line = rendered .iter() @@ -143,7 +143,7 @@ fn table_code_adjacent_text_no_extra_space() { fn table_bold_adjacent_text_no_extra_space() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| **bold**:text |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let cell_line = rendered .iter() @@ -159,7 +159,7 @@ fn table_bold_adjacent_text_no_extra_space() { fn table_apostrophe_no_split() { let (ss, theme) = test_assets(); let md = "| A |\n|---|\n| apos'trophe |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let cell_line = rendered .iter() @@ -178,7 +178,7 @@ fn table_long_inline_code_wraps_without_clipping() { let md = format!("| Description |\n|---|\n| `{code}` |\n"); let width = 24; let (lines, _, _, _) = - parse_markdown_with_width(&md, &ss, &theme, width, &test_md_theme(), false).into(); + parse_markdown_with_width(&md, &ss, &theme, width, &test_md_theme(), false, true).into(); let rendered = rendered_non_empty_lines(&lines); let theme_colors = &app_theme().markdown; diff --git a/src/tests/render.rs b/src/tests/render.rs index d501fb6..f4bf033 100644 --- a/src/tests/render.rs +++ b/src/tests/render.rs @@ -8,7 +8,7 @@ use ratatui::style::Style; fn code_block_box_renders_right_border_in_one_column() { let (ss, theme) = test_assets(); let md = "```ts\nconst city = \"東京\";\n\tconsole.log(city)\n```"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let buffer = render_buffer(&lines); let (right_x, start_y) = find_symbol(&buffer, "┐").unwrap(); @@ -28,8 +28,16 @@ fn file_mode_code_block_fills_full_render_width() { let (ss, theme) = test_assets(); let render_width = 40; let src = App::fence_wrap("fn main() {\n let city = \"東京\";\n}", "rs"); - let (lines, _, _, _) = - parse_markdown_with_width(&src, &ss, &theme, render_width, &test_md_theme(), true).into(); + let (lines, _, _, _) = parse_markdown_with_width( + &src, + &ss, + &theme, + render_width, + &test_md_theme(), + true, + true, + ) + .into(); let buffer = render_buffer(&lines); assert!(find_symbol(&buffer, "┐").is_some()); @@ -47,7 +55,7 @@ fn file_mode_code_block_fills_full_render_width() { fn table_render_right_border_stays_aligned() { let (ss, theme) = test_assets(); let md = "| Name | Value |\n| --- | --- |\n| 東京 | 12 |\n| tab\tcell | ok |"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let buffer = render_buffer(&lines); let (right_x, start_y) = find_symbol(&buffer, "┐").unwrap(); @@ -66,7 +74,7 @@ fn table_render_right_border_stays_aligned() { fn table_render_right_border_stays_aligned_with_emoji_cells() { let (ss, theme) = test_assets(); let md = "| Critère | Note |\n| --- | --- |\n| Tests | ✅ Bonne couverture |\n| Sécurité | ⚠ Quelques points |\n"; - let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(md, &ss, &theme, &test_md_theme(), false, true).into(); let buffer = render_buffer(&lines); let (right_x, start_y) = find_symbol(&buffer, "┐").unwrap(); diff --git a/src/tests/theme.rs b/src/tests/theme.rs index 89aca17..33b123d 100644 --- a/src/tests/theme.rs +++ b/src/tests/theme.rs @@ -126,7 +126,7 @@ fn theme_picker_restores_original_preset_on_escape() { let (ss, theme) = test_assets(); let ts = ThemeSet::load_defaults(); let (lines, toc, _, _) = - parse_markdown("# Demo\n", &ss, &theme, &test_md_theme(), false).into(); + parse_markdown("# Demo\n", &ss, &theme, &test_md_theme(), false, true).into(); let mut app = App::new_with_source( lines, toc, @@ -167,6 +167,7 @@ fn theme_picker_caches_previewed_themes_for_reuse() { &theme, &test_md_theme(), false, + true, ) .into(); let mut app = App::new_with_source( @@ -218,7 +219,7 @@ heading_1 = "#010203" let ts = ThemeSet::load_defaults(); let theme = current_syntect_theme(&ts).clone(); let (lines, toc, _, _) = - parse_markdown("# Demo\n", &ss, &theme, &test_md_theme(), false).into(); + parse_markdown("# Demo\n", &ss, &theme, &test_md_theme(), false, true).into(); let mut app = App::new_with_source( lines, toc, diff --git a/src/tests/toc.rs b/src/tests/toc.rs index 6c0a3a0..9d21602 100644 --- a/src/tests/toc.rs +++ b/src/tests/toc.rs @@ -22,7 +22,7 @@ fn make_app_with_toc(total_lines: usize, viewport_height: u16, toc: Vec>() .join("\n"); - let (lines, _, _, _) = parse_markdown(&md, &ss, &theme, &test_md_theme(), false).into(); + let (lines, _, _, _) = parse_markdown(&md, &ss, &theme, &test_md_theme(), false, true).into(); let mut app = App::new(lines, toc, "test".to_string(), false, false, None, None); app.content_area = Rect::new(0, 0, 80, viewport_height); app @@ -77,6 +77,7 @@ fn toc_only_includes_first_two_heading_levels() { &theme, &test_md_theme(), false, + true, ) .into(); @@ -90,7 +91,7 @@ fn toc_only_includes_first_two_heading_levels() { fn frontmatter_is_ignored_in_toc() { let (ss, theme) = test_assets(); let src = "---\ntitle: Demo\nowner: me\n---\n# Visible\nBody\n"; - let (_, toc, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false).into(); + let (_, toc, _, _) = parse_markdown(src, &ss, &theme, &test_md_theme(), false, true).into(); assert_eq!(toc.len(), 1); assert_eq!(toc[0].title, "Visible");