feat: impl md ordered list
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user