From e924359df63692fa0df8fa63169512f58234600e Mon Sep 17 00:00:00 2001 From: PoiScript Date: Fri, 17 Nov 2023 13:34:06 +0800 Subject: [PATCH] feat: simplify public api --- examples/html-slugify.rs | 6 +- src/ast/generate.js | 74 +----- src/ast/generated.rs | 552 ++++++++++++++++++++++++++------------- src/ast/headline.rs | 126 +++++---- src/ast/list.rs | 52 +++- src/ast/mod.rs | 1 + src/ast/planning.rs | 67 +++++ src/export/forward.rs | 6 +- src/export/html.rs | 13 +- src/syntax/headline.rs | 45 ++-- 10 files changed, 586 insertions(+), 356 deletions(-) create mode 100644 src/ast/planning.rs diff --git a/examples/html-slugify.rs b/examples/html-slugify.rs index 6f62d4d..672ac09 100644 --- a/examples/html-slugify.rs +++ b/examples/html-slugify.rs @@ -27,8 +27,7 @@ impl Traverser for MyHtmlHandler { fn headline_title(&mut self, event: WalkEvent<&HeadlineTitle>, _ctx: &mut TraversalContext) { match event { WalkEvent::Enter(title) => { - let level = title.headline().and_then(|h| h.level()).unwrap_or(1); - let level = min(level, 6); + let level = title.headline().map(|h| min(h.level(), 6)).unwrap_or(1); let raw = title.syntax().to_string(); self.0.push_str(format!( "", @@ -36,8 +35,7 @@ impl Traverser for MyHtmlHandler { )); } WalkEvent::Leave(title) => { - let level = title.headline().and_then(|h| h.level()).unwrap_or(1); - let level = min(level, 6); + let level = title.headline().map(|h| min(h.level(), 6)).unwrap_or(1); self.0.push_str(format!("")); } } diff --git a/src/ast/generate.js b/src/ast/generate.js index 12145b2..8aca5bd 100644 --- a/src/ast/generate.js +++ b/src/ast/generate.js @@ -27,43 +27,17 @@ const nodes = [ first_child: [ ["title", "HeadlineTitle"], ["section", "Section"], - ["tags", "HeadlineTags"], ["planning", "Planning"], - ["priority", "HeadlinePriority"], ], children: [["headlines", "Headline"]], - token: [ - ["stars", "HEADLINE_STARS"], - ["keyword", "HEADLINE_KEYWORD"], - ], + token: [["keyword", "HEADLINE_KEYWORD"]], post_blank: true, }, - { - struct: "HeadlineStars", - kind: ["HEADLINE_STARS"], - parent: [["headline", "Headline"]], - }, { struct: "HeadlineTitle", kind: ["HEADLINE_TITLE"], parent: [["headline", "Headline"]], }, - { - struct: "HeadlineKeyword", - kind: ["HEADLINE_KEYWORD"], - parent: [["headline", "Headline"]], - }, - { - struct: "HeadlinePriority", - kind: ["HEADLINE_PRIORITY"], - parent: [["headline", "Headline"]], - token: [["text", "TEXT"]], - }, - { - struct: "HeadlineTags", - kind: ["HEADLINE_TAGS"], - parent: [["headline", "Headline"]], - }, { struct: "PropertyDrawer", kind: ["PROPERTY_DRAWER"], @@ -76,23 +50,6 @@ const nodes = [ { struct: "Planning", kind: ["PLANNING"], - last_child: [ - ["deadline", "PlanningDeadline"], - ["scheduled", "PlanningScheduled"], - ["closed", "PlanningClosed"], - ], - }, - { - struct: "PlanningDeadline", - kind: ["PLANNING_DEADLINE"], - }, - { - struct: "PlanningScheduled", - kind: ["PLANNING_SCHEDULED"], - }, - { - struct: "PlanningClosed", - kind: ["PLANNING_CLOSED"], }, { struct: "OrgTable", @@ -118,26 +75,6 @@ const nodes = [ struct: "ListItem", kind: ["LIST_ITEM"], first_child: [["content", "ListItemContent"]], - token: [ - ["indent", "LIST_ITEM_INDENT"], - ["bullet", "LIST_ITEM_BULLET"], - ], - }, - { - struct: "ListItemIndent", - kind: ["LIST_ITEM_INDENT"], - }, - { - struct: "ListItemTag", - kind: ["LIST_ITEM_TAG"], - }, - { - struct: "ListItemBullet", - kind: ["LIST_ITEM_BULLET"], - }, - { - struct: "ListItemContent", - kind: ["LIST_ITEM_CONTENT"], }, { struct: "Drawer", @@ -357,7 +294,14 @@ impl AstNode for ${node.struct} { }> { Self::can_cast(node.kind()).then(|| ${node.struct} { syntax: node }) } fn syntax(&self) -> &SyntaxNode { &self.syntax } } -impl ${node.struct} {\n`; +impl ${node.struct} { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +`; for (const [method, kind] of node.token || []) { content += ` pub fn ${method}(&self) -> Option { support::token(&self.syntax, ${kind}) }\n`; } diff --git a/src/ast/generated.rs b/src/ast/generated.rs index d52c64a..90c2554 100644 --- a/src/ast/generated.rs +++ b/src/ast/generated.rs @@ -32,6 +32,12 @@ impl AstNode for Document { } } impl Document { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn section(&self) -> Option
{ support::child(&self.syntax) } @@ -66,6 +72,12 @@ impl AstNode for Section { } } impl Section { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -88,6 +100,12 @@ impl AstNode for Paragraph { } } impl Paragraph { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -130,8 +148,11 @@ impl AstNode for Headline { } } impl Headline { - pub fn stars(&self) -> Option { - support::token(&self.syntax, HEADLINE_STARS) + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() } pub fn keyword(&self) -> Option { support::token(&self.syntax, HEADLINE_KEYWORD) @@ -142,15 +163,9 @@ impl Headline { pub fn section(&self) -> Option
{ support::child(&self.syntax) } - pub fn tags(&self) -> Option { - support::child(&self.syntax) - } pub fn planning(&self) -> Option { support::child(&self.syntax) } - pub fn priority(&self) -> Option { - support::child(&self.syntax) - } pub fn headlines(&self) -> AstChildren { support::children(&self.syntax) } @@ -159,28 +174,6 @@ impl Headline { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct HeadlineStars { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for HeadlineStars { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == HEADLINE_STARS - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| HeadlineStars { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl HeadlineStars { - pub fn headline(&self) -> Option { - self.syntax.parent().and_then(Headline::cast) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct HeadlineTitle { pub(crate) syntax: SyntaxNode, @@ -198,75 +191,12 @@ impl AstNode for HeadlineTitle { } } impl HeadlineTitle { - pub fn headline(&self) -> Option { - self.syntax.parent().and_then(Headline::cast) + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct HeadlineKeyword { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for HeadlineKeyword { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == HEADLINE_KEYWORD + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| HeadlineKeyword { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl HeadlineKeyword { - pub fn headline(&self) -> Option { - self.syntax.parent().and_then(Headline::cast) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct HeadlinePriority { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for HeadlinePriority { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == HEADLINE_PRIORITY - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| HeadlinePriority { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl HeadlinePriority { - pub fn text(&self) -> Option { - support::token(&self.syntax, TEXT) - } - pub fn headline(&self) -> Option { - self.syntax.parent().and_then(Headline::cast) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct HeadlineTags { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for HeadlineTags { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == HEADLINE_TAGS - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| HeadlineTags { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl HeadlineTags { pub fn headline(&self) -> Option { self.syntax.parent().and_then(Headline::cast) } @@ -289,6 +219,12 @@ impl AstNode for PropertyDrawer { } } impl PropertyDrawer { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn node_properties(&self) -> AstChildren { support::children(&self.syntax) } @@ -310,7 +246,14 @@ impl AstNode for NodeProperty { &self.syntax } } -impl NodeProperty {} +impl NodeProperty { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Planning { @@ -329,71 +272,14 @@ impl AstNode for Planning { } } impl Planning { - pub fn deadline(&self) -> Option { - super::last_child(&self.syntax) + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() } - pub fn scheduled(&self) -> Option { - super::last_child(&self.syntax) - } - pub fn closed(&self) -> Option { - super::last_child(&self.syntax) + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PlanningDeadline { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for PlanningDeadline { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == PLANNING_DEADLINE - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| PlanningDeadline { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl PlanningDeadline {} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PlanningScheduled { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for PlanningScheduled { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == PLANNING_SCHEDULED - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| PlanningScheduled { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl PlanningScheduled {} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PlanningClosed { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for PlanningClosed { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == PLANNING_CLOSED - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| PlanningClosed { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl PlanningClosed {} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct OrgTable { pub(crate) syntax: SyntaxNode, @@ -411,6 +297,12 @@ impl AstNode for OrgTable { } } impl OrgTable { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -452,7 +344,14 @@ impl AstNode for OrgTableRow { &self.syntax } } -impl OrgTableRow {} +impl OrgTableRow { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct OrgTableCell { @@ -470,7 +369,14 @@ impl AstNode for OrgTableCell { &self.syntax } } -impl OrgTableCell {} +impl OrgTableCell { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct List { @@ -489,6 +395,12 @@ impl AstNode for List { } } impl List { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn items(&self) -> AstChildren { support::children(&self.syntax) } @@ -531,6 +443,12 @@ impl AstNode for ListItem { } } impl ListItem { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn indent(&self) -> Option { support::token(&self.syntax, LIST_ITEM_INDENT) } @@ -558,7 +476,14 @@ impl AstNode for ListItemIndent { &self.syntax } } -impl ListItemIndent {} +impl ListItemIndent { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ListItemTag { @@ -576,7 +501,14 @@ impl AstNode for ListItemTag { &self.syntax } } -impl ListItemTag {} +impl ListItemTag { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ListItemBullet { @@ -594,7 +526,14 @@ impl AstNode for ListItemBullet { &self.syntax } } -impl ListItemBullet {} +impl ListItemBullet { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ListItemContent { @@ -612,7 +551,14 @@ impl AstNode for ListItemContent { &self.syntax } } -impl ListItemContent {} +impl ListItemContent { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Drawer { @@ -630,7 +576,14 @@ impl AstNode for Drawer { &self.syntax } } -impl Drawer {} +impl Drawer { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DynBlock { @@ -649,6 +602,12 @@ impl AstNode for DynBlock { } } impl DynBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -687,7 +646,14 @@ impl AstNode for Keyword { &self.syntax } } -impl Keyword {} +impl Keyword { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct BabelCall { @@ -705,7 +671,14 @@ impl AstNode for BabelCall { &self.syntax } } -impl BabelCall {} +impl BabelCall { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AffiliatedKeyword { @@ -723,7 +696,14 @@ impl AstNode for AffiliatedKeyword { &self.syntax } } -impl AffiliatedKeyword {} +impl AffiliatedKeyword { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TableEl { @@ -742,6 +722,12 @@ impl AstNode for TableEl { } } impl TableEl { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -764,6 +750,12 @@ impl AstNode for Clock { } } impl Clock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -786,6 +778,12 @@ impl AstNode for FnDef { } } impl FnDef { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -828,6 +826,12 @@ impl AstNode for Comment { } } impl Comment { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn text(&self) -> Option { support::token(&self.syntax, TEXT) } @@ -873,6 +877,12 @@ impl AstNode for Rule { } } impl Rule { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } @@ -895,6 +905,12 @@ impl AstNode for FixedWidth { } } impl FixedWidth { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn text(&self) -> Option { support::token(&self.syntax, TEXT) } @@ -940,6 +956,12 @@ impl AstNode for SpecialBlock { } } impl SpecialBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -979,6 +1001,12 @@ impl AstNode for QuoteBlock { } } impl QuoteBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1018,6 +1046,12 @@ impl AstNode for CenterBlock { } } impl CenterBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1057,6 +1091,12 @@ impl AstNode for VerseBlock { } } impl VerseBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1096,6 +1136,12 @@ impl AstNode for CommentBlock { } } impl CommentBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1135,6 +1181,12 @@ impl AstNode for ExampleBlock { } } impl ExampleBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1174,6 +1226,12 @@ impl AstNode for ExportBlock { } } impl ExportBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1213,6 +1271,12 @@ impl AstNode for SourceBlock { } } impl SourceBlock { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") } @@ -1251,7 +1315,14 @@ impl AstNode for InlineCall { &self.syntax } } -impl InlineCall {} +impl InlineCall { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InlineSrc { @@ -1269,7 +1340,14 @@ impl AstNode for InlineSrc { &self.syntax } } -impl InlineSrc {} +impl InlineSrc { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Link { @@ -1288,6 +1366,12 @@ impl AstNode for Link { } } impl Link { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn path(&self) -> Option { support::token(&self.syntax, LINK_PATH) } @@ -1309,7 +1393,14 @@ impl AstNode for Cookie { &self.syntax } } -impl Cookie {} +impl Cookie { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RadioTarget { @@ -1327,7 +1418,14 @@ impl AstNode for RadioTarget { &self.syntax } } -impl RadioTarget {} +impl RadioTarget { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FnRef { @@ -1345,7 +1443,14 @@ impl AstNode for FnRef { &self.syntax } } -impl FnRef {} +impl FnRef { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct LatexEnvironment { @@ -1363,7 +1468,14 @@ impl AstNode for LatexEnvironment { &self.syntax } } -impl LatexEnvironment {} +impl LatexEnvironment { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Macros { @@ -1381,7 +1493,14 @@ impl AstNode for Macros { &self.syntax } } -impl Macros {} +impl Macros { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacrosArgument { @@ -1399,7 +1518,14 @@ impl AstNode for MacrosArgument { &self.syntax } } -impl MacrosArgument {} +impl MacrosArgument { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Snippet { @@ -1418,6 +1544,12 @@ impl AstNode for Snippet { } } impl Snippet { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn name(&self) -> Option { support::token(&self.syntax, TEXT) } @@ -1439,7 +1571,14 @@ impl AstNode for Target { &self.syntax } } -impl Target {} +impl Target { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Bold { @@ -1457,7 +1596,14 @@ impl AstNode for Bold { &self.syntax } } -impl Bold {} +impl Bold { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Strike { @@ -1475,7 +1621,14 @@ impl AstNode for Strike { &self.syntax } } -impl Strike {} +impl Strike { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Italic { @@ -1493,7 +1646,14 @@ impl AstNode for Italic { &self.syntax } } -impl Italic {} +impl Italic { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Underline { @@ -1511,7 +1671,14 @@ impl AstNode for Underline { &self.syntax } } -impl Underline {} +impl Underline { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Verbatim { @@ -1529,7 +1696,14 @@ impl AstNode for Verbatim { &self.syntax } } -impl Verbatim {} +impl Verbatim { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Code { @@ -1548,6 +1722,12 @@ impl AstNode for Code { } } impl Code { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn text(&self) -> Option { support::token(&self.syntax, TEXT) } @@ -1570,6 +1750,12 @@ impl AstNode for Timestamp { } } impl Timestamp { + pub fn begin(&self) -> u32 { + self.syntax.text_range().start().into() + } + pub fn end(&self) -> u32 { + self.syntax.text_range().end().into() + } pub fn year_start(&self) -> Option { support::token(&self.syntax, TIMESTAMP_YEAR) } diff --git a/src/ast/headline.rs b/src/ast/headline.rs index f4d4dfe..54cd52d 100644 --- a/src/ast/headline.rs +++ b/src/ast/headline.rs @@ -1,8 +1,6 @@ -use rowan::ast::support; +use crate::syntax::{SyntaxKind, SyntaxToken}; -use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxToken}; - -use super::{filter_token, Headline, HeadlinePriority, HeadlineTags, Timestamp}; +use super::{filter_token, Headline, Timestamp}; impl Headline { /// Return level of this headline @@ -11,12 +9,19 @@ impl Headline { /// use orgize::{Org, ast::Headline}; /// /// let hdl = Org::parse("* ").first_node::().unwrap(); - /// assert_eq!(hdl.level(), Some(1)); + /// assert_eq!(hdl.level(), 1); /// let hdl = Org::parse("****** hello").first_node::().unwrap(); - /// assert_eq!(hdl.level(), Some(6)); + /// assert_eq!(hdl.level(), 6); /// ``` - pub fn level(&self) -> Option { - self.stars().map(|stars| stars.text().len()) + pub fn level(&self) -> usize { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::HEADLINE_STARS)) + .map(|stars| stars.text().len()) + .unwrap_or_else(|| { + debug_assert!(false, "headline must contains starts token"); + 0 + }) } /// Return `true` if this headline contains a COMMENT keyword @@ -54,34 +59,67 @@ impl Headline { /// assert!(!hdl.is_archived()); /// ``` pub fn is_archived(&self) -> bool { - self.tags() - .map(|tags| { - tags.syntax - .children_with_tokens() - .any(|elem| matches!(elem, SyntaxElement::Token(t) if t.text() == "ARCHIVE")) - }) - .unwrap_or_default() + self.tags().any(|t| t.text() == "ARCHIVE") } /// Returns this headline's closed timestamp, or `None` if not set. pub fn closed(&self) -> Option { - self.planning() - .and_then(|planning| planning.closed()) - .and_then(|node| support::child::(&node.syntax)) + self.planning().and_then(|planning| planning.closed()) } /// Returns this headline's scheduled timestamp, or `None` if not set. pub fn scheduled(&self) -> Option { - self.planning() - .and_then(|planning| planning.scheduled()) - .and_then(|node| support::child::(&node.syntax)) + self.planning().and_then(|planning| planning.scheduled()) } /// Returns this headline's deadline timestamp, or `None` if not set. pub fn deadline(&self) -> Option { - self.planning() - .and_then(|planning| planning.deadline()) - .and_then(|node| support::child::(&node.syntax)) + self.planning().and_then(|planning| planning.deadline()) + } + + /// Returns an iterator of text token in this tags + /// + /// ```rust + /// use orgize::{Org, ast::Headline}; + /// + /// let tags_vec = |input: &str| { + /// let hdl = Org::parse(input).first_node::().unwrap(); + /// let tags: Vec<_> = hdl.tags().map(|t| t.to_string()).collect(); + /// tags + /// }; + /// + /// assert_eq!(tags_vec("* :tag:"), vec!["tag".to_string()]); + /// assert_eq!(tags_vec("* [#A] :::::a2%:"), vec!["a2%".to_string()]); + /// assert_eq!(tags_vec("* TODO :tag: :a2%:"), vec!["tag".to_string(), "a2%".to_string()]); + /// assert_eq!(tags_vec("* title :tag:a2%:"), vec!["tag".to_string(), "a2%".to_string()]); + /// ``` + pub fn tags(&self) -> impl Iterator { + self.syntax + .children() + .find(|n| n.kind() == SyntaxKind::HEADLINE_TAGS) + .into_iter() + .flat_map(|t| t.children_with_tokens()) + .filter_map(filter_token(SyntaxKind::TEXT)) + } + + /// Returns priority text + /// + /// ```rust + /// use orgize::{Org, ast::Headline}; + /// + /// let hdl = Org::parse("* [#A]").first_node::().unwrap(); + /// assert_eq!(hdl.priority().unwrap().text(), "A"); + /// let hdl = Org::parse("* [#破]").first_node::().unwrap(); + /// assert_eq!(hdl.priority().unwrap().text(), "破"); + /// ``` + pub fn priority(&self) -> Option { + self.syntax + .children() + .find(|n| n.kind() == SyntaxKind::HEADLINE_PRIORITY) + .and_then(|n| { + n.children_with_tokens() + .find_map(filter_token(SyntaxKind::TEXT)) + }) } } @@ -316,43 +354,3 @@ impl Headline { // } // } // } - -impl HeadlineTags { - /// Returns an iterator of text token in this tags - /// - /// ```rust - /// use orgize::{Org, ast::HeadlineTags}; - /// - /// let tags_vec = |input: &str| { - /// let tags = Org::parse(input).first_node::().unwrap(); - /// let tags: Vec<_> = tags.iter().map(|t| t.to_string()).collect(); - /// tags - /// }; - /// - /// assert_eq!(tags_vec("* :tag:"), vec!["tag".to_string()]); - /// assert_eq!(tags_vec("* [#A] :::::a2%:"), vec!["a2%".to_string()]); - /// assert_eq!(tags_vec("* TODO :tag: :a2%:"), vec!["tag".to_string(), "a2%".to_string()]); - /// assert_eq!(tags_vec("* title :tag:a2%:"), vec!["tag".to_string(), "a2%".to_string()]); - /// ``` - pub fn iter(&self) -> impl Iterator { - self.syntax - .children_with_tokens() - .filter_map(filter_token(SyntaxKind::TEXT)) - } -} - -impl HeadlinePriority { - /// Returns priority text - /// - /// ```rust - /// use orgize::{Org, ast::HeadlinePriority}; - /// - /// let priority = Org::parse("* [#A]").first_node::().unwrap(); - /// assert_eq!(priority.text_string().unwrap(), "A".to_string()); - /// let priority = Org::parse("* [#破]").first_node::().unwrap(); - /// assert_eq!(priority.text_string().unwrap(), "破".to_string()); - /// ``` - pub fn text_string(&self) -> Option { - self.text().map(|tk| tk.to_string()) - } -} diff --git a/src/ast/list.rs b/src/ast/list.rs index 1ca114e..5a694c7 100644 --- a/src/ast/list.rs +++ b/src/ast/list.rs @@ -1,7 +1,55 @@ -use super::List; -use crate::syntax::SyntaxKind; +use super::{filter_token, List}; +use crate::{syntax::SyntaxKind, SyntaxElement, SyntaxToken}; impl List { + pub fn indent(&self) -> usize { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::LIST_ITEM_INDENT)) + .map(|t| t.text().len()) + .unwrap_or_else(|| { + debug_assert!(false, "list must contains indent token"); + 0 + }) + } + + pub fn bullet(&self) -> Option { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::LIST_ITEM_BULLET)) + } + + pub fn checkbox(&self) -> Option { + self.syntax + .children() + .find(|n| n.kind() == SyntaxKind::LIST_ITEM_CHECK_BOX) + .and_then(|n| { + n.children_with_tokens() + .find_map(filter_token(SyntaxKind::TEXT)) + }) + } + + pub fn counter(&self) -> Option { + self.syntax + .children() + .find(|n| n.kind() == SyntaxKind::LIST_ITEM_COUNTER) + .and_then(|n| { + n.children_with_tokens() + .find_map(filter_token(SyntaxKind::TEXT)) + }) + } + + pub fn tag(&self) -> impl Iterator { + self.syntax + .children() + .find(|n| n.kind() == SyntaxKind::LIST_ITEM_TAG) + .into_iter() + .flat_map(|n| { + n.children_with_tokens() + .filter(|n| n.kind() != SyntaxKind::COLON2) + }) + } + /// Returns `true` if this list is an ordered link /// /// ```rust diff --git a/src/ast/mod.rs b/src/ast/mod.rs index dadd2cf..1bfdf94 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -8,6 +8,7 @@ mod headline; mod inline_call; mod link; mod list; +mod planning; mod snippet; mod table; mod timestamp; diff --git a/src/ast/planning.rs b/src/ast/planning.rs new file mode 100644 index 0000000..f0b68c9 --- /dev/null +++ b/src/ast/planning.rs @@ -0,0 +1,67 @@ +use rowan::ast::AstNode; + +use super::{Planning, Timestamp}; +use crate::syntax::SyntaxKind; + +impl Planning { + /// Returns deadline timestamp + /// + /// + /// ```rust + /// use orgize::{ast::Planning, Org}; + /// + /// let s = Org::parse("* a\nDEADLINE: <2019-04-08 Mon>") + /// .first_node::() + /// .unwrap() + /// .deadline() + /// .unwrap(); + /// assert_eq!(s.day_start().unwrap().text(), "08"); + /// ``` + pub fn deadline(&self) -> Option { + self.syntax + .children() + .filter(|n| n.kind() == SyntaxKind::PLANNING_DEADLINE) + .last() + .and_then(|n| n.children().find_map(Timestamp::cast)) + } + + /// Returns scheduled timestamp + /// + /// ```rust + /// use orgize::{ast::Planning, Org}; + /// + /// let s = Org::parse("* a\nSCHEDULED: <2019-04-08 Mon>") + /// .first_node::() + /// .unwrap() + /// .scheduled() + /// .unwrap(); + /// assert_eq!(s.year_start().unwrap().text(), "2019"); + /// ``` + pub fn scheduled(&self) -> Option { + self.syntax + .children() + .filter(|n| n.kind() == SyntaxKind::PLANNING_SCHEDULED) + .last() + .and_then(|n| n.children().find_map(Timestamp::cast)) + } + + /// Returns closed timestamp + /// + /// ```rust + /// use orgize::{ast::Planning, Org}; + /// + /// let s = Org::parse("* a\nCLOSED: <2019-04-08 Mon>") + /// .first_node::() + /// .unwrap() + /// .closed() + /// .unwrap(); + /// assert_eq!(s.month_start().unwrap().text(), "04"); + /// ``` + pub fn closed(&self) -> Option { + self.syntax + .children() + .filter(|n| n.kind() == SyntaxKind::PLANNING_CLOSED) + .last() + .and_then(|n| n.children().find_map(Timestamp::cast)) + } +} diff --git a/src/export/forward.rs b/src/export/forward.rs index 602061f..cfe87fc 100644 --- a/src/export/forward.rs +++ b/src/export/forward.rs @@ -28,14 +28,12 @@ /// fn headline_title(&mut self, event: WalkEvent<&HeadlineTitle>, _ctx: &mut TraversalContext) { /// match event { /// WalkEvent::Enter(title) => { -/// let level = title.headline().and_then(|h| h.level()).unwrap_or(1); -/// let level = min(level, 6); +/// let level = title.headline().map(|h| min(h.level(), 6)).unwrap_or(1); /// let raw = title.syntax().to_string(); /// self.0.push_str(format!("", slugify!(&raw))); /// } /// WalkEvent::Leave(title) => { -/// let level = title.headline().and_then(|h| h.level()).unwrap_or(1); -/// let level = min(level, 6); +/// let level = title.headline().map(|h| min(h.level(), 6)).unwrap_or(1); /// self.0.push_str(format!("")); /// } /// } diff --git a/src/export/html.rs b/src/export/html.rs index 0ee41df..df894f7 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -1,4 +1,5 @@ use rowan::WalkEvent; +use std::cmp::min; use std::fmt; use super::TraversalContext; @@ -188,19 +189,11 @@ impl Traverser for HtmlExport { fn headline_title(&mut self, event: WalkEvent<&HeadlineTitle>, _ctx: &mut TraversalContext) { self.output += &match event { WalkEvent::Enter(title) => { - let level = title - .headline() - .and_then(|hdl| hdl.level()) - .map(|lvl| std::cmp::min(lvl, 6)) - .unwrap_or(1); + let level = title.headline().map(|h| min(h.level(), 6)).unwrap_or(1); format!("") } WalkEvent::Leave(title) => { - let level = title - .headline() - .and_then(|hdl| hdl.level()) - .map(|lvl| std::cmp::min(lvl, 6)) - .unwrap_or(1); + let level = title.headline().map(|h| min(h.level(), 6)).unwrap_or(1); format!("") } }; diff --git a/src/syntax/headline.rs b/src/syntax/headline.rs index 6bf47db..1821659 100644 --- a/src/syntax/headline.rs +++ b/src/syntax/headline.rs @@ -273,8 +273,8 @@ fn parse() { ); let hdl = to_headline("* TODO foo\nbar\n** baz\n"); - assert_eq!(hdl.level(), Some(1)); - assert_eq!(hdl.keyword().as_ref().map(|x| x.text()), Some("TODO")); + assert_eq!(hdl.level(), 1); + assert_eq!(hdl.keyword().unwrap().text(), "TODO"); insta::assert_debug_snapshot!( hdl.syntax, @r###" @@ -299,11 +299,8 @@ fn parse() { ); let hdl = to_headline("** [#A] foo\n* baz"); - assert_eq!(hdl.level(), Some(2)); - assert_eq!( - hdl.priority().unwrap().text_string().unwrap(), - "A".to_string() - ); + assert_eq!(hdl.level(), 2); + assert_eq!(hdl.priority().unwrap().text(), "A"); insta::assert_debug_snapshot!( hdl.syntax, @r###" @@ -329,41 +326,41 @@ fn issue_15_16() { let to_headline = to_ast::(headline_node); - assert!(to_headline("* a ::").tags().is_none()); - assert!(to_headline("* a : :").tags().is_none()); - assert!(to_headline("* a :(:").tags().is_none()); - assert!(to_headline("* a :a: :").tags().is_none()); - assert!(to_headline("* a :a :").tags().is_none()); - assert!(to_headline("* a a:").tags().is_none()); - assert!(to_headline("* a :a").tags().is_none()); + assert!(to_headline("* a ::").tags().count() == 0); + assert!(to_headline("* a : :").tags().count() == 0); + assert!(to_headline("* a :(:").tags().count() == 0); + assert!(to_headline("* a :a: :").tags().count() == 0); + assert!(to_headline("* a :a :").tags().count() == 0); + assert!(to_headline("* a a:").tags().count() == 0); + assert!(to_headline("* a :a").tags().count() == 0); - let tags = to_headline("* a \t:_:").tags().unwrap(); + let tags = to_headline("* a \t:_:").tags(); assert_eq!( vec!["_".to_string()], - tags.iter().map(|x| x.to_string()).collect::>(), + tags.map(|x| x.to_string()).collect::>(), ); - let tags = to_headline("* a \t :@:").tags().unwrap(); + let tags = to_headline("* a \t :@:").tags(); assert_eq!( vec!["@".to_string()], - tags.iter().map(|x| x.to_string()).collect::>(), + tags.map(|x| x.to_string()).collect::>(), ); - let tags = to_headline("* a :#:").tags().unwrap(); + let tags = to_headline("* a :#:").tags(); assert_eq!( vec!["#".to_string()], - tags.iter().map(|x| x.to_string()).collect::>(), + tags.map(|x| x.to_string()).collect::>(), ); - let tags = to_headline("* a\t :%:").tags().unwrap(); + let tags = to_headline("* a\t :%:").tags(); assert_eq!( vec!["%".to_string()], - tags.iter().map(|x| x.to_string()).collect::>(), + tags.map(|x| x.to_string()).collect::>(), ); - let tags = to_headline("* a :余: :破:").tags().unwrap(); + let tags = to_headline("* a :余: :破:").tags(); assert_eq!( vec!["余".to_string(), "破".to_string()], - tags.iter().map(|x| x.to_string()).collect::>(), + tags.map(|x| x.to_string()).collect::>(), ); }