diff --git a/Cargo.toml b/Cargo.toml index 8803d00..55c7f35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ gpui = { version = "*" } graphql_client = { version = "0.16.0", features = ["reqwest"] } paste = "1.0" rand = "0.10.1" +regex = "1.12.3" reqwest = { version = "0.13.2", features = ["form", "json", "query"] } serde = "1.0.228" serde_json = "1.0.149" diff --git a/src/component/markdown.rs b/src/component/markdown.rs index 7602e10..2d20f00 100644 --- a/src/component/markdown.rs +++ b/src/component/markdown.rs @@ -1,9 +1,8 @@ // markdown treesitter playground: https://ikatyang.github.io/tree-sitter-markdown/ -use std::ops::Range; +use std::{ops::Range, sync::LazyLock}; -use gpui::{AppContext, ParentElement, Refineable, RenderOnce, Styled, div, px, relative, rems}; -use tree_sitter::Node; +use gpui::{AppContext, ParentElement, Refineable, Styled, div, px, relative, rems}; use crate::{app, theme}; @@ -93,7 +92,7 @@ pub(crate) struct MarkdownText { enum ContentBlock { Text { - decoration: Option<&'static str>, + decoration: Option, text: gpui::SharedString, highlights: Vec<(Range, gpui::HighlightStyle)>, links: Vec<(Range, gpui::SharedString)>, @@ -147,6 +146,9 @@ impl MarkdownText { fn on_open_link(&self, _link: &str, _cx: &gpui::Context) {} fn render_tree(&mut self, tree: &tree_sitter::Tree, theme: &theme::Theme) { + static ORDERED_LIST_MARKER_REGEX: LazyLock = + LazyLock::new(|| regex::Regex::new(r"^\d+\.$").unwrap()); + let mut cursor = tree.walk(); cursor.goto_first_child(); @@ -280,7 +282,7 @@ impl MarkdownText { return false; } - let mut list_index = 0; + let mut list_index: Option = None; loop { if cursor.node().kind_id() != MARKDOWN_KIND_ID_LIST_ITEM_TIGHT @@ -298,46 +300,61 @@ impl MarkdownText { let marker_node = cursor.node(); let marker_content = &content[marker_node.byte_range()]; - match marker_content { + let list_marker_char = match marker_content { // unordered list item - | "-" | "+" | "*" => { - // go to paragraph sibling node - let block = if cursor.goto_next_sibling() { - let mut b = block_for_node(cursor, content, 0, theme); - match b { - | ContentBlock::Text { - ref mut decoration, .. - } => *decoration = Some("•"), - } - b - } else { - ContentBlock::Text { - decoration: Some("•"), - text: gpui::SharedString::default(), - highlights: Vec::new(), - links: Vec::new(), - style: gpui::StyleRefinement::default(), - } - } - .text_sm() - .text_color(theme.colors.text) - .p(rems(indentation as f32)); + | "-" | "+" | "*" => Some("•".to_string()), - blocks.push(block); - - // if there is a nested tight_light after paragraph - // render it recursively - if cursor.goto_next_sibling() - && cursor.node().kind_id() == MARKDOWN_KIND_ID_TIGHT_LIST - { - render_list_node(cursor, content, blocks, theme, indentation + 1); - } + | marker_content if ORDERED_LIST_MARKER_REGEX.is_match(marker_content) => { + let i = list_index.get_or_insert_with(|| { + marker_content + .strip_suffix('.') + .unwrap() + .parse::() + .unwrap() + }); + let j = *i; + *i = j + 1; + Some(format!("{j}.")) } - | _ => { + | _ => None, + }; + + let Some(list_marker_char) = list_marker_char else { render_fallback_content(&cursor, content, blocks); return false; + }; + + // go to paragraph sibling node + let block = if cursor.goto_next_sibling() { + let mut b = block_for_node(cursor, content, 0, theme); + match b { + | ContentBlock::Text { + ref mut decoration, .. + } => *decoration = Some(list_marker_char.into()), + } + b + } else { + ContentBlock::Text { + decoration: Some(list_marker_char.into()), + text: gpui::SharedString::default(), + highlights: Vec::new(), + links: Vec::new(), + style: gpui::StyleRefinement::default(), + } } + .text_sm() + .text_color(theme.colors.text) + .p(rems(indentation as f32)); + + blocks.push(block); + + // if there is a nested tight_light after paragraph + // render it recursively + if cursor.goto_next_sibling() + && cursor.node().kind_id() == MARKDOWN_KIND_ID_TIGHT_LIST + { + render_list_node(cursor, content, blocks, theme, indentation + 1); } // go back to list_item node @@ -549,7 +566,7 @@ impl gpui::Render for MarkdownText { .flex_row() .gap_2() .items_start() - .child(*d) + .child(d.clone()) .child(div().flex_1().min_w_0().child(content)), | None => div().w_full().child(content), };