use crate::syntax::{SyntaxKind, SyntaxNode};
pub fn command_name(command: &SyntaxNode) -> Option<String> {
command
.children_with_tokens()
.filter_map(|element| element.into_token())
.find(|token| token.kind() == SyntaxKind::CONTROL_WORD)
.map(|token| token.text().trim_start_matches('\\').to_string())
}
pub fn nth_group_text(command: &SyntaxNode, n: usize) -> Option<String> {
let group = command
.children()
.filter(|child| child.kind() == SyntaxKind::GROUP)
.nth(n)?;
let mut text = String::new();
for element in group.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(token) => match token.kind() {
SyntaxKind::L_BRACE | SyntaxKind::R_BRACE => {}
_ => text.push_str(token.text()),
},
rowan::NodeOrToken::Node(_) => return None,
}
}
Some(text)
}
pub fn nth_group(command: &SyntaxNode, n: usize) -> Option<SyntaxNode> {
command
.children()
.filter(|child| child.kind() == SyntaxKind::GROUP)
.nth(n)
}
pub fn group_command_name(group: &SyntaxNode) -> Option<String> {
let command = group
.children()
.find(|child| child.kind() == SyntaxKind::COMMAND)?;
command_name(&command)
}
pub fn group_inner_source(group: &SyntaxNode) -> String {
let mut text = String::new();
for element in group.descendants_with_tokens() {
if let rowan::NodeOrToken::Token(token) = element {
text.push_str(token.text());
}
}
let inner = text.strip_prefix('{').unwrap_or(&text);
inner.strip_suffix('}').unwrap_or(inner).to_string()
}
pub fn environment_name(begin_or_end: &SyntaxNode) -> Option<String> {
let group = begin_or_end
.children()
.find(|child| child.kind() == SyntaxKind::NAME_GROUP)?;
let mut text = String::new();
for element in group.children_with_tokens() {
match element {
rowan::NodeOrToken::Token(token) => match token.kind() {
SyntaxKind::L_BRACE | SyntaxKind::R_BRACE => {}
_ => text.push_str(token.text()),
},
rowan::NodeOrToken::Node(_) => return None,
}
}
Some(text)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse;
fn command(src: &str) -> SyntaxNode {
SyntaxNode::new_root(parse(src).green)
.descendants()
.find(|node| node.kind() == SyntaxKind::COMMAND)
.expect("a COMMAND node")
}
#[test]
fn command_name_strips_backslash() {
assert_eq!(
command_name(&command("\\section{Hi}\n")).as_deref(),
Some("section")
);
}
#[test]
fn nth_group_text_reassembles_inner_tokens() {
assert_eq!(
nth_group_text(&command("\\label{sec:intro}\n"), 0).as_deref(),
Some("sec:intro")
);
}
#[test]
fn nth_group_text_none_for_nested_command() {
assert_eq!(nth_group_text(&command("\\input{\\jobname}\n"), 0), None);
}
#[test]
fn nth_group_text_none_when_group_absent() {
assert_eq!(nth_group_text(&command("\\input\n"), 0), None);
}
#[test]
fn group_command_name_reads_braced_control_word() {
let cmd = command("\\newcommand{\\foo}{x}\n");
let name = nth_group(&cmd, 0).and_then(|g| group_command_name(&g));
assert_eq!(name.as_deref(), Some("foo"));
}
#[test]
fn group_command_name_none_for_plain_text() {
let cmd = command("\\newenvironment{thm}{a}{b}\n");
let name = nth_group(&cmd, 0).and_then(|g| group_command_name(&g));
assert_eq!(name, None);
}
#[test]
fn group_inner_source_keeps_nested_braces() {
let cmd = command("\\NewDocumentCommand{\\foo}{m O{d} m}{x}\n");
let spec = nth_group(&cmd, 1).map(|g| group_inner_source(&g));
assert_eq!(spec.as_deref(), Some("m O{d} m"));
assert_eq!(nth_group_text(&cmd, 1), None);
}
}