use crate::edit::series::lex::SyntaxKind;
use crate::edit::series::lossless::{SeriesEntry, SeriesFile, SeriesLang};
use rowan::{ast::AstNode, GreenNodeBuilder, NodeOrToken};
impl SeriesFile {
pub fn len(&self) -> usize {
self.patch_entries().count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn push(
&mut self,
name: impl AsRef<str>,
options: impl IntoIterator<Item = impl AsRef<str>>,
) {
let patch_count = self.len();
self.insert(patch_count, name, options);
}
pub fn prepend(
&mut self,
name: impl AsRef<str>,
options: impl IntoIterator<Item = impl AsRef<str>>,
) {
self.insert(0, name, options);
}
pub fn insert(
&mut self,
index: usize,
name: impl AsRef<str>,
options: impl IntoIterator<Item = impl AsRef<str>>,
) {
let name = name.as_ref();
let options: Vec<String> = options
.into_iter()
.map(|s| s.as_ref().to_string())
.collect();
let new_entry_green = Self::build_patch_entry_green(name, &options);
let new_entry_syntax = rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_entry_green);
let new_element = NodeOrToken::Node(new_entry_syntax);
let mut patch_count = 0;
let mut insertion_index = 0;
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = &element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if entry.as_patch_entry().is_some() {
if patch_count == index {
insertion_index = i;
break;
}
patch_count += 1;
}
}
}
insertion_index = i + 1;
}
self.syntax()
.splice_children(insertion_index..insertion_index, vec![new_element]);
}
pub fn remove(&mut self, name: &str) -> bool {
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if let Some(patch) = entry.as_patch_entry() {
if patch.name().as_deref() == Some(name) {
self.syntax().splice_children(i..i + 1, vec![]);
return true;
}
}
}
}
}
false
}
pub fn set_options(
&mut self,
name: &str,
options: impl IntoIterator<Item = impl AsRef<str>>,
) -> bool {
let new_options: Vec<String> = options
.into_iter()
.map(|s| s.as_ref().to_string())
.collect();
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if let Some(patch) = entry.as_patch_entry() {
if patch.name().as_deref() == Some(name) {
let new_entry_green = Self::build_patch_entry_green(name, &new_options);
let new_entry_syntax =
rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_entry_green);
let new_element = NodeOrToken::Node(new_entry_syntax);
self.syntax().splice_children(i..i + 1, vec![new_element]);
return true;
}
}
}
}
}
false
}
pub fn add_comment(&mut self, text: impl AsRef<str>) {
let text = text.as_ref();
let new_comment_green = Self::build_comment_entry_green(text);
let new_comment_syntax = rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_comment_green);
let new_element = NodeOrToken::Node(new_comment_syntax);
let end_index = self.syntax().children_with_tokens().count();
self.syntax()
.splice_children(end_index..end_index, vec![new_element]);
}
pub fn rename(&mut self, old_name: &str, new_name: &str) -> bool {
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if let Some(patch) = entry.as_patch_entry() {
if patch.name().as_deref() == Some(old_name) {
let options = patch.option_strings();
let new_entry_green = Self::build_patch_entry_green(new_name, &options);
let new_entry_syntax =
rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_entry_green);
let new_element = NodeOrToken::Node(new_entry_syntax);
self.syntax().splice_children(i..i + 1, vec![new_element]);
return true;
}
}
}
}
}
false
}
pub fn move_to(&mut self, name: &str, new_index: usize) -> bool {
let mut patch_entry = None;
let mut patch_options = Vec::new();
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = &element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if let Some(patch) = entry.as_patch_entry() {
if patch.name().as_deref() == Some(name) {
patch_options = patch.option_strings();
patch_entry = Some(i);
break;
}
}
}
}
}
if let Some(old_index) = patch_entry {
self.syntax()
.splice_children(old_index..old_index + 1, vec![]);
let mut patch_count = 0;
let mut insertion_index = 0;
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = &element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if entry.as_patch_entry().is_some() {
if patch_count == new_index {
insertion_index = i;
break;
}
patch_count += 1;
}
}
}
insertion_index = i + 1;
}
let new_entry_green = Self::build_patch_entry_green(name, &patch_options);
let new_entry_syntax = rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_entry_green);
let new_element = NodeOrToken::Node(new_entry_syntax);
self.syntax()
.splice_children(insertion_index..insertion_index, vec![new_element]);
true
} else {
false
}
}
pub fn insert_comment(&mut self, index: usize, text: impl AsRef<str>) {
let text = text.as_ref();
let new_comment_green = Self::build_comment_entry_green(text);
let new_comment_syntax = rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_comment_green);
let new_element = NodeOrToken::Node(new_comment_syntax);
let mut entry_count = 0;
let mut insertion_index = 0;
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = &element {
if let Some(_) = SeriesEntry::cast(node.clone()) {
if entry_count == index {
insertion_index = i;
break;
}
entry_count += 1;
}
}
insertion_index = i + 1;
}
self.syntax()
.splice_children(insertion_index..insertion_index, vec![new_element]);
}
pub fn clear(&mut self) {
let mut indices_to_remove = Vec::new();
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if entry.as_patch_entry().is_some() {
indices_to_remove.push(i);
}
}
}
}
for &i in indices_to_remove.iter().rev() {
self.syntax().splice_children(i..i + 1, vec![]);
}
}
pub fn contains(&self, name: &str) -> bool {
self.patch_entries()
.any(|patch| patch.name().as_deref() == Some(name))
}
pub fn position(&self, name: &str) -> Option<usize> {
self.patch_entries()
.position(|patch| patch.name().as_deref() == Some(name))
}
pub fn update_all<F>(&mut self, mut updates: F)
where
F: FnMut(&str, Vec<String>) -> Option<Vec<String>>,
{
let mut modifications = Vec::new();
for (i, element) in self.syntax().children_with_tokens().enumerate() {
if let NodeOrToken::Node(node) = element {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if let Some(patch) = entry.as_patch_entry() {
if let Some(name) = patch.name() {
let current_options = patch.option_strings();
if let Some(new_options) = updates(&name, current_options) {
let new_entry_green =
Self::build_patch_entry_green(&name, &new_options);
let new_entry_syntax =
rowan::SyntaxNode::<SeriesLang>::new_root_mut(new_entry_green);
modifications.push((i, NodeOrToken::Node(new_entry_syntax)));
}
}
}
}
}
}
for (index, new_element) in modifications.into_iter().rev() {
self.syntax()
.splice_children(index..index + 1, vec![new_element]);
}
}
pub fn reorder(&mut self, new_order: &[String]) -> bool {
let mut patch_elements = Vec::new();
let mut non_patch_positions = Vec::new();
for (i, element) in self.syntax().children_with_tokens().enumerate() {
match &element {
NodeOrToken::Node(node) => {
if let Some(entry) = SeriesEntry::cast(node.clone()) {
if let Some(patch) = entry.as_patch_entry() {
if let Some(name) = patch.name() {
patch_elements.push((name, element.clone()));
continue;
}
}
}
non_patch_positions.push(i);
}
NodeOrToken::Token(_) => {
non_patch_positions.push(i);
}
}
}
for name in new_order {
if !patch_elements.iter().any(|(n, _)| n == name) {
return false;
}
}
let total_elements = self.syntax().children_with_tokens().count();
self.syntax().splice_children(0..total_elements, vec![]);
let mut patch_iter = new_order.iter();
let mut next_patch_name = patch_iter.next();
let mut used_patches = std::collections::HashSet::new();
for original_pos in 0..total_elements {
if non_patch_positions.contains(&original_pos) {
continue;
} else {
if let Some(patch_name) = next_patch_name {
if let Some((_, element)) = patch_elements.iter().find(|(n, _)| n == patch_name)
{
self.syntax().splice_children(
self.syntax().children_with_tokens().count()
..self.syntax().children_with_tokens().count(),
vec![element.clone()],
);
used_patches.insert(patch_name.clone());
next_patch_name = patch_iter.next();
}
}
}
}
true
}
fn build_patch_entry_green(name: &str, options: &[String]) -> rowan::GreenNode {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SERIES_ENTRY.into());
builder.start_node(SyntaxKind::PATCH_ENTRY.into());
builder.token(SyntaxKind::PATCH_NAME.into(), name);
if !options.is_empty() {
builder.token(SyntaxKind::SPACE.into(), " ");
builder.start_node(SyntaxKind::OPTIONS.into());
for (i, option) in options.iter().enumerate() {
if i > 0 {
builder.token(SyntaxKind::SPACE.into(), " ");
}
builder.start_node(SyntaxKind::OPTION_ITEM.into());
builder.token(SyntaxKind::OPTION.into(), option);
builder.finish_node(); }
builder.finish_node(); }
builder.token(SyntaxKind::NEWLINE.into(), "\n");
builder.finish_node(); builder.finish_node();
builder.finish()
}
fn build_comment_entry_green(text: &str) -> rowan::GreenNode {
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::SERIES_ENTRY.into());
builder.start_node(SyntaxKind::COMMENT_LINE.into());
builder.token(SyntaxKind::HASH.into(), "#");
if !text.is_empty() {
builder.token(SyntaxKind::SPACE.into(), " ");
builder.token(SyntaxKind::TEXT.into(), text);
}
builder.token(SyntaxKind::NEWLINE.into(), "\n");
builder.finish_node(); builder.finish_node();
builder.finish()
}
}
#[cfg(test)]
mod tests {
use crate::edit::series;
#[test]
fn test_insert() {
let text = "patch1.patch\npatch2.patch\n";
let parsed = series::parse(text);
let mut series = parsed.quilt_tree_mut();
series.insert(1, "new.patch", ["-p1"]);
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 3);
assert_eq!(patches[0].name(), Some("patch1.patch".to_string()));
assert_eq!(patches[1].name(), Some("new.patch".to_string()));
assert_eq!(patches[2].name(), Some("patch2.patch".to_string()));
}
#[test]
fn test_remove_patch() {
let text = "patch1.patch\npatch2.patch\npatch3.patch\n";
let parsed = series::parse(text);
let mut series = parsed.quilt_tree_mut();
assert!(series.remove("patch2.patch"));
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 2);
assert_eq!(patches[0].name(), Some("patch1.patch".to_string()));
assert_eq!(patches[1].name(), Some("patch3.patch".to_string()));
}
#[test]
fn test_collection_api() {
let text = "patch1.patch\npatch2.patch\n";
let parsed = series::parse(text);
let mut series = parsed.quilt_tree_mut();
assert_eq!(series.len(), 2);
assert!(!series.is_empty());
assert!(series.contains("patch1.patch"));
assert_eq!(series.position("patch2.patch"), Some(1));
series.push("patch3.patch", ["-p1", "--reverse"]);
series.prepend("patch0.patch", std::iter::empty::<&str>());
series.add_comment("Test comment");
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 4);
assert_eq!(patches[0].name(), Some("patch0.patch".to_string()));
assert_eq!(patches[3].name(), Some("patch3.patch".to_string()));
series.clear();
assert!(series.is_empty());
let comments: Vec<_> = series.comment_lines().collect();
assert_eq!(comments.len(), 1);
}
}