orgize/src/ast/headline.rs
2023-11-17 18:08:47 +08:00

358 lines
12 KiB
Rust

use crate::syntax::{SyntaxKind, SyntaxToken};
use super::{filter_token, Headline, Timestamp};
impl Headline {
/// Return level of this headline
///
/// ```rust
/// use orgize::{Org, ast::Headline};
///
/// let hdl = Org::parse("* ").first_node::<Headline>().unwrap();
/// assert_eq!(hdl.level(), 1);
/// let hdl = Org::parse("****** hello").first_node::<Headline>().unwrap();
/// assert_eq!(hdl.level(), 6);
/// ```
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
///
/// ```rust
/// use orgize::{Org, ast::Headline};
///
/// let hdl = Org::parse("* COMMENT").first_node::<Headline>().unwrap();
/// assert!(hdl.is_commented());
/// let hdl = Org::parse("* COMMENT hello").first_node::<Headline>().unwrap();
/// assert!(hdl.is_commented());
/// let hdl = Org::parse("* hello").first_node::<Headline>().unwrap();
/// assert!(!hdl.is_commented());
/// ```
pub fn is_commented(&self) -> bool {
self.title()
.and_then(|title| title.syntax.first_token())
.map(|title| {
let text = title.text();
title.kind() == SyntaxKind::TEXT
&& text.starts_with("COMMENT")
&& (text.len() == 7 || text[7..].starts_with(char::is_whitespace))
})
.unwrap_or_default()
}
/// Return `true` if this headline contains an archive tag
///
/// ```rust
/// use orgize::{Org, ast::Headline};
///
/// let hdl = Org::parse("* hello :ARCHIVE:").first_node::<Headline>().unwrap();
/// assert!(hdl.is_archived());
/// let hdl = Org::parse("* hello :ARCHIVED:").first_node::<Headline>().unwrap();
/// assert!(!hdl.is_archived());
/// ```
pub fn is_archived(&self) -> bool {
self.tags().any(|t| t.text() == "ARCHIVE")
}
/// Returns this headline's closed timestamp, or `None` if not set.
pub fn closed(&self) -> Option<Timestamp> {
self.planning().and_then(|planning| planning.closed())
}
/// Returns this headline's scheduled timestamp, or `None` if not set.
pub fn scheduled(&self) -> Option<Timestamp> {
self.planning().and_then(|planning| planning.scheduled())
}
/// Returns this headline's deadline timestamp, or `None` if not set.
pub fn deadline(&self) -> Option<Timestamp> {
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::<Headline>().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<Item = SyntaxToken> {
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::<Headline>().unwrap();
/// assert_eq!(hdl.priority().unwrap().text(), "A");
/// let hdl = Org::parse("** DONE [#B]::").first_node::<Headline>().unwrap();
/// assert_eq!(hdl.priority().unwrap().text(), "B");
/// let hdl = Org::parse("* [#破]").first_node::<Headline>().unwrap();
/// assert_eq!(hdl.priority().unwrap().text(), "破");
/// ```
pub fn priority(&self) -> Option<SyntaxToken> {
self.syntax
.children()
.find(|n| n.kind() == SyntaxKind::HEADLINE_PRIORITY)
.and_then(|n| {
n.children_with_tokens()
.find_map(filter_token(SyntaxKind::TEXT))
})
}
}
// pub enum DocumentOrHeadline {
// Document(Document),
// Headline(Headline),
// }
// impl From<Document> for DocumentOrHeadline {
// fn from(value: Document) -> Self {
// DocumentOrHeadline::Document(value)
// }
// }
// impl From<Headline> for DocumentOrHeadline {
// fn from(value: Headline) -> Self {
// DocumentOrHeadline::Headline(value)
// }
// }
// impl DocumentOrHeadline {
// pub fn section(&self) -> Option<Section> {
// match self {
// DocumentOrHeadline::Document(v) => v.section(),
// DocumentOrHeadline::Headline(v) => v.section(),
// }
// }
// }
// impl Org {
// /// set the title of this headline
// ///
// /// ```rust
// /// use orgize::Org;
// ///
// /// let mut org = Org::parse("* [#A]");
// /// let hdl = org.document().first_headline().unwrap();
// /// org.set_title(hdl, "world");
// /// assert_eq!(org.to_org(), "* [#A] world");
// /// let hdl = org.document().first_headline().unwrap();
// /// org.set_title(hdl, "world!");
// /// assert_eq!(org.to_org(), "* [#A] world!");
// /// ```
// pub fn set_title(&mut self, headline: Headline, title: &str) -> Option<HeadlineTitle> {
// let bytes = title.as_bytes();
// let title = match memchr(b'\n', bytes) {
// Some(i) if i > 0 && bytes[i] == b'\r' => &title[0..i - 1],
// Some(i) => &title[0..i],
// _ => title,
// };
// let new_title = node(HEADLINE_TITLE, object_nodes(self.create_input(title)));
// if let Some(title) = headline.title() {
// self.green = title.syntax.replace_with(new_title.into_node().unwrap());
// return Some(title);
// }
// let mut child: Vec<_> = headline
// .syntax
// .green()
// .children()
// .map(|ch| ch.to_owned())
// .collect();
// let index = support::child
// .iter()
// .enumerate()
// .filter_map(|(idx, it)| {
// if it.kind() == HEADLINE_STARS.into()
// || it.kind() == HEADLINE_KEYWORD.into()
// || it.kind() == HEADLINE_PRIORITY.into()
// {
// Some(idx + 1)
// } else {
// None
// }
// })
// .last()
// .unwrap_or_default();
// if index == child.len() {
// child.push(token(WHITESPACE, " "));
// child.push(new_title);
// } else if child[index].kind() != WHITESPACE.into() {
// child.insert(index, token(WHITESPACE, " "));
// child.insert(index + 1, new_title);
// } else {
// child.insert(index, new_title);
// }
// self.green = headline
// .syntax
// .replace_with(node(HEADLINE, child).into_node().unwrap());
// None
// }
// /// set the section of this document or headline
// ///
// /// ```rust
// /// use orgize::Org;
// ///
// /// let mut org = Org::parse("* hello");
// ///
// /// let hdl = org.document().first_headline().unwrap();
// /// org.set_section(hdl, "world");
// /// assert_eq!(org.to_org(), "* hello\nworld\n");
// ///
// /// let hdl = org.document().first_headline().unwrap();
// /// org.set_section(hdl, "world!");
// /// assert_eq!(org.to_org(), "* hello\nworld!\n");
// ///
// /// let doc = org.document();
// /// org.set_section(doc, "doc");
// /// assert_eq!(org.to_org(), "doc\n* hello\nworld!\n");
// /// ```
// pub fn set_section(
// &mut self,
// document_or_headline: impl Into<DocumentOrHeadline>,
// section: &str,
// ) -> Option<Section> {
// let document_or_headline = document_or_headline.into();
// let section = section_text(self.create_input(section)).ok()?.1.as_str();
// let section = if section.ends_with('\n') {
// section_node(self.create_input(section)).map(|(_, s)| s)
// } else {
// section_node(self.create_input(&format!("{section}\n"))).map(|(_, s)| s)
// }
// .ok()?;
// if let Some(old) = document_or_headline.section() {
// self.green = old.syntax.replace_with(section.into_node().unwrap());
// return Some(old);
// }
// match document_or_headline {
// DocumentOrHeadline::Document(document) => {
// let mut child: Vec<_> = document
// .syntax
// .green()
// .children()
// .map(|ch| ch.to_owned())
// .collect();
// let headline_idx = child.iter().position(|it| it.kind() == HEADLINE.into());
// if let Some(idx) = headline_idx {
// child.insert(idx, section);
// } else {
// child.push(section);
// }
// self.green = document
// .syntax
// .replace_with(GreenNode::new(DOCUMENT.into(), child));
// None
// }
// DocumentOrHeadline::Headline(headline) => {
// let mut child: Vec<_> = headline
// .syntax
// .green()
// .children()
// .map(|ch| ch.to_owned())
// .collect();
// let new_line_idx = support::child
// .iter()
// .position(|it| it.kind() == NEW_LINE.into());
// if let Some(idx) = new_line_idx {
// // add section *after* newline
// if idx < support::child.len() {
// support::child.insert(idx, section);
// } else {
// support::child.push(section);
// }
// } else {
// support::child.push(token(NEW_LINE, "\n"));
// support::child.push(section);
// }
// self.green = headline
// .syntax
// .replace_with(GreenNode::new(HEADLINE.into(), support::child));
// None
// }
// }
// }
// /// set the level of this headline
// ///
// /// ```rust
// /// use orgize::Org;
// ///
// /// let mut org = Org::parse("** 1\n** 2");
// ///
// /// let hdl = org.document().last_headline().unwrap();
// /// org.set_level(hdl, 1);
// /// assert_eq!(org.to_org(), "** 1\n* 2");
// ///
// /// let hdl = org.document().last_headline().unwrap();
// /// org.set_level(hdl, 3);
// /// assert_eq!(org.to_org(), "** 1\n* 2");
// /// ```
// pub fn set_level(&mut self, headline: Headline, level: usize) {
// if level == 0 {
// return;
// }
// let min_level_in_siblings = headline
// .syntax
// .siblings(rowan::Direction::Next)
// .chain(headline.syntax.siblings(rowan::Direction::Prev))
// .filter_map(Headline::cast)
// .filter_map(|headline| headline.level())
// .min()
// .unwrap_or(1);
// if level <= min_level_in_siblings {
// if let Some(stars) = headline.stars() {
// self.green = stars.replace_with(GreenToken::new(
// SyntaxKind::HEADLINE_STARS.into(),
// "*".repeat(level).as_str(),
// ));
// }
// }
// }
// }