feat: impl md ordered list

This commit is contained in:
2026-05-13 00:52:13 +08:00
parent ee226178b2
commit 53993fed93
2 changed files with 57 additions and 39 deletions

View File

@@ -12,6 +12,7 @@ gpui = { version = "*" }
graphql_client = { version = "0.16.0", features = ["reqwest"] } graphql_client = { version = "0.16.0", features = ["reqwest"] }
paste = "1.0" paste = "1.0"
rand = "0.10.1" rand = "0.10.1"
regex = "1.12.3"
reqwest = { version = "0.13.2", features = ["form", "json", "query"] } reqwest = { version = "0.13.2", features = ["form", "json", "query"] }
serde = "1.0.228" serde = "1.0.228"
serde_json = "1.0.149" serde_json = "1.0.149"

View File

@@ -1,9 +1,8 @@
// markdown treesitter playground: https://ikatyang.github.io/tree-sitter-markdown/ // 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 gpui::{AppContext, ParentElement, Refineable, Styled, div, px, relative, rems};
use tree_sitter::Node;
use crate::{app, theme}; use crate::{app, theme};
@@ -93,7 +92,7 @@ pub(crate) struct MarkdownText {
enum ContentBlock { enum ContentBlock {
Text { Text {
decoration: Option<&'static str>, decoration: Option<gpui::SharedString>,
text: gpui::SharedString, text: gpui::SharedString,
highlights: Vec<(Range<usize>, gpui::HighlightStyle)>, highlights: Vec<(Range<usize>, gpui::HighlightStyle)>,
links: Vec<(Range<usize>, gpui::SharedString)>, links: Vec<(Range<usize>, gpui::SharedString)>,
@@ -147,6 +146,9 @@ impl MarkdownText {
fn on_open_link(&self, _link: &str, _cx: &gpui::Context<Self>) {} fn on_open_link(&self, _link: &str, _cx: &gpui::Context<Self>) {}
fn render_tree(&mut self, tree: &tree_sitter::Tree, theme: &theme::Theme) { fn render_tree(&mut self, tree: &tree_sitter::Tree, theme: &theme::Theme) {
static ORDERED_LIST_MARKER_REGEX: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new(r"^\d+\.$").unwrap());
let mut cursor = tree.walk(); let mut cursor = tree.walk();
cursor.goto_first_child(); cursor.goto_first_child();
@@ -280,7 +282,7 @@ impl MarkdownText {
return false; return false;
} }
let mut list_index = 0; let mut list_index: Option<usize> = None;
loop { loop {
if cursor.node().kind_id() != MARKDOWN_KIND_ID_LIST_ITEM_TIGHT if cursor.node().kind_id() != MARKDOWN_KIND_ID_LIST_ITEM_TIGHT
@@ -298,46 +300,61 @@ impl MarkdownText {
let marker_node = cursor.node(); let marker_node = cursor.node();
let marker_content = &content[marker_node.byte_range()]; let marker_content = &content[marker_node.byte_range()];
match marker_content { let list_marker_char = match marker_content {
// unordered list item // unordered list item
| "-" | "+" | "*" => { | "-" | "+" | "*" => Some("".to_string()),
// 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));
blocks.push(block); | marker_content if ORDERED_LIST_MARKER_REGEX.is_match(marker_content) => {
let i = list_index.get_or_insert_with(|| {
// if there is a nested tight_light after paragraph marker_content
// render it recursively .strip_suffix('.')
if cursor.goto_next_sibling() .unwrap()
&& cursor.node().kind_id() == MARKDOWN_KIND_ID_TIGHT_LIST .parse::<usize>()
{ .unwrap()
render_list_node(cursor, content, blocks, theme, indentation + 1); });
} 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); render_fallback_content(&cursor, content, blocks);
return false; 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 // go back to list_item node
@@ -549,7 +566,7 @@ impl gpui::Render for MarkdownText {
.flex_row() .flex_row()
.gap_2() .gap_2()
.items_start() .items_start()
.child(*d) .child(d.clone())
.child(div().flex_1().min_w_0().child(content)), .child(div().flex_1().min_w_0().child(content)),
| None => div().w_full().child(content), | None => div().w_full().child(content),
}; };