| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | |
|
| 6 | | use crate::graph::AtomKey; |
| 7 | | use crate::ids::NodeId; |
| 8 | | use crate::objects::{AtomRange, AtomRef, Edit}; |
| 9 | | use similar::DiffOp; |
| 10 | | use std::collections::HashMap; |
| 11 | |
|
| 12 | |
|
| 13 | |
|
| 14 | 4087× | pub fn split_lines(b: &[u8]) -> Vec<&[u8]> { |
| 15 | 4087× | let mut out = Vec::new(); |
| 16 | 4087× | let mut start = 0; |
| 17 | 633073× | for (i, &c) in b.iter().enumerate() { |
| 18 | 633073× | if c == b'\n' { |
| 19 | 74499× | out.push(&b[start..=i]); |
| 20 | 74499× | start = i + 1; |
| 21 | 558574× | } |
| 22 | | } |
| 23 | 4087× | if start < b.len() { |
| 24 | 18× | out.push(&b[start..]); |
| 25 | 4069× | } |
| 26 | 4087× | out |
| 27 | 4087× | } |
| 28 | |
|
| 29 | |
|
| 30 | 2245× | pub fn ranges(keys: &[AtomKey]) -> Vec<AtomRange> { |
| 31 | 2245× | let mut out: Vec<AtomRange> = Vec::new(); |
| 32 | 17941× | for &(c, i) in keys { |
| 33 | 17941× | match out.last_mut() { |
| 34 | 15696× | Some(r) if r.change == c && r.to == i => r.to = i + 1, |
| 35 | 7533× | _ => out.push(AtomRange { |
| 36 | 7533× | change: c, |
| 37 | 7533× | from: i, |
| 38 | 7533× | to: i + 1, |
| 39 | 7533× | }), |
| 40 | | } |
| 41 | | } |
| 42 | 2245× | out |
| 43 | 2245× | } |
| 44 | |
|
| 45 | |
|
| 46 | |
|
| 47 | |
|
| 48 | |
|
| 49 | |
|
| 50 | |
|
| 51 | 2946× | fn kept_lines<'a>(old: &[&'a [u8]], new: &[&'a [u8]]) -> Vec<(usize, usize)> { |
| 52 | 2946× | let mut ids: HashMap<&'a [u8], u32> = HashMap::new(); |
| 53 | 102852× | let mut intern = |l: &'a [u8]| -> u32 { |
| 54 | 102852× | let next = ids.len() as u32; |
| 55 | 102852× | *ids.entry(l).or_insert(next) |
| 56 | 102852× | }; |
| 57 | 50971× | let o: Vec<u32> = old.iter().map(|l| intern(l)).collect(); |
| 58 | 51881× | let n: Vec<u32> = new.iter().map(|l| intern(l)).collect(); |
| 59 | 2946× | let mut in_old = vec![false; ids.len()]; |
| 60 | 2946× | let mut in_new = vec![false; ids.len()]; |
| 61 | 50971× | for &x in &o { |
| 62 | 50971× | in_old[x as usize] = true; |
| 63 | 50971× | } |
| 64 | 51881× | for &x in &n { |
| 65 | 51881× | in_new[x as usize] = true; |
| 66 | 51881× | } |
| 67 | 50971× | let oi: Vec<usize> = (0..o.len()).filter(|&i| in_new[o[i] as usize]).collect(); |
| 68 | 51881× | let ni: Vec<usize> = (0..n.len()).filter(|&j| in_old[n[j] as usize]).collect(); |
| 69 | 45376× | let os: Vec<u32> = oi.iter().map(|&i| o[i]).collect(); |
| 70 | 45353× | let ns: Vec<u32> = ni.iter().map(|&j| n[j]).collect(); |
| 71 | 2946× | let mut capture = similar::algorithms::Capture::new(); |
| 72 | 2946× | let _ = similar::algorithms::myers::diff(&mut capture, &os, 0..os.len(), &ns, 0..ns.len()); |
| 73 | 2946× | let mut kept = Vec::new(); |
| 74 | 25448× | for op in capture.into_ops() { |
| 75 | | if let DiffOp::Equal { |
| 76 | 9765× | old_index, |
| 77 | 9765× | new_index, |
| 78 | 9765× | len, |
| 79 | 25448× | } = op |
| 80 | | { |
| 81 | 33128× | kept.extend((0..len).map(|k| (oi[old_index + k], ni[new_index + k]))); |
| 82 | 15683× | } |
| 83 | | } |
| 84 | 2946× | kept |
| 85 | 2946× | } |
| 86 | |
|
| 87 | |
|
| 88 | 2050× | pub fn text_edits(node: NodeId, old: &[(AtomKey, Vec<u8>)], new: &[u8]) -> Vec<Edit> { |
| 89 | 2050× | let keys: Vec<AtomKey> = old.iter().map(|(k, _)| *k).collect(); |
| 90 | 29199× | let old_lines: Vec<&[u8]> = old.iter().map(|(_, l)| l.as_slice()).collect(); |
| 91 | 2050× | text_edits_of(node, &keys, &old_lines, new) |
| 92 | 2050× | } |
| 93 | |
|
| 94 | |
|
| 95 | |
|
| 96 | 2946× | pub fn text_edits_of(node: NodeId, keys: &[AtomKey], old_lines: &[&[u8]], new: &[u8]) -> Vec<Edit> { |
| 97 | 2946× | let new_lines = split_lines(new); |
| 98 | |
|
| 99 | 7890× | let anchor = |i: usize| -> (AtomRef, AtomRef) { |
| 100 | 7890× | let after = if i == 0 { |
| 101 | 1277× | AtomRef::Start |
| 102 | | } else { |
| 103 | 6613× | AtomRef::Atom { |
| 104 | 6613× | change: keys[i - 1].0, |
| 105 | 6613× | index: keys[i - 1].1, |
| 106 | 6613× | } |
| 107 | | }; |
| 108 | 7890× | let before = if i >= keys.len() { |
| 109 | 1053× | AtomRef::End |
| 110 | | } else { |
| 111 | 6837× | AtomRef::Atom { |
| 112 | 6837× | change: keys[i].0, |
| 113 | 6837× | index: keys[i].1, |
| 114 | 6837× | } |
| 115 | | }; |
| 116 | 7890× | (after, before) |
| 117 | 7890× | }; |
| 118 | |
|
| 119 | | |
| 120 | | |
| 121 | 2946× | let mut deleted: Vec<AtomKey> = Vec::new(); |
| 122 | 2946× | let mut inserts: Vec<Edit> = Vec::new(); |
| 123 | 2946× | let (mut i, mut j) = (0, 0); |
| 124 | 2946× | let end = (old_lines.len(), new_lines.len()); |
| 125 | 36074× | for (ki, kj) in kept_lines(old_lines, &new_lines).into_iter().chain([end]) { |
| 126 | 36074× | deleted.extend_from_slice(&keys[i..ki]); |
| 127 | 36074× | if kj > j { |
| 128 | 7890× | let (after, before) = anchor(i); |
| 129 | 7890× | inserts.push(Edit::Insert { |
| 130 | 7890× | node, |
| 131 | 7890× | after, |
| 132 | 7890× | before, |
| 133 | 7890× | lines: new_lines[j..kj].iter().copied().collect(), |
| 134 | 7890× | }); |
| 135 | 28184× | } |
| 136 | 36074× | (i, j) = (ki + 1, kj + 1); |
| 137 | | } |
| 138 | 2946× | let mut edits = Vec::new(); |
| 139 | 2946× | if !deleted.is_empty() { |
| 140 | 2197× | edits.push(Edit::Delete { |
| 141 | 2197× | node, |
| 142 | 2197× | atoms: ranges(&deleted), |
| 143 | 2197× | }); |
| 144 | 2197× | } |
| 145 | 2946× | edits.extend(inserts); |
| 146 | 2946× | edits |
| 147 | 2946× | } |
| 148 | |
|
| 149 | | #[cfg(test)] |
| 150 | | mod tests { |
| 151 | | use super::*; |
| 152 | | use crate::hash::ObjectRef; |
| 153 | |
|
| 154 | 29106× | fn h(n: u8) -> ObjectRef { |
| 155 | 29106× | ObjectRef([n; 32]) |
| 156 | 29106× | } |
| 157 | |
|
| 158 | | |
| 159 | | |
| 160 | | |
| 161 | | #[test] |
| 162 | 1× | fn edits_replay_to_the_new_text_on_repetitive_files() { |
| 163 | | use rand::{Rng, SeedableRng}; |
| 164 | 1× | let words: [&[u8]; 5] = [b"}\n", b" }\n", b"\n", b"a\n", b" b\n"]; |
| 165 | 1× | let mut rng = rand::rngs::StdRng::seed_from_u64(11); |
| 166 | 1× | for _ in 0..2000 { |
| 167 | 2000× | let old: Vec<Vec<u8>> = (0..rng.gen_range(0..30)) |
| 168 | 29092× | .map(|_| words[rng.gen_range(0..5)].to_vec()) |
| 169 | 2000× | .collect(); |
| 170 | 2000× | let new: Vec<u8> = (0..rng.gen_range(0..30)) |
| 171 | 29543× | .flat_map(|_| words[rng.gen_range(0..5)].to_vec()) |
| 172 | 2000× | .collect(); |
| 173 | 2000× | let keyed: Vec<(AtomKey, Vec<u8>)> = old |
| 174 | 2000× | .iter() |
| 175 | 2000× | .enumerate() |
| 176 | 29092× | .map(|(i, l)| ((h(1), i as u32), l.clone())) |
| 177 | 2000× | .collect(); |
| 178 | 2000× | let edits = text_edits(NodeId::ROOT, &keyed, &new); |
| 179 | 2000× | let mut dead = vec![false; old.len()]; |
| 180 | 2000× | let mut after: std::collections::BTreeMap<i64, Vec<u8>> = Default::default(); |
| 181 | 8219× | for e in &edits { |
| 182 | 8219× | match e { |
| 183 | 1725× | Edit::Delete { atoms, .. } => atoms |
| 184 | 1725× | .iter() |
| 185 | 16820× | .for_each(|r| (r.from..r.to).for_each(|i| dead[i as usize] = true)), |
| 186 | | Edit::Insert { |
| 187 | 6494× | after: a, |
| 188 | 6494× | before: b, |
| 189 | 6494× | lines, |
| 190 | | .. |
| 191 | | } => { |
| 192 | 6494× | let at = match a { |
| 193 | 1053× | AtomRef::Start => -1, |
| 194 | 5441× | AtomRef::Atom { index, .. } => *index as i64, |
| 195 | 0× | AtomRef::End => unreachable!(), |
| 196 | | }; |
| 197 | 6494× | let next = match b { |
| 198 | 640× | AtomRef::End => old.len() as i64, |
| 199 | 5854× | AtomRef::Atom { index, .. } => *index as i64, |
| 200 | 0× | AtomRef::Start => unreachable!(), |
| 201 | | }; |
| 202 | 6494× | assert_eq!(next, at + 1, "anchors adjacent"); |
| 203 | 6494× | after.entry(at).or_default().extend(lines.iter().flatten()); |
| 204 | | } |
| 205 | 0× | _ => {} |
| 206 | | } |
| 207 | | } |
| 208 | 2000× | let mut out = after.get(&-1).cloned().unwrap_or_default(); |
| 209 | 29092× | for (i, l) in old.iter().enumerate() { |
| 210 | 29092× | if !dead[i] { |
| 211 | 12272× | out.extend(l); |
| 212 | 16820× | } |
| 213 | 29092× | out.extend(after.get(&(i as i64)).cloned().unwrap_or_default()); |
| 214 | | } |
| 215 | 2000× | assert_eq!( |
| 216 | 2000× | String::from_utf8_lossy(&out), |
| 217 | 2000× | String::from_utf8_lossy(&new), |
| 218 | | "old {old:?}" |
| 219 | | ); |
| 220 | | } |
| 221 | 1× | } |
| 222 | |
|
| 223 | | #[test] |
| 224 | 1× | fn split_keeps_terminators() { |
| 225 | 1× | assert_eq!(split_lines(b"a\nb\n"), vec![&b"a\n"[..], &b"b\n"[..]]); |
| 226 | 1× | assert_eq!(split_lines(b"a\nb"), vec![&b"a\n"[..], &b"b"[..]]); |
| 227 | 1× | assert_eq!(split_lines(b""), Vec::<&[u8]>::new()); |
| 228 | 1× | assert_eq!(split_lines(b"\n\n"), vec![&b"\n"[..], &b"\n"[..]]); |
| 229 | 1× | } |
| 230 | |
|
| 231 | | #[test] |
| 232 | 1× | fn ranges_coalesce() { |
| 233 | 1× | let r = ranges(&[(h(1), 0), (h(1), 1), (h(1), 3), (h(2), 0)]); |
| 234 | 1× | assert_eq!( |
| 235 | | r, |
| 236 | 1× | vec![ |
| 237 | 1× | AtomRange { |
| 238 | 1× | change: h(1), |
| 239 | 1× | from: 0, |
| 240 | 1× | to: 2 |
| 241 | 1× | }, |
| 242 | 1× | AtomRange { |
| 243 | 1× | change: h(1), |
| 244 | 1× | from: 3, |
| 245 | 1× | to: 4 |
| 246 | 1× | }, |
| 247 | 1× | AtomRange { |
| 248 | 1× | change: h(2), |
| 249 | 1× | from: 0, |
| 250 | 1× | to: 1 |
| 251 | 1× | } |
| 252 | | ] |
| 253 | | ); |
| 254 | 1× | } |
| 255 | |
|
| 256 | | #[test] |
| 257 | 1× | fn edits_for_typical_changes() { |
| 258 | 1× | let node = NodeId::random(); |
| 259 | 1× | let old: Vec<(AtomKey, Vec<u8>)> = ["a\n", "b\n", "c\n"] |
| 260 | 1× | .iter() |
| 261 | 1× | .enumerate() |
| 262 | 3× | .map(|(i, l)| ((h(1), i as u32), l.as_bytes().to_vec())) |
| 263 | 1× | .collect(); |
| 264 | | |
| 265 | 1× | assert!(text_edits(node, &old, b"a\nb\nc\n").is_empty()); |
| 266 | | |
| 267 | 1× | let e = text_edits(node, &old, b"a\nB\nc\n"); |
| 268 | 1× | assert_eq!( |
| 269 | | e, |
| 270 | 1× | vec![ |
| 271 | 1× | Edit::Delete { |
| 272 | 1× | node, |
| 273 | 1× | atoms: vec![AtomRange { |
| 274 | 1× | change: h(1), |
| 275 | 1× | from: 1, |
| 276 | 1× | to: 2 |
| 277 | 1× | }] |
| 278 | 1× | }, |
| 279 | 1× | Edit::Insert { |
| 280 | 1× | node, |
| 281 | 1× | after: AtomRef::Atom { |
| 282 | 1× | change: h(1), |
| 283 | 1× | index: 0 |
| 284 | 1× | }, |
| 285 | 1× | before: AtomRef::Atom { |
| 286 | 1× | change: h(1), |
| 287 | 1× | index: 1 |
| 288 | 1× | }, |
| 289 | 1× | lines: vec![b"B\n".to_vec()].into() |
| 290 | 1× | }, |
| 291 | | ] |
| 292 | | ); |
| 293 | | |
| 294 | 1× | let e = text_edits(node, &old, b"top\na\nb\nc\nend\n"); |
| 295 | 1× | assert_eq!(e.len(), 2); |
| 296 | 1× | assert!(matches!( |
| 297 | 1× | &e[0], |
| 298 | | Edit::Insert { |
| 299 | | after: AtomRef::Start, |
| 300 | | before: AtomRef::Atom { index: 0, .. }, |
| 301 | | .. |
| 302 | | } |
| 303 | | )); |
| 304 | 1× | assert!(matches!( |
| 305 | 1× | &e[1], |
| 306 | | Edit::Insert { |
| 307 | | after: AtomRef::Atom { index: 2, .. }, |
| 308 | | before: AtomRef::End, |
| 309 | | .. |
| 310 | | } |
| 311 | | )); |
| 312 | | |
| 313 | 1× | let e = text_edits(node, &old, b""); |
| 314 | 1× | assert_eq!( |
| 315 | | e, |
| 316 | 1× | vec![Edit::Delete { |
| 317 | 1× | node, |
| 318 | 1× | atoms: vec![AtomRange { |
| 319 | 1× | change: h(1), |
| 320 | 1× | from: 0, |
| 321 | 1× | to: 3 |
| 322 | 1× | }] |
| 323 | 1× | }] |
| 324 | | ); |
| 325 | | |
| 326 | 1× | let e = text_edits(node, &[], b"x\n"); |
| 327 | 1× | assert_eq!( |
| 328 | | e, |
| 329 | 1× | vec![Edit::Insert { |
| 330 | 1× | node, |
| 331 | 1× | after: AtomRef::Start, |
| 332 | 1× | before: AtomRef::End, |
| 333 | 1× | lines: vec![b"x\n".to_vec()].into() |
| 334 | 1× | }] |
| 335 | | ); |
| 336 | 1× | } |
| 337 | | } |