imara_diff/
postprocess.rs

1use crate::intern::{InternedInput, Token};
2use crate::slider_heuristic::SliderHeuristic;
3use crate::util::{find_hunk_end, find_hunk_start};
4use crate::{Diff, Hunk};
5
6impl Diff {
7    pub fn postprocess_with(
8        &mut self,
9        before: &[Token],
10        after: &[Token],
11        mut heuristic: impl SliderHeuristic,
12    ) {
13        Postprocessor {
14            added: &mut self.added,
15            removed: &mut self.removed,
16            tokens: after,
17            hunk: Hunk {
18                before: 0..0,
19                after: 0..0,
20            },
21            heuristic: &mut heuristic,
22        }
23        .run();
24        Postprocessor {
25            added: &mut self.removed,
26            removed: &mut self.added,
27            tokens: before,
28            hunk: Hunk {
29                before: 0..0,
30                after: 0..0,
31            },
32            heuristic: &mut heuristic,
33        }
34        .run()
35    }
36
37    pub fn postprocess_with_heuristic<T>(
38        &mut self,
39        input: &InternedInput<T>,
40        heuristic: impl SliderHeuristic,
41    ) {
42        self.postprocess_with(&input.before, &input.after, heuristic);
43    }
44}
45
46struct Postprocessor<'a, H> {
47    added: &'a mut [bool],
48    removed: &'a [bool],
49    tokens: &'a [Token],
50    // the current hunk in the iteration
51    hunk: Hunk,
52    heuristic: &'a mut H,
53}
54
55impl<H: SliderHeuristic> Postprocessor<'_, H> {
56    fn run(mut self) {
57        loop {
58            // find a hunk
59            if !self.hunk.next_hunk(self.removed, self.added) {
60                break;
61            }
62
63            let mut earliest_end;
64            let mut is_modification;
65            loop {
66                // move hunk up as far as possible to possibly merge it with other hunks
67                // and discover wether there are other possible positions
68                while self.slide_up() {}
69                earliest_end = self.hunk.after.end;
70                is_modification = self.hunk.before.start != self.hunk.before.end;
71
72                let hunk_size_unexpanded = self.hunk.after.len();
73                // move hunk down as far as possible (and merge with other hunks it if
74                // possible) sliding down is often the most preferred position
75                while self.slide_down() {
76                    is_modification |= self.hunk.before.start != self.hunk.before.end;
77                }
78                // if this hunk was merged with another hunk while sliding down we might
79                // be able to slide up more otherwise we are done
80                if hunk_size_unexpanded == self.hunk.after.len() {
81                    break;
82                }
83            }
84
85            if self.hunk.after.end == earliest_end {
86                continue;
87            }
88            if is_modification {
89                // hunk can be moved and there is a removed hunk in the same region
90                // move the hunk so it align with the other hunk to produce a single
91                // MODIFIED hunk instead of two seperate ADDED/REMOVED hunks
92                while self.hunk.before.start == self.hunk.before.end {
93                    let success = self.slide_up();
94                    debug_assert!(success);
95                }
96            } else {
97                let slider_end = self.heuristic.best_slider_end(
98                    self.tokens,
99                    self.hunk.after.clone(),
100                    earliest_end,
101                );
102                for _ in slider_end..self.hunk.after.end {
103                    let success = self.slide_up();
104                    debug_assert!(success);
105                }
106            }
107        }
108    }
109
110    /// slide up a hunk by one token/line, potenitally merging it with a subsequent hunk
111    fn slide_down(&mut self) -> bool {
112        let Some(&next_token) = self.tokens.get(self.hunk.after.end as usize) else {
113            return false;
114        };
115        if self.tokens[self.hunk.after.start as usize] != next_token {
116            return false;
117        }
118        self.added[self.hunk.after.start as usize] = false;
119        self.added[self.hunk.after.end as usize] = true;
120        self.hunk.after.start += 1;
121        self.hunk.after.end = find_hunk_end(self.added, self.hunk.after.end);
122        // move the end of the remove range one down to keep the unchanged lines aligned
123        self.hunk.before.start = self.hunk.before.end + 1;
124        self.hunk.before.end = find_hunk_end(self.removed, self.hunk.before.start);
125        true
126    }
127
128    /// slide up a hunk by one token/line, potenitally merging it with a previous hunk
129    fn slide_up(&mut self) -> bool {
130        if self.hunk.after.start == 0 {
131            return false;
132        }
133        if self.tokens[self.hunk.after.start as usize - 1]
134            != self.tokens[self.hunk.after.end as usize - 1]
135        {
136            return false;
137        }
138        self.added[self.hunk.after.start as usize - 1] = true;
139        self.added[self.hunk.after.end as usize - 1] = false;
140        self.hunk.after.end -= 1;
141        self.hunk.after.start = find_hunk_start(self.added, self.hunk.after.start - 1);
142        // move the start of the remove range one up to keep the unchanged lines aligned
143        self.hunk.before.end = self.hunk.before.start - 1;
144        self.hunk.before.start = find_hunk_start(self.removed, self.hunk.before.start - 1);
145        true
146    }
147}