From 6c27a9257f25a44fb7342b17dbaee51f83160fca Mon Sep 17 00:00:00 2001 From: PoiScript Date: Mon, 20 Nov 2023 14:56:38 +0800 Subject: [PATCH] feat: introduce Token struct --- README.md | 2 +- src/ast/affiliated_keyword.rs | 39 ++++++------- src/ast/clock.rs | 58 ++++++++++++++++++ src/ast/drawer.rs | 33 +++++------ src/ast/entity.rs | 80 +++++++++++++++---------- src/ast/generate.js | 18 ++---- src/ast/generated.rs | 75 +++++++----------------- src/ast/headline.rs | 37 ++++++------ src/ast/inline_call.rs | 79 ++++++++++++++++++++++--- src/ast/inline_src.rs | 68 +++++++++++++++++++++ src/ast/link.rs | 33 +++++++++-- src/ast/list.rs | 61 +++++++++++-------- src/ast/macros.rs | 38 ++++++++++++ src/ast/mod.rs | 107 ++++++++++++++++++++++++++++++---- src/ast/planning.rs | 6 +- src/ast/snippet.rs | 30 ++++++++-- src/ast/timestamp.rs | 24 ++++---- src/export/html.rs | 13 ++--- src/syntax/clock.rs | 16 ++--- src/syntax/element.rs | 3 +- src/syntax/link.rs | 1 - src/syntax/macros.rs | 26 ++++----- src/syntax/mod.rs | 1 - 23 files changed, 586 insertions(+), 262 deletions(-) create mode 100644 src/ast/clock.rs create mode 100644 src/ast/inline_src.rs create mode 100644 src/ast/macros.rs diff --git a/README.md b/README.md index e526c78..5e7988d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ let config = ParseConfig { }; let org = config.parse("* TASK Title 1"); let hdl = org.first_node::().unwrap(); -assert_eq!(hdl.todo_keyword().unwrap().text(), "TASK"); +assert_eq!(hdl.todo_keyword().unwrap(), "TASK"); ``` ## Render to html diff --git a/src/ast/affiliated_keyword.rs b/src/ast/affiliated_keyword.rs index 07ed569..ed8ff6c 100644 --- a/src/ast/affiliated_keyword.rs +++ b/src/ast/affiliated_keyword.rs @@ -1,6 +1,6 @@ -use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxToken}; +use crate::syntax::SyntaxKind; -use super::AffiliatedKeyword; +use super::{filter_token, AffiliatedKeyword, Token}; impl AffiliatedKeyword { /// @@ -8,13 +8,16 @@ impl AffiliatedKeyword { /// use orgize::{Org, ast::AffiliatedKeyword}; /// /// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::().unwrap(); - /// assert_eq!(keyword.key().unwrap().text(), "CAPTION"); + /// assert_eq!(keyword.key(), "CAPTION"); /// ``` - pub fn key(&self) -> Option { - self.syntax.children_with_tokens().find_map(|it| match it { - SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), - _ => None, - }) + pub fn key(&self) -> Token { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::TEXT)) + .unwrap_or_else(|| { + debug_assert!(false, "keyword must contains TEXT"); + Token::default() + }) } /// @@ -24,17 +27,14 @@ impl AffiliatedKeyword { /// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::().unwrap(); /// assert!(keyword.optional().is_none()); /// let keyword = Org::parse("#+CAPTION[OPTIONAL]: VALUE\nabc").first_node::().unwrap(); - /// assert_eq!(keyword.optional().unwrap().text(), "OPTIONAL"); + /// assert_eq!(keyword.optional().unwrap(), "OPTIONAL"); /// ``` - pub fn optional(&self) -> Option { + pub fn optional(&self) -> Option { self.syntax .children_with_tokens() .skip_while(|it| it.kind() != SyntaxKind::L_BRACKET) .nth(1) - .and_then(|it| match it { - SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), - _ => None, - }) + .and_then(filter_token(SyntaxKind::TEXT)) } /// @@ -42,17 +42,14 @@ impl AffiliatedKeyword { /// use orgize::{Org, ast::AffiliatedKeyword}; /// /// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::().unwrap(); - /// assert_eq!(keyword.value().unwrap().text(), " VALUE"); + /// assert_eq!(keyword.value().unwrap(), " VALUE"); /// let keyword = Org::parse("#+CAPTION[OPTIONAL]:VALUE\nabc").first_node::().unwrap(); - /// assert_eq!(keyword.value().unwrap().text(), "VALUE"); + /// assert_eq!(keyword.value().unwrap(), "VALUE"); /// ``` - pub fn value(&self) -> Option { + pub fn value(&self) -> Option { self.syntax .children_with_tokens() - .filter_map(|it| match it { - SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), - _ => None, - }) + .filter_map(filter_token(SyntaxKind::TEXT)) .last() } } diff --git a/src/ast/clock.rs b/src/ast/clock.rs new file mode 100644 index 0000000..5317ff6 --- /dev/null +++ b/src/ast/clock.rs @@ -0,0 +1,58 @@ +use rowan::ast::support; + +use crate::{ast::Token, SyntaxKind}; + +use super::{Clock, Timestamp}; + +impl Clock { + pub fn value(&self) -> Option { + support::child(&self.syntax) + } + + /// ```rust + /// use orgize::{Org, ast::Clock}; + /// + /// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39]").first_node::().unwrap(); + /// assert!(clock.duration().is_none()); + /// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39] =>12:00").first_node::().unwrap(); + /// assert_eq!(clock.duration().unwrap(), "12:00"); + /// + /// ``` + pub fn duration(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|t| t.kind() != SyntaxKind::DOUBLE_ARROW) + .skip(1) + .find(|t| t.kind() != SyntaxKind::WHITESPACE) + .map(|e| { + debug_assert!(e.kind() == SyntaxKind::TEXT); + Token(e.into_token()) + }) + } + + /// ```rust + /// use orgize::{Org, ast::Clock}; + /// + /// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39]").first_node::().unwrap(); + /// assert!(!clock.is_closed()); + /// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39] =>12:00").first_node::().unwrap(); + /// assert!(clock.is_closed()); + /// ``` + pub fn is_closed(&self) -> bool { + self.syntax + .children_with_tokens() + .any(|t| t.kind() == SyntaxKind::DOUBLE_ARROW) + } + + /// ```rust + /// use orgize::{Org, ast::Clock}; + /// + /// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39]").first_node::().unwrap(); + /// assert!(clock.is_running()); + /// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39] =>12:00").first_node::().unwrap(); + /// assert!(!clock.is_running()); + /// ``` + pub fn is_running(&self) -> bool { + !self.is_closed() + } +} diff --git a/src/ast/drawer.rs b/src/ast/drawer.rs index 3baa2a8..e6225b0 100644 --- a/src/ast/drawer.rs +++ b/src/ast/drawer.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use super::{filter_token, SyntaxKind::*}; -use crate::{ast::PropertyDrawer, syntax::SyntaxToken}; +use super::{filter_token, SyntaxKind, Token}; +use crate::ast::PropertyDrawer; impl PropertyDrawer { /// ```rust @@ -11,12 +11,12 @@ impl PropertyDrawer { /// let drawer = org.first_node::().unwrap(); /// assert_eq!(drawer.iter().count(), 2); /// ``` - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.node_properties().filter_map(|property| { let mut texts = property .syntax .children_with_tokens() - .filter_map(filter_token(TEXT)); + .filter_map(filter_token(SyntaxKind::TEXT)); Some((texts.next()?, texts.next()?)) }) @@ -27,12 +27,11 @@ impl PropertyDrawer { /// /// let org = Org::parse("* Heading\n:PROPERTIES:\n:CUSTOM_ID: someid\n:ID: id\n:END:"); /// let drawer = org.first_node::().unwrap(); - /// assert_eq!(drawer.get("CUSTOM_ID").unwrap().text(), "someid"); - /// assert_eq!(drawer.get("ID").unwrap().text(), "id"); + /// assert_eq!(drawer.get("CUSTOM_ID").unwrap(), "someid"); + /// assert_eq!(drawer.get("ID").unwrap(), "id"); /// ``` - pub fn get(&self, key: &str) -> Option { - self.iter() - .find_map(|(k, v)| (k.text() == key).then_some(v)) + pub fn get(&self, key: &str) -> Option { + self.iter().find_map(|(k, v)| (k == key).then_some(v)) } /// ```rust @@ -44,10 +43,8 @@ impl PropertyDrawer { /// assert_eq!(map.len(), 1); /// assert_eq!(map.get("CUSTOM_ID").unwrap(), "id"); /// ``` - pub fn to_hash_map(&self) -> HashMap { - self.iter() - .map(|(k, v)| (k.text().into(), v.text().into())) - .collect() + pub fn to_hash_map(&self) -> HashMap { + self.iter().collect() } #[cfg(feature = "indexmap")] @@ -57,11 +54,11 @@ impl PropertyDrawer { /// let org = Org::parse("* Heading\n:PROPERTIES:\n:CUSTOM_ID: someid\n:ID: id\n:END:"); /// let drawer = org.first_node::().unwrap(); /// let map = drawer.to_index_map(); - /// assert_eq!(map.get_index(1).unwrap(), (&"ID".to_string(), &"id".to_string())); + /// let item1 = map.get_index(1).unwrap(); + /// assert_eq!(item1.0, "ID"); + /// assert_eq!(item1.1, "id"); /// ``` - pub fn to_index_map(&self) -> indexmap::IndexMap { - self.iter() - .map(|(k, v)| (k.text().into(), v.text().into())) - .collect() + pub fn to_index_map(&self) -> indexmap::IndexMap { + self.iter().collect() } } diff --git a/src/ast/entity.rs b/src/ast/entity.rs index 7306bfd..49cd19c 100644 --- a/src/ast/entity.rs +++ b/src/ast/entity.rs @@ -8,9 +8,8 @@ impl Entity { .syntax .children_with_tokens() .find_map(filter_token(SyntaxKind::TEXT))?; - let token = token.text(); - ENTITIES.iter().find(|i| i.0 == token) + ENTITIES.iter().find(|i| i.0 == token.as_ref()) } /// Entity name @@ -24,10 +23,13 @@ impl Entity { /// assert_eq!(e.name(), " "); /// ``` pub fn name(&self) -> &str { - self.entity().map(|e| e.0).unwrap_or_else(|| { - debug_assert!(false); - "" - }) + self.entity().map_or_else( + || { + debug_assert!(false); + "" + }, + |e| e.0, + ) } /// Entity LaTeX representation @@ -39,10 +41,13 @@ impl Entity { /// assert_eq!(e.latex(), "\\textperiodcentered{}"); /// ``` pub fn latex(&self) -> &str { - self.entity().map(|e| e.1).unwrap_or_else(|| { - debug_assert!(false); - "" - }) + self.entity().map_or_else( + || { + debug_assert!(false); + "" + }, + |e| e.1, + ) } /// Whether entity needs to be in math mode @@ -56,10 +61,13 @@ impl Entity { /// assert!(e.is_latex_math()); /// ``` pub fn is_latex_math(&self) -> bool { - self.entity().map(|e| e.2).unwrap_or_else(|| { - debug_assert!(false); - false - }) + self.entity().map_or_else( + || { + debug_assert!(false); + false + }, + |e| e.2, + ) } /// Entity HTML representation @@ -71,10 +79,13 @@ impl Entity { /// assert_eq!(e.html(), "§"); /// ``` pub fn html(&self) -> &str { - self.entity().map(|e| e.3).unwrap_or_else(|| { - debug_assert!(false); - "" - }) + self.entity().map_or_else( + || { + debug_assert!(false); + "" + }, + |e| e.3, + ) } /// Entity ASCII representation @@ -86,10 +97,13 @@ impl Entity { /// assert_eq!(e.ascii(), "section"); /// ``` pub fn ascii(&self) -> &str { - self.entity().map(|e| e.4).unwrap_or_else(|| { - debug_assert!(false); - "" - }) + self.entity().map_or_else( + || { + debug_assert!(false); + "" + }, + |e| e.4, + ) } /// Entity Latin1 encoding representation @@ -103,10 +117,13 @@ impl Entity { /// assert_eq!(e.latin1(), ">"); /// ``` pub fn latin1(&self) -> &str { - self.entity().map(|e| e.5).unwrap_or_else(|| { - debug_assert!(false); - "" - }) + self.entity().map_or_else( + || { + debug_assert!(false); + "" + }, + |e| e.5, + ) } /// Entity UTF-8 encoding representation @@ -120,10 +137,13 @@ impl Entity { /// assert_eq!(e.utf8(), "›"); /// ``` pub fn utf8(&self) -> &str { - self.entity().map(|e| e.6).unwrap_or_else(|| { - debug_assert!(false); - "" - }) + self.entity().map_or_else( + || { + debug_assert!(false); + "" + }, + |e| e.6, + ) } /// Entity contains optional brackets diff --git a/src/ast/generate.js b/src/ast/generate.js index d9ac885..b0e6ea3 100644 --- a/src/ast/generate.js +++ b/src/ast/generate.js @@ -180,7 +180,6 @@ const nodes = [ { struct: "Link", kind: ["LINK"], - token: [["path", "LINK_PATH"]], }, { struct: "Cookie", @@ -198,14 +197,9 @@ const nodes = [ struct: "Macros", kind: ["MACROS"], }, - { - struct: "MacrosArgument", - kind: ["MACROS_ARGUMENT"], - }, { struct: "Snippet", kind: ["SNIPPET"], - token: [["name", "TEXT"]], }, { struct: "Target", @@ -276,10 +270,10 @@ use rowan::ast::{support, AstChildren, AstNode}; use crate::syntax::{OrgLanguage, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken}; fn affiliated_keyword(node: &SyntaxNode, filter: impl Fn(&str) -> bool) -> Option { - node.children() - .take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD) - .filter_map(AffiliatedKeyword::cast) - .find(|k| matches!(k.key(), Some(k) if filter(k.text()))) + node.children() + .take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD) + .filter_map(AffiliatedKeyword::cast) + .find(|k| filter(&k.key())) } `; @@ -308,10 +302,10 @@ impl ${node.struct} { } `; for (const [method, kind] of node.token || []) { - content += ` pub fn ${method}(&self) -> Option { support::token(&self.syntax, ${kind}) }\n`; + content += ` pub fn ${method}(&self) -> Option { super::token(&self.syntax, ${kind}) }\n`; } for (const [method, kind] of node.last_token || []) { - content += ` pub fn ${method}(&self) -> Option { super::last_token(&self.syntax, ${kind}) }\n`; + content += ` pub fn ${method}(&self) -> Option { super::last_token(&self.syntax, ${kind}) }\n`; } for (const [method, kind] of node.parent || []) { content += ` pub fn ${method}(&self) -> Option<${kind}> { self.syntax.parent().and_then(${kind}::cast) }\n`; diff --git a/src/ast/generated.rs b/src/ast/generated.rs index 52e17cc..8a32652 100644 --- a/src/ast/generated.rs +++ b/src/ast/generated.rs @@ -12,7 +12,7 @@ fn affiliated_keyword( node.children() .take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD) .filter_map(AffiliatedKeyword::cast) - .find(|k| matches!(k.key(), Some(k) if filter(k.text()))) + .find(|k| filter(&k.key())) } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -717,8 +717,8 @@ impl Comment { pub fn end(&self) -> u32 { self.syntax.text_range().end().into() } - pub fn text(&self) -> Option { - support::token(&self.syntax, TEXT) + pub fn text(&self) -> Option { + super::token(&self.syntax, TEXT) } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) @@ -796,8 +796,8 @@ impl FixedWidth { pub fn end(&self) -> u32 { self.syntax.text_range().end().into() } - pub fn text(&self) -> Option { - support::token(&self.syntax, TEXT) + pub fn text(&self) -> Option { + super::token(&self.syntax, TEXT) } pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) @@ -1257,9 +1257,6 @@ impl Link { pub fn end(&self) -> u32 { self.syntax.text_range().end().into() } - pub fn path(&self) -> Option { - support::token(&self.syntax, LINK_PATH) - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1362,31 +1359,6 @@ impl Macros { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MacrosArgument { - pub(crate) syntax: SyntaxNode, -} -impl AstNode for MacrosArgument { - type Language = OrgLanguage; - fn can_cast(kind: SyntaxKind) -> bool { - kind == MACROS_ARGUMENT - } - fn cast(node: SyntaxNode) -> Option { - Self::can_cast(node.kind()).then(|| MacrosArgument { syntax: node }) - } - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -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 { pub(crate) syntax: SyntaxNode, @@ -1410,9 +1382,6 @@ impl Snippet { pub fn end(&self) -> u32 { self.syntax.text_range().end().into() } - pub fn name(&self) -> Option { - support::token(&self.syntax, TEXT) - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1588,8 +1557,8 @@ impl Code { pub fn end(&self) -> u32 { self.syntax.text_range().end().into() } - pub fn text(&self) -> Option { - support::token(&self.syntax, TEXT) + pub fn text(&self) -> Option { + super::token(&self.syntax, TEXT) } } @@ -1616,34 +1585,34 @@ impl Timestamp { pub fn end(&self) -> u32 { self.syntax.text_range().end().into() } - pub fn year_start(&self) -> Option { - support::token(&self.syntax, TIMESTAMP_YEAR) + pub fn year_start(&self) -> Option { + super::token(&self.syntax, TIMESTAMP_YEAR) } - pub fn month_start(&self) -> Option { - support::token(&self.syntax, TIMESTAMP_MONTH) + pub fn month_start(&self) -> Option { + super::token(&self.syntax, TIMESTAMP_MONTH) } - pub fn day_start(&self) -> Option { - support::token(&self.syntax, TIMESTAMP_DAY) + pub fn day_start(&self) -> Option { + super::token(&self.syntax, TIMESTAMP_DAY) } - pub fn hour_start(&self) -> Option { - support::token(&self.syntax, TIMESTAMP_HOUR) + pub fn hour_start(&self) -> Option { + super::token(&self.syntax, TIMESTAMP_HOUR) } - pub fn minute_start(&self) -> Option { - support::token(&self.syntax, TIMESTAMP_MINUTE) + pub fn minute_start(&self) -> Option { + super::token(&self.syntax, TIMESTAMP_MINUTE) } - pub fn year_end(&self) -> Option { + pub fn year_end(&self) -> Option { super::last_token(&self.syntax, TIMESTAMP_YEAR) } - pub fn month_end(&self) -> Option { + pub fn month_end(&self) -> Option { super::last_token(&self.syntax, TIMESTAMP_MONTH) } - pub fn day_end(&self) -> Option { + pub fn day_end(&self) -> Option { super::last_token(&self.syntax, TIMESTAMP_DAY) } - pub fn hour_end(&self) -> Option { + pub fn hour_end(&self) -> Option { super::last_token(&self.syntax, TIMESTAMP_HOUR) } - pub fn minute_end(&self) -> Option { + pub fn minute_end(&self) -> Option { super::last_token(&self.syntax, TIMESTAMP_MINUTE) } } diff --git a/src/ast/headline.rs b/src/ast/headline.rs index 63bf75a..46dc446 100644 --- a/src/ast/headline.rs +++ b/src/ast/headline.rs @@ -1,11 +1,8 @@ use rowan::NodeOrToken; -use crate::{ - syntax::{SyntaxKind, SyntaxToken}, - SyntaxElement, -}; +use crate::{syntax::SyntaxKind, SyntaxElement}; -use super::{filter_token, Headline, Timestamp}; +use super::{filter_token, Headline, Timestamp, Token}; #[derive(Debug, Copy, Clone, PartialEq)] pub enum TodoType { @@ -28,20 +25,22 @@ impl Headline { 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 - }) + .map_or_else( + || { + debug_assert!(false, "headline must contains starts token"); + 0 + }, + |stars| stars.len(), + ) } /// ```rust /// use orgize::{Org, ast::Headline}; /// /// let hdl = Org::parse("* TODO a").first_node::().unwrap(); - /// assert_eq!(hdl.todo_keyword().unwrap().text(), "TODO"); + /// assert_eq!(hdl.todo_keyword().unwrap(), "TODO"); /// ``` - pub fn todo_keyword(&self) -> Option { + pub fn todo_keyword(&self) -> Option { self.syntax .children_with_tokens() .find_map(|elem| match elem { @@ -49,7 +48,7 @@ impl Headline { if tk.kind() == SyntaxKind::HEADLINE_KEYWORD_TODO || tk.kind() == SyntaxKind::HEADLINE_KEYWORD_DONE => { - Some(tk) + Some(Token(Some(tk))) } _ => None, }) @@ -131,7 +130,7 @@ impl Headline { /// assert!(!hdl.is_archived()); /// ``` pub fn is_archived(&self) -> bool { - self.tags().any(|t| t.text() == "ARCHIVE") + self.tags().any(|t| t == "ARCHIVE") } /// Returns this headline's closed timestamp, or `None` if not set. @@ -165,7 +164,7 @@ impl Headline { /// 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 { + pub fn tags(&self) -> impl Iterator { self.syntax .children() .find(|n| n.kind() == SyntaxKind::HEADLINE_TAGS) @@ -180,13 +179,13 @@ impl Headline { /// use orgize::{Org, ast::Headline}; /// /// let hdl = Org::parse("* [#A]").first_node::().unwrap(); - /// assert_eq!(hdl.priority().unwrap().text(), "A"); + /// assert_eq!(hdl.priority().unwrap(), "A"); /// let hdl = Org::parse("** DONE [#B]::").first_node::().unwrap(); - /// assert_eq!(hdl.priority().unwrap().text(), "B"); + /// assert_eq!(hdl.priority().unwrap(), "B"); /// let hdl = Org::parse("* [#破]").first_node::().unwrap(); - /// assert_eq!(hdl.priority().unwrap().text(), "破"); + /// assert_eq!(hdl.priority().unwrap(), "破"); /// ``` - pub fn priority(&self) -> Option { + pub fn priority(&self) -> Option { self.syntax .children() .find(|n| n.kind() == SyntaxKind::HEADLINE_PRIORITY) diff --git a/src/ast/inline_call.rs b/src/ast/inline_call.rs index d259af0..6994295 100644 --- a/src/ast/inline_call.rs +++ b/src/ast/inline_call.rs @@ -1,6 +1,6 @@ -use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxToken}; +use crate::syntax::SyntaxKind; -use super::InlineCall; +use super::{filter_token, InlineCall, Token}; impl InlineCall { /// @@ -8,15 +8,78 @@ impl InlineCall { /// use orgize::{Org, ast::InlineCall}; /// /// let call = Org::parse("call_square(4)").first_node::().unwrap(); - /// assert_eq!(call.call().unwrap().text(), "square"); + /// assert_eq!(call.call(), "square"); /// ``` - pub fn call(&self) -> Option { + pub fn call(&self) -> Token { self.syntax .children_with_tokens() - .filter_map(|it| match it { - SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), - _ => None, - }) + .filter_map(filter_token(SyntaxKind::TEXT)) .nth(1) + .unwrap_or_else(|| { + debug_assert!(false, "inline call must contains two TEXT"); + Token::default() + }) + } + + /// + /// ```rust + /// use orgize::{Org, ast::InlineCall}; + /// + /// let call = Org::parse("call_square[:results output](4)").first_node::().unwrap(); + /// assert_eq!(call.inside_header().unwrap(), ":results output"); + /// ``` + pub fn inside_header(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|e| e.kind() != SyntaxKind::L_BRACKET) + .nth(1) + .map(|e| { + debug_assert!(e.kind() == SyntaxKind::TEXT); + Token(e.into_token()) + }) + } + + /// + /// ```rust + /// use orgize::{Org, ast::InlineCall}; + /// + /// let call = Org::parse("call_square(4)").first_node::().unwrap(); + /// assert_eq!(call.arguments(), "4"); + /// ``` + pub fn arguments(&self) -> Token { + self.syntax + .children_with_tokens() + .skip_while(|e| e.kind() != SyntaxKind::L_PARENS) + .nth(1) + .map_or_else( + || { + debug_assert!(false); + Token::default() + }, + |e| { + debug_assert!(e.kind() == SyntaxKind::TEXT); + Token(e.into_token()) + }, + ) + } + + /// + /// ```rust + /// use orgize::{Org, ast::InlineCall}; + /// + /// let call = Org::parse("call_square[:results output](4)[:results html]").first_node::().unwrap(); + /// assert_eq!(call.end_header().unwrap(), ":results html"); + /// ``` + pub fn end_header(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|e| e.kind() != SyntaxKind::L_BRACKET) + .skip(1) + .skip_while(|e| e.kind() != SyntaxKind::L_BRACKET) + .nth(1) + .map(|e| { + debug_assert!(e.kind() == SyntaxKind::TEXT); + Token(e.into_token()) + }) } } diff --git a/src/ast/inline_src.rs b/src/ast/inline_src.rs new file mode 100644 index 0000000..163e845 --- /dev/null +++ b/src/ast/inline_src.rs @@ -0,0 +1,68 @@ +use crate::SyntaxKind; + +use super::{filter_token, InlineSrc, Token}; + +impl InlineSrc { + /// Language of the code + /// + /// ```rust + /// use orgize::{Org, ast::InlineSrc}; + /// + /// let s = Org::parse("src_C{int a = 0;}").first_node::().unwrap(); + /// assert_eq!(s.language(), "C"); + /// let s = Org::parse("src_xml[:exports code]{text}").first_node::().unwrap(); + /// assert_eq!(s.language(), "xml"); + /// ``` + pub fn language(&self) -> Token { + self.syntax + .children_with_tokens() + .nth(1) + .and_then(filter_token(SyntaxKind::TEXT)) + .unwrap_or_else(|| { + debug_assert!(false, "inline src must contains TEXT"); + Token::default() + }) + } + + /// Optional header arguments + /// + /// ```rust + /// use orgize::{Org, ast::InlineSrc}; + /// + /// let s = Org::parse("src_C{int a = 0;}").first_node::().unwrap(); + /// assert!(s.parameters().is_none()); + /// let s = Org::parse("src_xml[:exports code]{text}").first_node::().unwrap(); + /// assert_eq!(s.parameters().unwrap(), ":exports code"); + /// ``` + pub fn parameters(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|n| n.kind() != SyntaxKind::L_BRACKET) + .nth(1) + .map(|n| { + debug_assert!(n.kind() == SyntaxKind::TEXT); + Token(n.into_token()) + }) + } + + /// Source code + /// + /// ```rust + /// use orgize::{Org, ast::InlineSrc}; + /// + /// let s = Org::parse("src_C{int a = 0;}").first_node::().unwrap(); + /// assert_eq!(s.value(), "int a = 0;"); + /// let s = Org::parse("src_xml[:exports code]{text}").first_node::().unwrap(); + /// assert_eq!(s.value(), "text"); + /// ``` + pub fn value(&self) -> Token { + self.syntax + .children_with_tokens() + .filter_map(filter_token(SyntaxKind::TEXT)) + .last() + .unwrap_or_else(|| { + debug_assert!(false, "inline src must contains TEXT"); + Token::default() + }) + } +} diff --git a/src/ast/link.rs b/src/ast/link.rs index f2aa87e..eddb2ef 100644 --- a/src/ast/link.rs +++ b/src/ast/link.rs @@ -1,9 +1,31 @@ use rowan::ast::{support, AstNode}; -use super::{AffiliatedKeyword, Link, Paragraph}; +use super::{AffiliatedKeyword, Link, Paragraph, Token}; use crate::syntax::SyntaxKind; impl Link { + /// Returns link destination + /// + /// ```rust + /// use orgize::{Org, ast::Link}; + /// + /// let link = Org::parse("[[#id]]").first_node::().unwrap(); + /// assert_eq!(link.path(), "#id"); + /// let link = Org::parse("[[https://google.com]]").first_node::().unwrap(); + /// assert_eq!(link.path(), "https://google.com"); + /// let link = Org::parse("[[https://google.com][Google]]").first_node::().unwrap(); + /// assert_eq!(link.path(), "https://google.com"); + /// ``` + pub fn path(&self) -> Token { + support::token(&self.syntax, SyntaxKind::LINK_PATH).map_or_else( + || { + debug_assert!(false, "link must contains LINK_PATH"); + Token::default() + }, + |e| Token(Some(e)), + ) + } + /// Returns `true` if link contains description /// /// ```rust @@ -35,10 +57,9 @@ impl Link { ".ppm", ".webp", ".avif", ".svg", ]; - self.path() - .map(|path| IMAGE_SUFFIX.iter().any(|e| path.text().ends_with(e))) - .unwrap_or_default() - && !self.has_description() + let path = self.path(); + + IMAGE_SUFFIX.iter().any(|e| path.ends_with(e)) && !self.has_description() } /// Returns caption keyword in this link @@ -47,7 +68,7 @@ impl Link { /// use orgize::{Org, ast::Link}; /// /// let link = Org::parse("#+CAPTION: image link\n[[file:/home/dominik/images/jupiter.jpg]]").first_node::().unwrap(); - /// assert_eq!(link.caption().unwrap().value().unwrap().text(), " image link"); + /// assert_eq!(link.caption().unwrap().value().unwrap(), " image link"); /// ``` pub fn caption(&self) -> Option { // TODO: support other element type diff --git a/src/ast/list.rs b/src/ast/list.rs index 24e8258..27aec7c 100644 --- a/src/ast/list.rs +++ b/src/ast/list.rs @@ -1,5 +1,5 @@ -use super::{filter_token, List, ListItem}; -use crate::{syntax::SyntaxKind, SyntaxElement, SyntaxToken}; +use super::{filter_token, List, ListItem, Token}; +use crate::{syntax::SyntaxKind, SyntaxElement}; impl List { /// Returns `true` if this list is an ordered link @@ -17,11 +17,13 @@ impl List { /// assert!(list.is_ordered()); /// ``` pub fn is_ordered(&self) -> bool { - self.items() - .next() - .and_then(|item| item.bullet()) - .map(|bullet| bullet.text().starts_with(|c: char| c.is_ascii_digit())) - .unwrap_or_default() + self.items().next().map_or_else( + || { + debug_assert!(false, "list muts contains LIST_ITEM"); + false + }, + |item| item.bullet().starts_with(|c: char| c.is_ascii_digit()), + ) } /// Returns `true` if this list contains a TAG @@ -35,14 +37,17 @@ impl List { /// assert!(!list.is_descriptive()); /// ``` pub fn is_descriptive(&self) -> bool { - self.items() - .next() - .map(|item| { + self.items().next().map_or_else( + || { + debug_assert!(false, "list must contains LIST_ITEM"); + false + }, + |item| { item.syntax .children() .any(|it| it.kind() == SyntaxKind::LIST_ITEM_TAG) - }) - .unwrap_or_default() + }, + ) } } @@ -59,38 +64,44 @@ impl ListItem { 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 - }) + .map_or_else( + || { + debug_assert!(false, "list item must contains LIST_ITEM_INDENT"); + 0 + }, + |t| t.len(), + ) } /// ```rust /// use orgize::{Org, ast::ListItem}; /// /// let item = Org::parse("- some tag").first_node::().unwrap(); - /// assert_eq!(item.bullet().unwrap().text(), "- "); + /// assert_eq!(item.bullet(), "- "); /// let item = Org::parse("2. [X] item 2").first_node::().unwrap(); - /// assert_eq!(item.bullet().unwrap().text(), "2. "); + /// assert_eq!(item.bullet(), "2. "); /// ``` - pub fn bullet(&self) -> Option { + pub fn bullet(&self) -> Token { self.syntax .children_with_tokens() .find_map(filter_token(SyntaxKind::LIST_ITEM_BULLET)) + .unwrap_or_else(|| { + debug_assert!(false, "list item must contains LIST_ITEM_BULLET"); + Token::default() + }) } /// ```rust /// use orgize::{Org, ast::ListItem}; /// /// let item = Org::parse("- [-] item 1").first_node::().unwrap(); - /// assert_eq!(item.checkbox().unwrap().text(), "-"); + /// assert_eq!(item.checkbox().unwrap(), "-"); /// let item = Org::parse("2. [X] item 2").first_node::().unwrap(); - /// assert_eq!(item.checkbox().unwrap().text(), "X"); + /// assert_eq!(item.checkbox().unwrap(), "X"); /// let item = Org::parse("3) [ ] item 3").first_node::().unwrap(); - /// assert_eq!(item.checkbox().unwrap().text(), " "); + /// assert_eq!(item.checkbox().unwrap(), " "); /// ``` - pub fn checkbox(&self) -> Option { + pub fn checkbox(&self) -> Option { self.syntax .children() .find(|n| n.kind() == SyntaxKind::LIST_ITEM_CHECK_BOX) @@ -100,7 +111,7 @@ impl ListItem { }) } - pub fn counter(&self) -> Option { + pub fn counter(&self) -> Option { self.syntax .children() .find(|n| n.kind() == SyntaxKind::LIST_ITEM_COUNTER) diff --git a/src/ast/macros.rs b/src/ast/macros.rs new file mode 100644 index 0000000..e315e4c --- /dev/null +++ b/src/ast/macros.rs @@ -0,0 +1,38 @@ +use crate::SyntaxKind; + +use super::{filter_token, Macros, Token}; + +impl Macros { + /// ```rust + /// use orgize::{Org, ast::Macros}; + /// + /// let m = Org::parse("{{{title}}}").first_node::().unwrap(); + /// assert_eq!(m.key(), "title"); + /// let m = Org::parse("{{{two_arg_macro(1, 2)}}}").first_node::().unwrap(); + /// assert_eq!(m.key(), "two_arg_macro"); + /// ``` + pub fn key(&self) -> Token { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::TEXT)) + .unwrap_or_else(|| { + debug_assert!(false, "macros must contains TEXT"); + Token::default() + }) + } + + /// ```rust + /// use orgize::{Org, ast::Macros}; + /// + /// let m = Org::parse("{{{title}}}").first_node::().unwrap(); + /// assert!(m.args().is_none()); + /// let m = Org::parse("{{{two_arg_macro(1, 2)}}}").first_node::().unwrap(); + /// assert_eq!(m.args().unwrap(), "1, 2"); + /// ``` + pub fn args(&self) -> Option { + self.syntax + .children_with_tokens() + .filter_map(filter_token(SyntaxKind::TEXT)) + .nth(1) + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 366916c..440bd64 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3,24 +3,37 @@ mod generated; mod affiliated_keyword; +mod clock; mod drawer; mod entity; mod headline; mod inline_call; +mod inline_src; mod link; mod list; +mod macros; mod planning; mod snippet; mod table; mod timestamp; +use std::{ + borrow::{Borrow, Cow}, + fmt::Debug, + hash::Hash, + ops::Deref, +}; + pub use generated::*; pub use headline::*; pub use rowan::ast::support::*; pub use timestamp::*; -use crate::syntax::{SyntaxKind, SyntaxNode}; -use rowan::{ast::AstNode, Language, NodeOrToken}; +use crate::{ + syntax::{SyntaxKind, SyntaxNode}, + SyntaxToken, +}; +use rowan::{ast::AstNode, NodeOrToken}; pub fn blank_lines(parent: &SyntaxNode) -> usize { parent @@ -33,22 +46,94 @@ pub fn last_child(parent: &rowan::SyntaxNode) -> Option parent.children().filter_map(N::cast).last() } -pub fn last_token( - parent: &rowan::SyntaxNode, - kind: L::Kind, -) -> Option> { +pub fn last_token(parent: &SyntaxNode, kind: SyntaxKind) -> Option { parent .children_with_tokens() .filter_map(filter_token(kind)) .last() } -pub fn filter_token( - kind: L::Kind, -) -> impl Fn(NodeOrToken, rowan::SyntaxToken>) -> Option> -{ +pub fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option { + rowan::ast::support::token(parent, kind).map(|t| Token(Some(t))) +} + +pub fn filter_token( + kind: SyntaxKind, +) -> impl Fn(NodeOrToken) -> Option { move |elem| match elem { - NodeOrToken::Token(tk) if tk.kind() == kind => Some(tk), + NodeOrToken::Token(tk) if tk.kind() == kind => Some(Token(Some(tk))), _ => None, } } + +/// A simple wrapper of `Option` +/// +/// It acts like a `token.text()` when inner is `Some(token)`, and an empty string when `None`. +#[derive(Default, Eq)] +pub struct Token(pub(crate) Option); + +impl AsRef for Token { + fn as_ref(&self) -> &str { + match &self.0 { + Some(t) => t.text(), + None => "", + } + } +} + +impl Borrow for Token { + fn borrow(&self) -> &str { + self.as_ref() + } +} + +impl Debug for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_ref().fmt(f) + } +} + +impl<'a> PartialEq<&'a str> for Token { + fn eq(&self, other: &&'a str) -> bool { + self.as_ref() == *other + } +} + +impl PartialEq for Token { + fn eq(&self, other: &String) -> bool { + self.as_ref() == other + } +} + +impl PartialEq for Token { + fn eq(&self, other: &Token) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl Hash for Token { + fn hash(&self, state: &mut H) { + self.as_ref().hash(state) + } +} + +impl<'a> PartialEq> for Token { + fn eq(&self, other: &Cow<'a, str>) -> bool { + self.as_ref() == other + } +} + +impl PartialEq for Token { + fn eq(&self, other: &str) -> bool { + self.as_ref() == other + } +} + +impl Deref for Token { + type Target = str; + + #[inline] + fn deref(&self) -> &str { + self.as_ref() + } +} diff --git a/src/ast/planning.rs b/src/ast/planning.rs index f0b68c9..6a8bba3 100644 --- a/src/ast/planning.rs +++ b/src/ast/planning.rs @@ -15,7 +15,7 @@ impl Planning { /// .unwrap() /// .deadline() /// .unwrap(); - /// assert_eq!(s.day_start().unwrap().text(), "08"); + /// assert_eq!(s.day_start().unwrap(), "08"); /// ``` pub fn deadline(&self) -> Option { self.syntax @@ -35,7 +35,7 @@ impl Planning { /// .unwrap() /// .scheduled() /// .unwrap(); - /// assert_eq!(s.year_start().unwrap().text(), "2019"); + /// assert_eq!(s.year_start().unwrap(), "2019"); /// ``` pub fn scheduled(&self) -> Option { self.syntax @@ -55,7 +55,7 @@ impl Planning { /// .unwrap() /// .closed() /// .unwrap(); - /// assert_eq!(s.month_start().unwrap().text(), "04"); + /// assert_eq!(s.month_start().unwrap(), "04"); /// ``` pub fn closed(&self) -> Option { self.syntax diff --git a/src/ast/snippet.rs b/src/ast/snippet.rs index 398ec40..e80e7e1 100644 --- a/src/ast/snippet.rs +++ b/src/ast/snippet.rs @@ -1,18 +1,40 @@ -use crate::syntax::{SyntaxKind, SyntaxToken}; +use crate::syntax::SyntaxKind; -use super::{filter_token, Snippet}; +use super::{filter_token, Snippet, Token}; impl Snippet { /// ```rust /// use orgize::{Org, ast::Snippet}; /// /// let snippet = Org::parse("@@BACKEND:VALUE@@").first_node::().unwrap(); - /// assert_eq!(snippet.value().unwrap().text(), "VALUE"); + /// assert_eq!(snippet.backend(), "BACKEND"); /// ``` - pub fn value(&self) -> Option { + pub fn backend(&self) -> Token { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::TEXT)) + .unwrap_or_else(|| { + debug_assert!(false, "snippet must contains TEXT"); + Token::default() + }) + } + + /// ```rust + /// use orgize::{Org, ast::Snippet}; + /// + /// let snippet = Org::parse("@@BACKEND:@@").first_node::().unwrap(); + /// assert_eq!(snippet.value(), ""); + /// let snippet = Org::parse("@@BACKEND:VALUE@@").first_node::().unwrap(); + /// assert_eq!(snippet.value(), "VALUE"); + /// ``` + pub fn value(&self) -> Token { self.syntax .children_with_tokens() .filter_map(filter_token(SyntaxKind::TEXT)) .nth(1) + .unwrap_or_else(|| { + debug_assert!(false, "snippet must contains two TEXT"); + Token::default() + }) } } diff --git a/src/ast/timestamp.rs b/src/ast/timestamp.rs index d706945..fd894ab 100644 --- a/src/ast/timestamp.rs +++ b/src/ast/timestamp.rs @@ -98,7 +98,7 @@ impl Timestamp { self.syntax .children_with_tokens() .find_map(filter_token(SyntaxKind::TIMESTAMP_REPEATER_MARK)) - .map(|t| match t.text() { + .map(|t| match t.as_ref() { "++" => RepeaterType::CatchUp, "+" => RepeaterType::Cumulate, ".+" => RepeaterType::Restart, @@ -178,7 +178,7 @@ impl Timestamp { self.syntax .children_with_tokens() .find_map(filter_token(SyntaxKind::TIMESTAMP_DELAY_MARK)) - .map(|t| match t.text() { + .map(|t| match t.as_ref() { "-" => DelayType::All, "--" => DelayType::First, _ => { @@ -259,13 +259,13 @@ impl Timestamp { pub fn start_to_chrono(&self) -> Option { Some(chrono::NaiveDateTime::new( chrono::NaiveDate::from_ymd_opt( - self.year_start()?.text().parse().ok()?, - self.month_start()?.text().parse().ok()?, - self.day_start()?.text().parse().ok()?, + self.year_start()?.parse().ok()?, + self.month_start()?.parse().ok()?, + self.day_start()?.parse().ok()?, )?, chrono::NaiveTime::from_hms_opt( - self.hour_start()?.text().parse().ok()?, - self.minute_start()?.text().parse().ok()?, + self.hour_start()?.parse().ok()?, + self.minute_start()?.parse().ok()?, 0, )?, )) @@ -284,13 +284,13 @@ impl Timestamp { pub fn end_to_chrono(&self) -> Option { Some(chrono::NaiveDateTime::new( chrono::NaiveDate::from_ymd_opt( - self.year_end()?.text().parse().ok()?, - self.month_end()?.text().parse().ok()?, - self.day_end()?.text().parse().ok()?, + self.year_end()?.parse().ok()?, + self.month_end()?.parse().ok()?, + self.day_end()?.parse().ok()?, )?, chrono::NaiveTime::from_hms_opt( - self.hour_end()?.text().parse().ok()?, - self.minute_end()?.text().parse().ok()?, + self.hour_end()?.parse().ok()?, + self.minute_end()?.parse().ok()?, 0, )?, )) diff --git a/src/export/html.rs b/src/export/html.rs index dc771fb..286f534 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -168,10 +168,8 @@ impl Traverser for HtmlExport { #[tracing::instrument(skip(self, ctx))] fn snippet(&mut self, event: WalkEvent<&Snippet>, ctx: &mut TraversalContext) { if let WalkEvent::Enter(snippet) = event { - if matches!(snippet.name(), Some(name) if name.text().eq_ignore_ascii_case("html")) { - if let Some(value) = snippet.value() { - self.output += value.text() - } + if snippet.backend().eq_ignore_ascii_case("html") { + self.output += &snippet.value(); } return ctx.skip(); }; @@ -238,17 +236,16 @@ impl Traverser for HtmlExport { match event { WalkEvent::Enter(link) => { let path = link.path(); - let path = path.as_ref().map(|path| path.text()).unwrap_or_default(); if link.is_image() { - let _ = write!(&mut self.output, r#""#, HtmlEscape(path)); + let _ = write!(&mut self.output, r#""#, HtmlEscape(&path)); return ctx.skip(); } - let _ = write!(&mut self.output, r#""#, HtmlEscape(path)); + let _ = write!(&mut self.output, r#""#, HtmlEscape(&path)); if !link.has_description() { - let _ = write!(&mut self.output, "{}", HtmlEscape(path)); + let _ = write!(&mut self.output, "{}", HtmlEscape(&path)); return ctx.skip(); } } diff --git a/src/syntax/clock.rs b/src/syntax/clock.rs index de9cc20..0e98a31 100644 --- a/src/syntax/clock.rs +++ b/src/syntax/clock.rs @@ -2,7 +2,7 @@ use nom::{ branch::alt, bytes::complete::tag, character::complete::{digit1, line_ending, space0}, - combinator::{eof, map, opt}, + combinator::{eof, map, opt, recognize}, sequence::tuple, IResult, }; @@ -26,9 +26,7 @@ pub fn clock_node(input: Input) -> IResult { space0, double_arrow_token, space0, - digit1, - colon_token, - digit1, + recognize(tuple((digit1, colon_token, digit1))), ))), space0, alt((line_ending, eof)), @@ -41,13 +39,11 @@ pub fn clock_node(input: Input) -> IResult { b.text(clock); b.ws(ws_); b.push(timestamp); - if let Some((ws, double_arrow, ws_, hour, colon, minute)) = duration { + if let Some((ws, double_arrow, ws_, time)) = duration { b.ws(ws); b.push(double_arrow); b.ws(ws_); - b.text(hour); - b.push(colon); - b.text(minute); + b.text(time); } b.ws(ws__); b.nl(nl); @@ -125,9 +121,7 @@ fn parse() { WHITESPACE@53..54 " " DOUBLE_ARROW@54..56 "=>" WHITESPACE@56..58 " " - TEXT@58..59 "1" - COLON@59..60 ":" - TEXT@60..62 "00" + TEXT@58..62 "1:00" NEW_LINE@62..63 "\n" BLANK_LINE@63..64 "\n" "### diff --git a/src/syntax/element.rs b/src/syntax/element.rs index b133f15..f590622 100644 --- a/src/syntax/element.rs +++ b/src/syntax/element.rs @@ -141,8 +141,7 @@ impl<'a> Iterator for ElementPositions<'a> { let previous = self.pos; self.pos = iter .next() - .map(|i| i + self.pos) - .unwrap_or_else(|| self.input.s.len()); + .map_or_else(|| self.input.s.len(), |i| i + self.pos); debug_assert!( previous < self.pos && self.pos <= self.input.s.len(), diff --git a/src/syntax/link.rs b/src/syntax/link.rs index 680529a..93c93b8 100644 --- a/src/syntax/link.rs +++ b/src/syntax/link.rs @@ -48,7 +48,6 @@ fn parse() { let to_link = to_ast::(link_node); let link = to_link("[[#id]]"); - assert_eq!(link.path().as_ref().map(|x| x.text()), Some("#id")); insta::assert_debug_snapshot!( link.syntax, @r###" diff --git a/src/syntax/macros.rs b/src/syntax/macros.rs index 2568bf9..fe27025 100644 --- a/src/syntax/macros.rs +++ b/src/syntax/macros.rs @@ -30,10 +30,7 @@ pub fn macros_node(input: Input) -> IResult { children.push(l_curly3); children.push(name.text_token()); if let Some((l_parens, argument, r_parens)) = argument { - children.push(node( - MACROS_ARGUMENT, - [l_parens, argument.text_token(), r_parens], - )); + children.extend([l_parens, argument.text_token(), r_parens]); } children.push(r_curly3); node(MACROS, children) @@ -64,10 +61,9 @@ fn test() { MACROS@0..22 L_CURLY3@0..3 "{{{" TEXT@3..16 "one_arg_macro" - MACROS_ARGUMENT@16..19 - L_PARENS@16..17 "(" - TEXT@17..18 "1" - R_PARENS@18..19 ")" + L_PARENS@16..17 "(" + TEXT@17..18 "1" + R_PARENS@18..19 ")" R_CURLY3@19..22 "}}}" "### ); @@ -78,10 +74,9 @@ fn test() { MACROS@0..25 L_CURLY3@0..3 "{{{" TEXT@3..16 "two_arg_macro" - MACROS_ARGUMENT@16..22 - L_PARENS@16..17 "(" - TEXT@17..21 "1, 2" - R_PARENS@21..22 ")" + L_PARENS@16..17 "(" + TEXT@17..21 "1, 2" + R_PARENS@21..22 ")" R_CURLY3@22..25 "}}}" "### ); @@ -92,10 +87,9 @@ fn test() { MACROS@0..28 L_CURLY3@0..3 "{{{" TEXT@3..16 "two_arg_macro" - MACROS_ARGUMENT@16..25 - L_PARENS@16..17 "(" - TEXT@17..24 "1\\,a, 2" - R_PARENS@24..25 ")" + L_PARENS@16..17 "(" + TEXT@17..24 "1\\,a, 2" + R_PARENS@24..25 ")" R_CURLY3@25..28 "}}}" "### ); diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index f53e1b0..b16bb75 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -189,7 +189,6 @@ pub enum SyntaxKind { FN_REF, LATEX_FRAGMENT, MACROS, - MACROS_ARGUMENT, SNIPPET, TARGET, BOLD,