orgize/src/org.rs
2024-04-01 11:41:40 +08:00

106 lines
3.1 KiB
Rust

use rowan::ast::AstNode;
use rowan::{GreenNode, TextSize};
use crate::ast::Document;
use crate::config::ParseConfig;
use crate::export::{HtmlExport, TraversalContext, Traverser};
use crate::syntax::{OrgLanguage, SyntaxNode};
use crate::SyntaxElement;
#[derive(Debug)]
pub struct Org {
pub(crate) green: GreenNode,
pub(crate) config: ParseConfig,
}
impl Org {
/// Parse input string to Org element tree using default parse config
pub fn parse(input: impl AsRef<str>) -> Org {
ParseConfig::default().parse(input)
}
pub fn green(&self) -> &GreenNode {
&self.green
}
pub fn config(&self) -> &ParseConfig {
&self.config
}
/// Returns the document
pub fn document(&self) -> Document {
Document {
syntax: SyntaxNode::new_root(self.green.clone()),
}
}
/// Returns org-mode string
pub fn to_org(&self) -> String {
self.green.to_string()
}
/// Convert org element tree to html-format using default html handler
pub fn to_html(&self) -> String {
let mut handler = HtmlExport::default();
self.traverse(&mut handler);
handler.finish()
}
/// Walk through org element tree using given traverser
pub fn traverse<T: Traverser>(&self, t: &mut T) {
let mut ctx = TraversalContext::default();
t.element(
SyntaxElement::Node(SyntaxNode::new_root(self.green.clone())),
&mut ctx,
);
}
/// Returns the first node in org element tree in depth first order
pub fn first_node<N: AstNode<Language = OrgLanguage>>(&self) -> Option<N> {
fn find<N: AstNode<Language = OrgLanguage>>(node: SyntaxNode) -> Option<N> {
if N::can_cast(node.kind()) {
N::cast(node)
} else {
node.children().find_map(find)
}
}
find(SyntaxNode::new_root(self.green.clone()))
}
/// Returns node in given offset
///
/// ```rust
/// use orgize::{Org, ast::Headline};
///
/// let org = Org::parse("\n\n* foo\n* bar");
///
/// assert!(org.node_at_offset::<Headline>(0).is_none());
///
/// let hdl = org.node_at_offset::<Headline>(2).unwrap();
/// assert_eq!(hdl.title_raw(), "foo");
///
/// let hdl = org.node_at_offset::<Headline>(9).unwrap();
/// assert_eq!(hdl.title_raw(), "bar");
///
/// assert!(org.node_at_offset::<Headline>(999).is_none());
/// ```
pub fn node_at_offset<N: AstNode<Language = OrgLanguage>>(
&self,
offset: impl Into<TextSize>,
) -> Option<N> {
let offset = offset.into();
fn find<N: AstNode<Language = OrgLanguage>>(
node: SyntaxNode,
offset: TextSize,
) -> Option<N> {
if !node.text_range().contains(offset) {
None
} else if N::can_cast(node.kind()) {
N::cast(node)
} else {
node.children().find_map(|node| find(node, offset))
}
}
find(SyntaxNode::new_root(self.green.clone()), offset)
}
}