mod name;
mod option;
mod separator;
mod value;
use std::fmt;
use itertools::Itertools;
use ra_ap_syntax::{ast, AstNode, AstToken, SyntaxElement, TextRange, TextSize};
use crate::closest_ancestor_ast_type;
pub use name::MetaName;
pub use option::MetaOption;
pub use separator::MetaSeparator;
pub use value::MetaValue;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetaNameValue {
name: MetaOption<MetaName>,
eq: Option<MetaSeparator>,
value: MetaOption<MetaValue>,
nested: Option<Box<MetaNameValue>>,
start: TextSize,
end: Option<TextSize>,
parent_tt: Option<ast::TokenTree>,
}
impl MetaNameValue {
pub fn new(
name: MetaOption<MetaName>,
eq: Option<MetaSeparator>,
value: MetaOption<MetaValue>,
nested: Option<Box<MetaNameValue>>,
start: TextSize,
) -> Self {
assert!(name.is_some() || eq.is_some() || value.is_some() || nested.is_some());
Self {
name,
eq,
value,
nested,
start,
end: None,
parent_tt: None,
}
}
pub fn empty(start: TextSize, end: Option<TextSize>, parent_tt: ast::TokenTree) -> Self {
assert!(parent_tt.syntax().text_range().contains_inclusive(start));
if let Some(ref end) = end {
assert!(parent_tt.syntax().text_range().contains_inclusive(*end));
}
Self {
name: MetaOption::None,
eq: None,
value: MetaOption::None,
nested: None,
start,
end,
parent_tt: Some(parent_tt),
}
}
pub fn is_empty(&self) -> bool {
self.name.is_none() && self.eq.is_none() && self.value.is_none() && self.nested.is_none()
}
pub fn name(&self) -> &MetaOption<MetaName> {
&self.name
}
pub fn eq(&self) -> Option<&MetaSeparator> {
self.eq.as_ref()
}
pub fn value(&self) -> &MetaOption<MetaValue> {
&self.value
}
pub fn nested(&self) -> Option<&MetaNameValue> {
self.nested.as_ref().map(Box::as_ref)
}
pub fn text_range(&self) -> TextRange {
let mut start: Option<TextSize> = None;
let mut end: Option<TextSize> = None;
let mut update_start_and_end = |range: TextRange| {
if start.is_none() {
start = Some(range.start());
}
end = Some(range.end());
};
let get_items_text_range = |items: &[SyntaxElement]| -> Option<TextRange> {
Some(TextRange::new(
items.first()?.text_range().start(),
items.last()?.text_range().end(),
))
};
match &self.name {
MetaOption::Ok(meta_name) => {
update_start_and_end(meta_name.syntax().text_range());
}
MetaOption::Err(items) => {
if let Some(range) = get_items_text_range(items) {
update_start_and_end(range);
}
}
MetaOption::None => (),
}
if let Some(token) = &self.eq {
update_start_and_end(token.syntax().text_range());
}
if let Some(nested_meta) = &self.nested {
update_start_and_end(nested_meta.text_range());
}
match &self.value {
MetaOption::Ok(meta_value) => {
update_start_and_end(meta_value.text_range());
}
MetaOption::Err(items) => {
if let Some(range) = get_items_text_range(items) {
update_start_and_end(range);
}
}
MetaOption::None => (),
}
TextRange::new(
start.unwrap_or(self.start),
end.or(self.end).unwrap_or(self.start),
)
}
pub fn parent_attr(&self) -> Option<ast::Attr> {
self.elements()
.next()
.as_ref()
.and_then(closest_ancestor_ast_type)
.or_else(|| {
self.parent_tt
.as_ref()
.map(ast::TokenTree::syntax)
.and_then(closest_ancestor_ast_type)
})
}
pub fn elements(&self) -> impl Iterator<Item = SyntaxElement> {
let mut elems = Vec::new();
match &self.name {
MetaOption::Ok(name) => elems.push(SyntaxElement::from(name.syntax().clone())),
MetaOption::Err(name_elems) => elems.extend(name_elems.iter().cloned()),
MetaOption::None => (),
};
if let Some(eq) = &self.eq {
elems.push(SyntaxElement::from(eq.syntax().clone()));
}
match &self.value {
MetaOption::Ok(name) => elems.extend(name.elements().iter().cloned()),
MetaOption::Err(value_elems) => elems.extend(value_elems.iter().cloned()),
MetaOption::None => (),
}
if let Some(nested) = &self.nested {
elems.extend(nested.elements());
}
elems
.into_iter()
.sorted_by_key(|elem| elem.text_range().start())
}
}
impl fmt::Display for MetaNameValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}{}",
self.name,
match &self.nested {
Some(nested_meta) => {
format!("({nested_meta})")
}
None => "".to_owned(),
},
match self.eq {
Some(_) => " = ",
None => "",
},
self.value
)
}
}