diff options
Diffstat (limited to 'src/changes.rs')
-rw-r--r-- | src/changes.rs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/src/changes.rs b/src/changes.rs new file mode 100644 index 0000000..5099364 --- /dev/null +++ b/src/changes.rs @@ -0,0 +1,176 @@ +use rowan::NodeOrToken; +use crate::pipeline::Change; + +pub fn apply_changes(content: &str, mut changes: Vec<Change>, debug: bool) -> String { + let mut last_pos = 0; + let mut ncontent = String::new(); + + changes.sort_by_key(|change| change.node.text_range().start()); + + for change in changes { + use crate::queries::Operator::*; + match change.kind { + Remove => { + let (before, after) = remove_node(&change.node); + if last_pos > before { continue } + ncontent += &content[last_pos..before]; + if debug { + println!("removing: {}", &content[before..after]) + } + last_pos = after; + } + Keep => { + let (before, after) = surrounding_noise(&change.node); + let (before, after) = eat_whitespace(&before, &after); + // let (before, after) = (change.node.text_range().start().into(), change.node.text_range().end().into()); + let (before, after) = (before.text_range().start().into(), after.text_range().end().into()); + if debug { + println!("keeping: {}", &content[before..after]) + }; + ncontent += &content[before..after]; + // last_pos = after; + } + } + } + + ncontent += &content[last_pos..]; + + ncontent +} + + + +// TODO: does rnix really not have an alias for this? +type NixNodeOrToken = NodeOrToken<rowan::SyntaxNode<rnix::NixLanguage>, rowan::SyntaxToken<rnix::NixLanguage>>; + +/// eat all tokens of the given kind (backwards or forwards) +fn eat_kind(node: &NixNodeOrToken, kind: rnix::SyntaxKind, backwards: bool) -> NixNodeOrToken { + let mut span = node.clone(); + + loop { + span = match if backwards { span.prev_sibling_or_token() } else { span.next_sibling_or_token() } { + Some(NodeOrToken::Token(t)) + if t.kind() == kind + => NodeOrToken::Token(t), + _ => break + }; + } + span +} + + +/// eat whitespace outwards both ways +fn eat_whitespace(begin: &NixNodeOrToken, end: &NixNodeOrToken) + -> (NixNodeOrToken, NixNodeOrToken) { + let begin = eat_kind(begin, rnix::SyntaxKind::TOKEN_WHITESPACE, true); + let end = eat_kind(end, rnix::SyntaxKind::TOKEN_WHITESPACE, false); + (begin, end) +} + +/// for a node, get all the "syntactic noise" which surrounds it +/// (i.e. brackets, whitespace, etc.) which should be removed if +/// the node itself is removed, except the final bit of whitespace +/// potentially separating it from neighbouring nodes, which should +/// be handled separately to keep formatting nice +fn surrounding_noise(node: &rnix::SyntaxNode) -> (NixNodeOrToken, NixNodeOrToken) { + + fn eat_matching_parens(begin: &NixNodeOrToken, end: &NixNodeOrToken) + -> Option<(NixNodeOrToken, NixNodeOrToken)> { + match (begin.prev_sibling_or_token(), end.next_sibling_or_token()) { + (Some(begin), Some(end)) + if begin.kind() == rnix::SyntaxKind::TOKEN_L_PAREN + && end.kind() == rnix::SyntaxKind::TOKEN_R_PAREN + => Some((begin, end)), + _ => None + } + } + + let (mut begin, mut end): (NixNodeOrToken, NixNodeOrToken) = + (NodeOrToken::Node(node.clone()), NodeOrToken::Node(node.clone())); + + loop { + let (a, b) = eat_whitespace(&begin, &end); + // println!("loop: {begin}, {end}"); + // println!("loop: {a}, {b}"); + // println!("{:?}", (begin.prev_sibling_or_token(), end.next_sibling_or_token())); + if let Some(res) = eat_matching_parens(&a, &b) { + // println!("ate parens!"); + (begin, end) = res; + } else { + break + } + } + + (begin, end) +} + + +fn remove_node(node: &rnix::SyntaxNode) -> (usize, usize) { + let (before, after) = surrounding_noise(node); + + let (mut prev, /*mut*/ next) = + eat_whitespace(&before, &after); + + // println!("{prev:?}, {next:?}"); + if let Some(t) = prev.as_token().map(|t| t.prev_token()).flatten() { + if t.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + prev = NodeOrToken::Token(t); + } + }; + // if let Some(t) = next.as_token().map(|t| t.next_token()).flatten() { + // if t.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + // next = NodeOrToken::Token(t); + // } + // }; + // println!("{prev:?}, {next:?}"); + + + if prev.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE + && next.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + // println!("inside if!"); + if prev.to_string().lines().count() < next.to_string().lines().count() { + let start: usize = (node.text_range().start() - prev.text_range().len()).into(); + let end: usize = node.text_range().end().into(); + (start + previous_indentation(&node).unwrap_or(0) + , end + next_indentation(&node).unwrap_or(0)) + } else { + let start: usize = node.text_range().start().into(); + let end: usize = (node.text_range().end() + next.text_range().len()).into(); + (start, end) + // (start - previous_indentation(&node).unwrap_or(0), + // end - next_indentation(&node).unwrap_or(0)) + } + } + else { + (prev.text_range().start().into(), next.text_range().end().into()) + } +} + +fn indentation_of_string(string: &str) -> Option<usize> { + if string.contains("\n") { + None + } else { + Some(string.lines().last().unwrap().len()) + } +} + +fn previous_indentation(node: &rnix::SyntaxNode) -> Option<usize> { + let whitespace_token = node.prev_sibling_or_token()?; + + if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + indentation_of_string(&whitespace_token.to_string()) + } else { + None + } +} + +fn next_indentation(node: &rnix::SyntaxNode) -> Option<usize> { + let whitespace_token = node.next_sibling_or_token()?; + + if whitespace_token.kind() == rnix::SyntaxKind::TOKEN_WHITESPACE { + indentation_of_string(&whitespace_token.to_string()) + // Some(whitespace_token.to_string().lines().last().unwrap().len()) + } else { + None + } +} |