diff --git a/src/interpreter/html.rs b/src/interpreter/html.rs index 8861527f..b64e080b 100644 --- a/src/interpreter/html.rs +++ b/src/interpreter/html.rs @@ -53,7 +53,6 @@ pub struct TextOptions { pub enum Element { List(List), - ListItem, Input, Table(Table), TableRow(Vec), diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 477f96f3..83a74f19 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -49,6 +49,7 @@ struct State { span_color: [f32; 4], // Stores the row and a counter of newlines after each image inline_images: Option<(Row, usize)>, + pending_list_prefix: Option, } // Images are loaded in a separate thread and use a callback to indicate when they're finished @@ -415,7 +416,31 @@ impl TokenSink for HtmlInterpreter { "bold" | "strong" => self.state.text_options.bold += 1, "code" => self.state.text_options.code += 1, "li" => { - self.state.element_stack.push(html::Element::ListItem); + // Push a pending list prefix based on the list type + let mut list = None; + for element in self.state.element_stack.iter_mut().rev() { + if let html::Element::List(html_list) = element { + list = Some(html_list); + break; + } + } + let list = list.expect("List ended unexpectedly"); + if self.current_textbox.texts.is_empty() { + if let html::List { + list_type: html::ListType::Ordered(index), + .. + } = list + { + self.state.pending_list_prefix = Some(format!("{}. ", index)); + *index += 1; + } else if let html::List { + list_type: html::ListType::Unordered, + .. + } = list + { + self.state.pending_list_prefix = Some("· ".to_owned()); + } + } } "ul" => { self.push_current_textbox(); @@ -523,6 +548,9 @@ impl TokenSink for HtmlInterpreter { if &name.local == "type" { let value = value.to_string(); if value == "checkbox" { + // Checkbox uses a custom prefix, so remove pending text + // prefix + let _ = self.state.pending_list_prefix.take(); self.push_current_textbox(); self.current_textbox.set_checkbox(Some( tag.attrs @@ -631,7 +659,6 @@ impl TokenSink for HtmlInterpreter { } "li" => { self.push_current_textbox(); - self.state.element_stack.pop(); } "input" => { self.push_current_textbox(); @@ -742,45 +769,16 @@ impl TokenSink for HtmlInterpreter { self.hidpi_scale, native_color(self.theme.text_color, &self.surface_format), ); - if let Some(html::Element::ListItem) = self.state.element_stack.last() { - let mut list = None; - for element in self.state.element_stack.iter_mut().rev() { - if let html::Element::List(html_list) = element { - list = Some(html_list); - break; - } - } - let list = list.expect("List ended unexpectedly"); - + if let Some(prefix) = self.state.pending_list_prefix.take() { if self.current_textbox.texts.is_empty() { - if let html::List { - list_type: html::ListType::Ordered(index), - .. - } = list - { - self.current_textbox.texts.push( - Text::new( - format!("{}. ", index), - self.hidpi_scale, - native_color(self.theme.text_color, &self.surface_format), - ) - .make_bold(true), - ); - *index += 1; - } else if let html::List { - list_type: html::ListType::Unordered, - .. - } = list - { - self.current_textbox.texts.push( - Text::new( - "· ".to_string(), - self.hidpi_scale, - native_color(self.theme.text_color, &self.surface_format), - ) - .make_bold(true), + self.current_textbox.texts.push( + Text::new( + prefix, + self.hidpi_scale, + native_color(self.theme.text_color, &self.surface_format), ) - } + .make_bold(true), + ); } } if self.state.text_options.block_quote >= 1 { diff --git a/src/interpreter/snapshots/inlyne__interpreter__tests__code_in_ordered_list.snap b/src/interpreter/snapshots/inlyne__interpreter__tests__code_in_ordered_list.snap new file mode 100644 index 00000000..2c881922 --- /dev/null +++ b/src/interpreter/snapshots/inlyne__interpreter__tests__code_in_ordered_list.snap @@ -0,0 +1,84 @@ +--- +source: src/interpreter/tests.rs +expression: interpret_md(text) +--- +[ + TextBox( + TextBox { + indent: 50.0, + texts: [ + Text { + text: "1. ", + default_color: Color(BLACK), + style: BOLD , + .. + }, + Text { + text: "1st item", + default_color: Color(BLACK), + .. + }, + ], + .. + }, + ), + Spacer( + InvisibleSpacer(5), + ), + TextBox( + TextBox { + indent: 50.0, + background_color: Some(Color { r: 0.86, g: 0.88, b: 0.91 }), + is_code_block: true, + texts: [ + Text { + text: "fn ", + size: 18.0, + color: Some(Color { r: 0.46, g: 0.27, b: 0.42 }), + .. + }, + Text { + text: "main", + size: 18.0, + color: Some(Color { r: 0.27, g: 0.36, b: 0.45 }), + .. + }, + Text { + text: "() {}", + size: 18.0, + color: Some(Color { r: 0.08, g: 0.10, b: 0.13 }), + .. + }, + ], + .. + }, + ), + Spacer( + InvisibleSpacer(5), + ), + TextBox( + TextBox { + indent: 50.0, + texts: [ + Text { + text: "2. ", + default_color: Color(BLACK), + style: BOLD , + .. + }, + Text { + text: "2nd item", + default_color: Color(BLACK), + .. + }, + ], + .. + }, + ), + Spacer( + InvisibleSpacer(5), + ), + Spacer( + InvisibleSpacer(5), + ), +] diff --git a/src/interpreter/snapshots/inlyne__interpreter__tests__para_in_ordered_list.snap b/src/interpreter/snapshots/inlyne__interpreter__tests__para_in_ordered_list.snap new file mode 100644 index 00000000..b00f8e2a --- /dev/null +++ b/src/interpreter/snapshots/inlyne__interpreter__tests__para_in_ordered_list.snap @@ -0,0 +1,70 @@ +--- +source: src/interpreter/tests.rs +description: " --- md\n\n1. 1st item\n\n Nested paragraph\n\n2. 2nd item\n\n\n --- html\n\n
    \n
  1. \n

    1st item

    \n

    Nested paragraph

    \n
  2. \n
  3. \n

    2nd item

    \n
  4. \n
\n" +expression: interpret_md(text) +--- +[ + TextBox( + TextBox { + indent: 50.0, + texts: [ + Text { + text: "1. ", + default_color: Color(BLACK), + style: BOLD , + .. + }, + Text { + text: "1st item", + default_color: Color(BLACK), + .. + }, + ], + .. + }, + ), + Spacer( + InvisibleSpacer(5), + ), + TextBox( + TextBox { + indent: 50.0, + texts: [ + Text { + text: "Nested paragraph", + default_color: Color(BLACK), + .. + }, + ], + .. + }, + ), + Spacer( + InvisibleSpacer(5), + ), + TextBox( + TextBox { + indent: 50.0, + texts: [ + Text { + text: "2. ", + default_color: Color(BLACK), + style: BOLD , + .. + }, + Text { + text: "2nd item", + default_color: Color(BLACK), + .. + }, + ], + .. + }, + ), + Spacer( + InvisibleSpacer(5), + ), + Spacer( + InvisibleSpacer(5), + ), +] diff --git a/src/interpreter/tests.rs b/src/interpreter/tests.rs index 012641cd..a1f9e1cf 100644 --- a/src/interpreter/tests.rs +++ b/src/interpreter/tests.rs @@ -159,6 +159,24 @@ const ORDERED_LIST_IN_UNORDERED: &str = "\ - bullet "; +const PARA_IN_ORDERED_LIST: &str = "\ +1. 1st item + + Nested paragraph + +2. 2nd item +"; + +const CODE_IN_ORDERED_LIST: &str = "\ +1. 1st item + + ```rust + fn main() {} + ``` + +2. 2nd item +"; + snapshot_interpreted_elements!( (sanity, SANITY), (checklist_has_no_text_prefix, CHECKLIST_HAS_NO_TEXT_PREFIX), @@ -167,6 +185,8 @@ snapshot_interpreted_elements!( (unordered_list_in_ordered, UNORDERED_LIST_IN_ORDERED), (nested_ordered_list, NESTED_ORDERED_LIST), (ordered_list_in_unordered, ORDERED_LIST_IN_UNORDERED), + (para_in_ordered_list, PARA_IN_ORDERED_LIST), + (code_in_ordered_list, CODE_IN_ORDERED_LIST), ); /// Spin up a server, so we can test network requests without external services diff --git a/src/main.rs b/src/main.rs index e17570dd..047d5eec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -536,14 +536,14 @@ impl Inlyne { } } } - } else if open::that(link).is_err() { - if let Some(anchor_pos) = - self.renderer.positioner.anchors.get(link) - { - self.renderer.set_scroll_y(*anchor_pos); - self.window.request_redraw(); - self.window.set_cursor_icon(CursorIcon::Default); - } + } else if let Some(anchor_pos) = + self.renderer.positioner.anchors.get(link) + { + self.renderer.set_scroll_y(*anchor_pos); + self.window.request_redraw(); + self.window.set_cursor_icon(CursorIcon::Default); + } else { + open::that(link).unwrap(); } } else if self.renderer.selection.is_none() { // Only set selection when not over link