Skip to main content

diff_match_patch/
patch.rs

1// Patch construction, splitting, padding, apply, and the patch text wire
2// format (Patch::to_string / patch_to_text / patch_from_text).
3
4use crate::delta::encode_uri;
5use crate::engine;
6use crate::types::{max, min, Diff, Dmp, Patch};
7use core::char;
8use percent_encoding::percent_decode;
9use std::fmt;
10
11// The historic public API takes &Vec/&mut Vec/&String; frozen by the
12// drop-in compatibility contract.
13#[allow(clippy::ptr_arg)]
14impl Dmp {
15    /// Increase the context until it is unique,
16    /// but don't let the pattern expand beyond Match_MaxBits.
17    ///
18    /// Args:
19    /// patch: The patch to grow.
20    /// text: Source text.
21    pub fn patch_add_context(&mut self, patch: &mut Patch, text: &mut Vec<char>) {
22        if text.is_empty() {
23            return;
24        }
25        let mut pattern: Vec<char> =
26            text[patch.start2 as usize..(patch.length1 as usize + patch.start2 as usize)].to_vec();
27        let mut padding: i32 = 0;
28
29        // Grow the pattern while it is ambiguous (occurs more than once) in
30        // text. A pattern is always a slice of text, so it occurs at least
31        // once; the empty pattern (insertion with no margin yet) mirrors the
32        // oracle's first-index != last-index check: unique only in 1-char text.
33        let ambiguous = |text: &[char], pattern: &[char]| {
34            if pattern.is_empty() {
35                text.len() != 1
36            } else {
37                engine::occurs_twice(text, pattern)
38            }
39        };
40        let mut rst = 0;
41        while ambiguous(text, &pattern)
42            && (pattern.len() as i32) < (self.match_maxbits - self.patch_margin * 2)
43        {
44            padding += self.patch_margin;
45            pattern = text[max(0, patch.start2 - padding) as usize
46                ..min(text.len() as i32, patch.start2 + patch.length1 + padding) as usize]
47                .to_vec();
48            rst += 1;
49            if rst > 5 {
50                break;
51            }
52        }
53        // Add one chunk for good luck.
54        padding += self.patch_margin;
55
56        // Add the prefix.
57        let prefix: String = text[max(0, patch.start2 - padding) as usize..patch.start2 as usize]
58            .iter()
59            .collect();
60        let prefix_length = prefix.chars().count() as i32;
61        if !prefix.is_empty() {
62            patch.diffs.insert(0, Diff::new(0, prefix.clone()));
63        }
64
65        // Add the suffix.
66        let suffix: String = text[(patch.start2 + patch.length1) as usize
67            ..min(text.len() as i32, patch.start2 + patch.length1 + padding) as usize]
68            .iter()
69            .collect();
70        let suffix_length = suffix.chars().count() as i32;
71        if !suffix.is_empty() {
72            patch.diffs.push(Diff::new(0, suffix));
73        }
74        // Roll back the start points.
75        patch.start1 -= prefix_length;
76        patch.start2 -= prefix_length;
77        // Extend lengths.
78        patch.length1 += prefix_length + suffix_length;
79        patch.length2 += prefix_length + suffix_length;
80    }
81
82    /// Compute a list of patches to turn text1 into text2.
83    /// compute diffs.
84    /// Args:
85    /// text1: First string.
86    /// text2: Second string.
87    /// Returns:
88    /// Vector of Patch objects.
89    pub fn patch_make1(&mut self, text1: &str, text2: &str) -> Vec<Patch> {
90        let mut diffs: Vec<Diff> = self.diff_main(text1, text2, true);
91        if diffs.len() > 2 {
92            self.diff_cleanup_semantic(&mut diffs);
93            self.diff_cleanup_efficiency(&mut diffs);
94        }
95        self.patch_make4(text1, &mut diffs)
96    }
97
98    /// Compute a list of patches to turn text1 into text2.
99    /// Use diffs to compute first text.
100    ///
101    /// Args:
102    /// diffs: Vector od diff object.
103    /// Returns:
104    /// Vector of Patch objects.
105    pub fn patch_make2(&mut self, diffs: &mut Vec<Diff>) -> Vec<Patch> {
106        let text1 = self.diff_text1(diffs);
107        self.patch_make4(text1.as_str(), diffs)
108    }
109
110    /// Compute a list of patches to turn text1 into text2.
111    ///
112    /// Args:
113    /// text1: First string.
114    /// text2: Second string.
115    /// diffs: Vector of diff.
116    ///
117    /// Returns:
118    /// Vector of Patch objects.
119    pub fn patch_make3(&mut self, text1: &str, _text2: &str, diffs: &mut Vec<Diff>) -> Vec<Patch> {
120        self.patch_make4(text1, diffs)
121    }
122    /// Compute a list of patches to turn text1 into text2.
123    ///
124    /// Args:
125    /// text1: First string.
126    /// diffs: Vector of diff object.
127    /// Returns:
128    /// Array of Patch objects.
129    pub fn patch_make4(&mut self, text1: &str, diffs: &mut Vec<Diff>) -> Vec<Patch> {
130        let mut patches: Vec<Patch> = vec![];
131        if diffs.is_empty() {
132            return patches; // Get rid of the None case.
133        }
134        let mut patch: Patch = Patch::new(vec![], 0, 0, 0, 0);
135        let mut char_count1 = 0; // Number of characters into the text1 string.
136        let mut char_count2 = 0; // Number of characters into the text2 string.
137        let mut prepatch: Vec<char> = text1.chars().collect(); // Recreate the patches to determine context info.
138        let mut postpatch: Vec<char> = prepatch.clone();
139        for i in 0..diffs.len() {
140            let temp1: &Vec<char> = &(diffs[i].text.chars().collect());
141            if patch.diffs.is_empty() && diffs[i].operation != 0 {
142                // A new patch starts here.
143                patch.start1 = char_count1;
144                patch.start2 = char_count2;
145            }
146            if diffs[i].operation == 1 {
147                // Insertion
148                patch
149                    .diffs
150                    .push(Diff::new(diffs[i].operation, diffs[i].text.clone()));
151                patch.length2 += temp1.len() as i32;
152                postpatch.splice(
153                    char_count2 as usize..char_count2 as usize,
154                    temp1.iter().copied(),
155                );
156            } else if diffs[i].operation == -1 {
157                // Deletion.
158                patch
159                    .diffs
160                    .push(Diff::new(diffs[i].operation, diffs[i].text.clone()));
161                patch.length1 += temp1.len() as i32;
162                postpatch.splice(
163                    char_count2 as usize..char_count2 as usize + temp1.len(),
164                    std::iter::empty(),
165                );
166            } else if temp1.len() as i32 <= self.patch_margin * 2
167                && !patch.diffs.is_empty()
168                && i != diffs.len() - 1
169            {
170                // Small equality inside a patch.
171                patch
172                    .diffs
173                    .push(Diff::new(diffs[i].operation, diffs[i].text.clone()));
174                patch.length1 += temp1.len() as i32;
175                patch.length2 += temp1.len() as i32;
176            } else if temp1.len() as i32 >= 2 * self.patch_margin {
177                // Time for a new patch.
178                if !patch.diffs.is_empty() {
179                    self.patch_add_context(&mut patch, &mut prepatch);
180                    patches.push(patch);
181                    patch = Patch::new(vec![], 0, 0, 0, 0);
182                    prepatch = postpatch.clone();
183                    char_count1 = char_count2;
184                }
185            }
186
187            // Update the current character count.
188            if diffs[i].operation != 1 {
189                char_count1 += temp1.len() as i32;
190            }
191            if diffs[i].operation != -1 {
192                char_count2 += temp1.len() as i32;
193            }
194        }
195
196        // Pick up the leftover patch if not empty.
197        if !patch.diffs.is_empty() {
198            self.patch_add_context(&mut patch, &mut prepatch);
199            // println!("{:?}", prepatch);
200            patches.push(patch);
201        }
202        patches
203    }
204
205    /// Given an Vector of patches, return another Vector that is identical.
206    ///
207    /// Args:
208    /// patches: Vector of Patch objects.
209    ///
210    /// Returns:
211    /// Vector of Patch objects.
212    pub fn patch_deep_copy(&mut self, patches: &mut Vec<Patch>) -> Vec<Patch> {
213        let mut patches_copy: Vec<Patch> = vec![];
214        for patches_item in patches {
215            let mut patch_copy = Patch::new(vec![], 0, 0, 0, 0);
216            for j in 0..patches_item.diffs.len() {
217                let diff_copy = Diff::new(
218                    patches_item.diffs[j].operation,
219                    patches_item.diffs[j].text.clone(),
220                );
221                patch_copy.diffs.push(diff_copy);
222            }
223            patch_copy.start1 = patches_item.start1;
224            patch_copy.start2 = patches_item.start2;
225            patch_copy.length1 = patches_item.length1;
226            patch_copy.length2 = patches_item.length2;
227            patches_copy.push(patch_copy);
228        }
229        patches_copy
230    }
231
232    pub fn patch_apply(
233        &mut self,
234        patches: &mut Vec<Patch>,
235        source_text: &str,
236    ) -> (Vec<char>, Vec<bool>) {
237        /*
238          Merge a set of patches onto the text.  Return a patched text, as well
239          as a list of true/false values indicating which patches were applied.
240
241          Args:
242              patches: Vector of Patch objects.
243              text: Old text.
244
245          Returns:
246              Two element Vector, containing the new chars and an Vector of boolean values.
247        */
248
249        if patches.is_empty() {
250            return (source_text.chars().collect(), vec![]);
251        }
252
253        // Deep copy the patches so that no changes are made to originals.
254        let mut patches_copy: Vec<Patch> = self.patch_deep_copy(patches);
255
256        let null_padding: Vec<char> = self.patch_add_padding(&mut patches_copy);
257
258        let mut text = null_padding.clone();
259        text.extend(source_text.chars());
260        text.extend(&null_padding);
261
262        self.patch_splitmax(&mut patches_copy);
263
264        // delta keeps track of the offset between the expected and actual location
265        // of the previous patch.  If there are patches expected at positions 10 and
266        // 20, but the first patch was found at 12, delta is 2 and the second patch
267        // has an effective expected position of 22.
268        let mut delta: i32 = 0;
269        let mut results: Vec<bool> = vec![false; patches_copy.len()];
270        for x in 0..patches_copy.len() {
271            let expected_loc: i32 = patches_copy[x].start2 + delta;
272            let text1: Vec<char> = diff_text1_chars(&patches_copy[x].diffs);
273            let mut start_loc: i32;
274            let mut end_loc = -1;
275            if text1.len() as i32 > self.match_maxbits {
276                // patch_splitMax will only provide an oversized pattern in the case of
277                // a monster delete.
278                start_loc = crate::match_::match_chars(
279                    self,
280                    &text,
281                    &text1[..self.match_maxbits as usize],
282                    expected_loc,
283                );
284                if start_loc != -1 {
285                    end_loc = crate::match_::match_chars(
286                        self,
287                        &text,
288                        &text1[text1.len() - self.match_maxbits as usize..],
289                        expected_loc + text1.len() as i32 - self.match_maxbits,
290                    );
291                    if end_loc == -1 || start_loc >= end_loc {
292                        // Can't find valid trailing context.  Drop this patch.
293                        start_loc = -1;
294                    }
295                }
296            } else {
297                start_loc = crate::match_::match_chars(self, &text, &text1, expected_loc);
298            }
299            if start_loc == -1 {
300                // No match found.  :(
301                results[x] = false;
302                // Subtract the delta for this failed patch from subsequent patches.
303                delta -= patches_copy[x].length2 - patches_copy[x].length1;
304            } else {
305                // Found a match.  :)
306                results[x] = true;
307                delta = start_loc - expected_loc;
308
309                let mut end_index: usize;
310                if end_loc == -1 {
311                    end_index = start_loc as usize + text1.len();
312                } else {
313                    end_index = (end_loc + self.match_maxbits) as usize;
314                }
315                end_index = std::cmp::min(text.len(), end_index);
316
317                if text1[..] == text[start_loc as usize..end_index] {
318                    // Perfect match, just splice the replacement text in.
319                    let replacement = diff_text2_chars(&patches_copy[x].diffs);
320                    text.splice(
321                        start_loc as usize..start_loc as usize + text1.len(),
322                        replacement,
323                    );
324                } else {
325                    // Imperfect match.
326                    // Run a diff to get a framework of equivalent indices.
327                    let mut diffs: Vec<Diff> = crate::diff::diff_main_chars(
328                        self,
329                        &text1,
330                        &text[start_loc as usize..end_index],
331                        false,
332                    );
333                    if text1.len() as i32 > self.match_maxbits
334                        && (self.diff_levenshtein(&diffs) as f32 / (text1.len() as f32)
335                            > self.patch_delete_threshold)
336                    {
337                        // The end points match, but the content is unacceptably bad.
338                        results[x] = false;
339                    } else {
340                        self.diff_cleanup_semantic_lossless(&mut diffs);
341                        let mut index1: i32 = 0;
342                        for y in 0..patches_copy[x].diffs.len() {
343                            let op = patches_copy[x].diffs[y].operation;
344                            let mod_len = patches_copy[x].diffs[y].text.chars().count() as i32;
345                            if op != 0 {
346                                let index2: i32 = self.diff_xindex(&diffs, index1);
347                                if op == 1 {
348                                    // Insertion
349                                    let at = (start_loc + index2) as usize;
350                                    text.splice(at..at, patches_copy[x].diffs[y].text.chars());
351                                } else if op == -1 {
352                                    // Deletion. diff_xindex is non-decreasing
353                                    // in loc, so the range never inverts.
354                                    let from = (start_loc + index2) as usize;
355                                    let until = (start_loc
356                                        + self.diff_xindex(&diffs, index1 + mod_len))
357                                        as usize;
358                                    text.splice(from..until, std::iter::empty());
359                                }
360                            }
361                            if op != -1 {
362                                index1 += mod_len;
363                            }
364                        }
365                    }
366                }
367            }
368        }
369        // Strip the padding off.
370        text.drain(..null_padding.len());
371        text.truncate(text.len() - null_padding.len());
372        (text, results)
373    }
374
375    /// Add some padding on text start and end so that edges can match
376    /// something.  Intended to be called only from within patch_apply.
377    ///
378    /// Args:
379    /// patches: Array of Patch objects.
380    ///
381    /// Returns:
382    /// The padding chars added to each side.
383    pub fn patch_add_padding(&mut self, patches: &mut Vec<Patch>) -> Vec<char> {
384        let padding_length = self.patch_margin;
385        let mut nullpadding: Vec<char> = vec![];
386        for i in 0..padding_length {
387            if let Some(ch) = char::from_u32(1 + i as u32) {
388                nullpadding.push(ch);
389            }
390        }
391
392        // Bump all the patches forward.
393        for patch in patches.iter_mut() {
394            patch.start1 += padding_length;
395            patch.start2 += padding_length;
396        }
397        let mut patch = patches[0].clone();
398        let mut diffs = patch.diffs;
399        if diffs.is_empty() || diffs[0].operation != 0 {
400            // Add nullPadding equality.
401            diffs.insert(0, Diff::new(0, nullpadding.clone().iter().collect()));
402            patch.start1 -= padding_length; // Should be 0.
403            patch.start2 -= padding_length; // Should be 0.
404            patch.length1 += padding_length;
405            patch.length2 += padding_length;
406        } else {
407            let text_len = diffs[0].text.chars().count() as i32;
408            if padding_length > text_len {
409                // Grow first equality.
410                let extra_length = padding_length - text_len;
411                let mut new_text: String = nullpadding[text_len as usize..].iter().collect();
412                new_text += diffs[0].text.as_str();
413                diffs[0] = Diff::new(diffs[0].operation, new_text);
414                patch.start1 -= extra_length;
415                patch.start2 -= extra_length;
416                patch.length1 += extra_length;
417                patch.length2 += extra_length;
418            }
419        }
420
421        // Add some padding on end of last diff.
422        patch.diffs = diffs;
423        patches[0] = patch;
424        patch = patches[patches.len() - 1].clone();
425        diffs = patch.diffs;
426        if diffs.is_empty() || diffs[diffs.len() - 1].operation != 0 {
427            // Add nullPadding equality.
428            diffs.push(Diff::new(0, nullpadding.clone().iter().collect()));
429            patch.length1 += padding_length;
430            patch.length2 += padding_length;
431        } else {
432            let text_len = diffs[diffs.len() - 1].text.chars().count() as i32;
433            if padding_length > text_len {
434                // Grow last equality.
435                let extra_length = padding_length - text_len;
436                let mut new_text: String = nullpadding[..extra_length as usize].iter().collect();
437                let diffs_len = diffs.len();
438                new_text = diffs[diffs_len - 1].text.clone() + new_text.as_str();
439                diffs[diffs_len - 1] = Diff::new(diffs[diffs_len - 1].operation, new_text);
440                patch.length1 += extra_length;
441                patch.length2 += extra_length;
442            }
443        }
444        patch.diffs = diffs;
445        let patches_len = patches.len();
446        patches[patches_len - 1] = patch;
447        nullpadding
448    }
449
450    /// Look through the patches and break up any which are longer than the
451    /// maximum limit of the match algorithm.
452    /// Intended to be called only from within patch_apply.
453    ///
454    /// Args:
455    /// patches: Array of Patch objects.
456    pub fn patch_splitmax(&mut self, patches: &mut Vec<Patch>) {
457        let patch_size = self.match_maxbits;
458        if patch_size == 0 {
459            return;
460        }
461        let mut x: i32 = 0;
462        while (x as usize) < patches.len() {
463            if patches[x as usize].length1 <= patch_size {
464                x += 1;
465                continue;
466            }
467            // Remove the big old patch.
468            let mut bigpatch = patches.remove(x as usize);
469            x -= 1;
470            let mut start1 = bigpatch.start1;
471            let mut start2 = bigpatch.start2;
472            let mut precontext: Vec<char> = vec![];
473            while !bigpatch.diffs.is_empty() {
474                // Create one of several smaller patches.
475                let mut patch = Patch::new(vec![], 0, 0, 0, 0);
476                let mut empty = true;
477                patch.start1 = start1 - precontext.len() as i32;
478                patch.start2 = start2 - precontext.len() as i32;
479                if !precontext.is_empty() {
480                    patch.length1 = precontext.len() as i32;
481                    patch.length2 = precontext.len() as i32;
482                    patch
483                        .diffs
484                        .push(Diff::new(0, precontext.clone().iter().collect()));
485                }
486                while !bigpatch.diffs.is_empty() && patch.length1 < patch_size - self.patch_margin {
487                    let diff_type = bigpatch.diffs[0].operation;
488                    let mut diff_text: Vec<char> = bigpatch.diffs[0].text.chars().collect();
489                    if diff_type == 1 {
490                        // Insertions are harmless.
491                        patch.length2 += diff_text.len() as i32;
492                        start2 += diff_text.len() as i32;
493                        patch.diffs.push(bigpatch.diffs[0].clone());
494                        bigpatch.diffs.remove(0);
495                        empty = false;
496                    } else if diff_type == -1
497                        && patch.diffs.len() == 1
498                        && patch.diffs[0].operation == 0
499                        && (diff_text.len() as i32) > 2 * patch_size
500                    {
501                        // This is a large deletion.  Let it pass in one chunk.
502                        patch.length1 += diff_text.len() as i32;
503                        start1 += diff_text.len() as i32;
504                        empty = false;
505                        patch
506                            .diffs
507                            .push(Diff::new(diff_type, diff_text.iter().collect()));
508                        bigpatch.diffs.remove(0);
509                    } else {
510                        // Deletion or equality.  Only take as much as we can stomach.
511                        let diff_text_len: i32 = diff_text.len() as i32;
512                        diff_text = diff_text[..min(
513                            diff_text_len,
514                            patch_size - patch.length1 - self.patch_margin,
515                        ) as usize]
516                            .to_vec();
517                        patch.length1 += diff_text.len() as i32;
518                        start1 += diff_text.len() as i32;
519                        if diff_type == 0 {
520                            patch.length2 += diff_text.len() as i32;
521                            start2 += diff_text.len() as i32;
522                        } else {
523                            empty = false;
524                        }
525                        patch
526                            .diffs
527                            .push(Diff::new(diff_type, diff_text.clone().iter().collect()));
528                        let temp: String = diff_text[..].iter().collect();
529                        if temp == bigpatch.diffs[0].text.clone() {
530                            bigpatch.diffs.remove(0);
531                        } else {
532                            let temp1: Vec<char> = bigpatch.diffs[0].text.chars().collect();
533                            bigpatch.diffs[0].text = temp1[diff_text.len()..].iter().collect();
534                        }
535                    }
536                }
537                // Compute the head context for the next patch.
538                precontext = self.diff_text2(&mut patch.diffs).chars().collect();
539                precontext = precontext[(precontext.len()
540                    - min(self.patch_margin, precontext.len() as i32) as usize)..]
541                    .to_vec();
542                // Append the end context for this patch.
543                let postcontext = if self.diff_text1(&mut bigpatch.diffs).chars().count() as i32
544                    > self.patch_margin
545                {
546                    let temp: Vec<char> = self.diff_text1(&mut bigpatch.diffs).chars().collect();
547                    temp[..self.patch_margin as usize].iter().collect()
548                } else {
549                    self.diff_text1(&mut bigpatch.diffs)
550                };
551                let postcontext_len = postcontext.chars().count() as i32;
552                if !postcontext.is_empty() {
553                    patch.length1 += postcontext_len;
554                    patch.length2 += postcontext_len;
555                    if !patch.diffs.is_empty() && patch.diffs[patch.diffs.len() - 1].operation == 0
556                    {
557                        let len = patch.diffs.len();
558                        patch.diffs[len - 1].text += postcontext.as_str();
559                    } else {
560                        patch.diffs.push(Diff::new(0, postcontext));
561                    }
562                }
563                if !empty {
564                    x += 1;
565                    patches.insert(x as usize, patch);
566                }
567            }
568            x += 1;
569        }
570    }
571
572    /// Take a list of patches and return a textual representation.
573    ///
574    /// Args:
575    /// patches: Vector of Patch objects.
576    ///
577    /// Returns:
578    /// Text representation of patches.
579    pub fn patch_to_text(&mut self, patches: &mut Vec<Patch>) -> String {
580        let mut text: String = "".to_string();
581        for patches_item in patches {
582            text += (patches_item.to_string()).as_str();
583        }
584        text
585    }
586
587    /// Parse a textual representation of patches and return a list of patch
588    /// objects.
589    ///
590    /// Args:
591    /// textline: Text representation of patches.
592    ///
593    /// Returns:
594    /// Vector of Patch objects.
595    ///
596    /// Panics on malformed patch text.
597    pub fn patch_from_text(&mut self, textline: String) -> Vec<Patch> {
598        try_patch_from_text(&textline).unwrap_or_else(|e| panic!("{}", e))
599    }
600
601    pub fn patch1_from_text(&mut self, textline: String) -> Patch {
602        // Parse full patch text and return its FIRST patch (any further hunks
603        // are ignored); panics on malformed input.
604        try_patch_from_text(&textline)
605            .unwrap_or_else(|e| panic!("{}", e))
606            .into_iter()
607            .next()
608            .unwrap_or_else(|| panic!("Invalid patch string: {}", textline))
609    }
610}
611
612/// diff_text1 (equalities + deletions) as chars — patch_apply works entirely
613/// in char space, so the String round trip would be pure overhead.
614fn diff_text1_chars(diffs: &[Diff]) -> Vec<char> {
615    let mut text = Vec::new();
616    for diff in diffs {
617        if diff.operation != 1 {
618            text.extend(diff.text.chars());
619        }
620    }
621    text
622}
623
624/// diff_text2 (equalities + insertions) as chars; see diff_text1_chars.
625fn diff_text2_chars(diffs: &[Diff]) -> Vec<char> {
626    let mut text = Vec::new();
627    for diff in diffs {
628        if diff.operation != -1 {
629            text.extend(diff.text.chars());
630        }
631    }
632    text
633}
634
635#[derive(Debug)]
636pub(crate) struct PatchError(String);
637
638impl fmt::Display for PatchError {
639    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
640        write!(f, "{}", self.0)
641    }
642}
643
644/// One header coordinate pair, with the oracle's exact semantics: "N" means
645/// start N-1 length 1, "N,0" keeps the raw start with length 0, "N,L" means
646/// start N-1 length L. Only plain digit runs are accepted, like the oracle's
647/// `\d+` (no sign, no overflow wrap).
648fn parse_coords(part: &str) -> Result<(i32, i32), PatchError> {
649    let err = || PatchError(format!("Invalid patch coordinates: {}", part));
650    let num = |s: &str| {
651        if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
652            return Err(err());
653        }
654        s.parse::<i32>().map_err(|_| err())
655    };
656    let (start, length) = part.split_once(',').unwrap_or((part, ""));
657    match length {
658        "" => Ok((num(start)? - 1, 1)),
659        "0" => Ok((num(start)?, 0)),
660        _ => Ok((num(start)? - 1, num(length)?)),
661    }
662}
663
664fn parse_header(line: &str) -> Result<(i32, i32, i32, i32), PatchError> {
665    let err = || PatchError(format!("Invalid patch string: {}", line));
666    let coords = line
667        .strip_prefix("@@ -")
668        .and_then(|r| r.strip_suffix(" @@"))
669        .ok_or_else(err)?;
670    let (c1, c2) = coords.split_once(" +").ok_or_else(err)?;
671    let (start1, length1) = parse_coords(c1)?;
672    let (start2, length2) = parse_coords(c2)?;
673    Ok((start1, length1, start2, length2))
674}
675
676pub(crate) fn try_patch_from_text(text: &str) -> Result<Vec<Patch>, PatchError> {
677    let mut patches: Vec<Patch> = vec![];
678    let lines: Vec<&str> = text.split('\n').collect();
679    let mut i = 0;
680    while i < lines.len() {
681        if lines[i].is_empty() {
682            i += 1;
683            continue;
684        }
685        let (start1, length1, start2, length2) = parse_header(lines[i])?;
686        let mut patch = Patch::new(vec![], start1, start2, length1, length2);
687        i += 1;
688        while i < lines.len() {
689            let line = lines[i];
690            if line.starts_with('@') {
691                // Start of next patch.
692                break;
693            }
694            if line.is_empty() {
695                // Blank line?  Whatever.
696                i += 1;
697                continue;
698            }
699            let mut line_chars = line.chars();
700            let sign = line_chars.next().unwrap();
701            let body = line_chars.as_str();
702            let decoded = percent_decode(body.as_bytes())
703                .decode_utf8()
704                .map_err(|_| PatchError(format!("Illegal escape in patch_from_text: {}", body)))?
705                .to_string();
706            match sign {
707                '+' => patch.diffs.push(Diff::new(1, decoded)),
708                '-' => patch.diffs.push(Diff::new(-1, decoded)),
709                ' ' => patch.diffs.push(Diff::new(0, decoded)),
710                _ => return Err(PatchError(format!("Invalid patch mode: {}", line))),
711            }
712            i += 1;
713        }
714        patches.push(patch);
715    }
716    Ok(patches)
717}
718
719/// Renders the patch text wire format, byte-compatible with the oracle:
720/// length 0 keeps the raw start, length 1 omits the length. `to_string()`
721/// call sites keep working through the blanket `ToString`.
722impl fmt::Display for Patch {
723    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
724        let coords = |start: i32, length: i32| match length {
725            0 => format!("{},0", start),
726            1 => format!("{}", start + 1),
727            _ => format!("{},{}", start + 1, length),
728        };
729        writeln!(
730            f,
731            "@@ -{} +{} @@",
732            coords(self.start1, self.length1),
733            coords(self.start2, self.length2)
734        )?;
735        for diff in &self.diffs {
736            let sign = match diff.operation {
737                0 => ' ',
738                -1 => '-',
739                _ => '+',
740            };
741            writeln!(f, "{}{}", sign, encode_uri(&diff.text))?;
742        }
743        Ok(())
744    }
745}
746
747// The historic public API takes &Vec/&mut Vec/&String; frozen by the
748// drop-in compatibility contract.
749#[allow(clippy::ptr_arg)]
750impl Dmp {
751    /// split the string accoring to given character
752    ///
753    /// Args:
754    /// text: string we have to split
755    /// ch: character by which we have to split string
756    ///
757    /// Returns:
758    /// Vector of string after spliting according to character.
759    pub fn split_by_char(&mut self, text: &str, ch: char) -> Vec<String> {
760        let temp: Vec<&str> = text.split(ch).collect();
761        let mut temp1: Vec<String> = vec![];
762        for temp_item in &temp {
763            temp1.push(temp_item.to_string());
764        }
765        temp1
766    }
767
768    /// split the string accoring to given characters "@@ ".
769    ///
770    /// Args:
771    /// text: string we have to split
772    ///
773    /// Returns:
774    /// Vector of string after spliting according to characters.
775    pub fn split_by_chars(&mut self, text: &str) -> Vec<String> {
776        let temp: Vec<&str> = text.split("@@ ").collect();
777        let mut temp1: Vec<String> = vec![];
778        for temp_item in &temp {
779            temp1.push(temp_item.to_string());
780        }
781        temp1
782    }
783}