feat: impl md ordered list
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<gpui::SharedString>,
|
||||
text: gpui::SharedString,
|
||||
highlights: Vec<(Range<usize>, gpui::HighlightStyle)>,
|
||||
links: Vec<(Range<usize>, gpui::SharedString)>,
|
||||
@@ -147,6 +146,9 @@ impl MarkdownText {
|
||||
fn on_open_link(&self, _link: &str, _cx: &gpui::Context<Self>) {}
|
||||
|
||||
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();
|
||||
cursor.goto_first_child();
|
||||
|
||||
@@ -280,7 +282,7 @@ impl MarkdownText {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut list_index = 0;
|
||||
let mut list_index: Option<usize> = 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::<usize>()
|
||||
.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),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user