Skip to main content

radicle_tui/ui/
utils.rs

1use std::collections::HashMap;
2
3/// A type that specifies the location of a line merge.
4/// The lines to merge will be either added before, within or after
5/// the base lines.
6#[derive(Default, Clone, Debug, Hash, Eq, PartialEq)]
7pub enum MergeLocation {
8    Start,
9    Line(usize),
10    End,
11    #[default]
12    Unknown,
13}
14
15/// A type that can merge lines based on their merge location.
16#[derive(Default)]
17pub struct LineMerger<T> {
18    /// Base lines that other lines will be merged with.
19    lines: Vec<T>,
20}
21
22impl<T: Clone> LineMerger<T> {
23    pub fn new(lines: impl IntoIterator<Item = T>) -> Self {
24        Self {
25            lines: lines.into_iter().collect::<Vec<_>>(),
26        }
27    }
28
29    pub fn merge(
30        &self,
31        merge: HashMap<MergeLocation, Vec<Vec<T>>>,
32        start: Option<usize>,
33    ) -> Vec<T> {
34        let mut merged = vec![];
35        for (idx, line) in self.lines.iter().enumerate() {
36            let location = if idx == 0 {
37                MergeLocation::Start
38            } else if idx == self.lines.len().saturating_sub(1) {
39                MergeLocation::End
40            } else {
41                let idx = idx
42                    .saturating_add(start.unwrap_or_default())
43                    .saturating_sub(1);
44                MergeLocation::Line(idx)
45            };
46
47            if location != MergeLocation::Start {
48                merged.push(line.clone());
49            }
50
51            merged.extend(
52                merge
53                    .get(&location)
54                    .into_iter()
55                    .flatten()
56                    .flat_map(|merge| merge.iter())
57                    .cloned(),
58            );
59
60            if location == MergeLocation::Start {
61                merged.push(line.clone());
62            }
63        }
64
65        merged
66    }
67}
68
69#[cfg(test)]
70mod test {
71    use std::collections::HashMap;
72
73    use pretty_assertions::assert_eq;
74
75    use crate::ui::utils::{LineMerger, MergeLocation};
76
77    #[test]
78    fn lines_should_be_merged_correctly() -> anyhow::Result<()> {
79        let diff = r#"
80fn main() {
81    println!("Hello, world!");
82
83    another_function();
84}
85
86fn another_function() {
87    println!("Another function.");
88}"#;
89
90        let comment = r#"──────────────────────────────────────
91Is this needed?
92──────────────────────────────────────"#
93            .to_string();
94        let comment = comment.lines().collect::<Vec<_>>();
95
96        let merged = LineMerger::new(diff.lines()).merge(
97            HashMap::from([(MergeLocation::Line(2), vec![comment])]),
98            Some(1),
99        );
100        let actual = merged.join("\n");
101
102        let expected = r#"
103fn main() {
104    println!("Hello, world!");
105──────────────────────────────────────
106Is this needed?
107──────────────────────────────────────
108
109    another_function();
110}
111
112fn another_function() {
113    println!("Another function.");
114}"#;
115
116        let expected = expected.to_string();
117
118        assert_eq!(expected, actual);
119
120        Ok(())
121    }
122
123    #[test]
124    fn lines_with_start_should_be_merged_correctly() -> anyhow::Result<()> {
125        let diff = r#"
126fn main() {
127    println!("Hello, world!");
128
129    another_function();
130}
131
132fn another_function() {
133    println!("Another function.");
134}"#;
135
136        let comment = r#"──────────────────────────────────────
137Is this needed?
138──────────────────────────────────────"#
139            .to_string();
140        let comment = comment.lines().collect::<Vec<_>>();
141
142        let merged = LineMerger::new(diff.lines()).merge(
143            HashMap::from([(MergeLocation::Line(103), vec![comment])]),
144            Some(100),
145        );
146        let actual = merged.join("\n");
147
148        let expected = r#"
149fn main() {
150    println!("Hello, world!");
151
152    another_function();
153──────────────────────────────────────
154Is this needed?
155──────────────────────────────────────
156}
157
158fn another_function() {
159    println!("Another function.");
160}"#;
161
162        let expected = expected.to_string();
163
164        assert_eq!(expected, actual);
165
166        Ok(())
167    }
168}