Skip to main content

java_diff_utils_rs/text/
diff_row_generator.rs

1//! Generates `DiffRow` instances for side-by-side or inline text views.
2use crate::patch::chunk::Chunk;
3use crate::patch::delta::Delta;
4use crate::patch::delta_type::DeltaType;
5use crate::patch::patch::Patch;
6use crate::text::delta_merge::delta_merge_utils::DeltaMergeUtils;
7use crate::text::delta_merge::inline_delta_merge_info::InlineDeltaMergeInfo;
8use crate::text::diff_row::{DiffRow, Tag};
9use crate::text::string_utils;
10use lazy_static::lazy_static;
11use regex::Regex;
12use std::sync::Arc;
13
14lazy_static! {
15    pub static ref SPLIT_BY_WORD_PATTERN: Regex =
16        Regex::new(r"\s+|[,.\[\](){}/\\*+\-#<>;:&']+").unwrap();
17    static ref WHITESPACE_RE: Regex = Regex::new(r"\s+").unwrap();
18    pub static ref WHITESPACE_EQUALITIES_MERGER: InlineDeltaMergerFn =
19        whitespace_equalities_merger();
20}
21
22// Type alias definitions for flexible closures
23pub type EqualizerFn = Arc<dyn Fn(&str, &str) -> bool + Send + Sync>;
24pub type TagGeneratorFn = Arc<dyn Fn(Tag, bool) -> String + Send + Sync>;
25pub type StringTransformFn = Arc<dyn Fn(&str) -> String + Send + Sync>;
26pub type SplitterFn = Arc<dyn Fn(&str) -> Vec<String> + Send + Sync>;
27pub type InlineDeltaMergerFn =
28    Arc<dyn Fn(&InlineDeltaMergeInfo<String>) -> Vec<Delta<String>> + Send + Sync>;
29
30/// Adjusts whitespace in a string by collapsing consecutive whitespaces into a single space.
31pub fn adjust_whitespace(raw: &str) -> String {
32    WHITESPACE_RE.replace_all(raw.trim(), " ").to_string()
33}
34
35/// Default equalizer checking strict string equality.
36pub fn default_equalizer() -> EqualizerFn {
37    Arc::new(|orig, rev| orig == rev)
38}
39
40/// Equalizer ignoring whitespace differences.
41pub fn ignore_whitespace_equalizer() -> EqualizerFn {
42    Arc::new(|orig, rev| adjust_whitespace(orig) == adjust_whitespace(rev))
43}
44
45/// Default line normalizer for HTML escaping.
46pub fn html_line_normalizer() -> StringTransformFn {
47    Arc::new(string_utils::normalize)
48}
49
50/// Character-by-character splitter.
51pub fn splitter_by_character() -> SplitterFn {
52    Arc::new(|line| line.chars().map(|c| c.to_string()).collect())
53}
54
55/// Word-by-word splitter.
56pub fn splitter_by_word() -> SplitterFn {
57    Arc::new(|line| split_string_preserve_delimiter(line, &SPLIT_BY_WORD_PATTERN))
58}
59
60/// Default inline delta merger returning unmodified deltas.
61pub fn default_inline_delta_merger() -> InlineDeltaMergerFn {
62    Arc::new(|info: &InlineDeltaMergeInfo<String>| info.deltas().to_vec())
63}
64
65/// Whitespace equalities inline delta merger.
66pub fn whitespace_equalities_merger() -> InlineDeltaMergerFn {
67    Arc::new(move |info| {
68        DeltaMergeUtils::merge_inline_deltas(info, move |equalities: &[String]| {
69            equalities
70                .iter()
71                .all(|s| WHITESPACE_RE.replace_all(s, "").is_empty())
72        })
73    })
74}
75
76/// Helper function to split a string while preserving matched delimiters.
77pub fn split_string_preserve_delimiter(str_input: &str, pattern: &Regex) -> Vec<String> {
78    let mut list = Vec::new();
79    let mut pos = 0;
80
81    for mat in pattern.find_iter(str_input) {
82        if pos < mat.start() {
83            list.push(str_input[pos..mat.start()].to_string());
84        }
85        list.push(mat.as_str().to_string());
86        pos = mat.end();
87    }
88
89    if pos < str_input.len() {
90        list.push(str_input[pos..].to_string());
91    }
92
93    list
94}
95
96/// Wraps elements in a string sequence with start/end tags.
97pub fn wrap_in_tag(
98    sequence: &mut Vec<String>,
99    start_position: usize,
100    end_position: usize,
101    tag: Tag,
102    tag_generator: &TagGeneratorFn,
103    process_diffs: Option<&StringTransformFn>,
104    replace_linefeed_with_space: bool,
105) {
106    if start_position >= sequence.len() && end_position >= sequence.len() {
107        return;
108    }
109
110    let mut end_pos = end_position.min(sequence.len());
111
112    while end_pos >= start_position {
113        while end_pos > start_position {
114            if sequence[end_pos - 1] != "\n" {
115                break;
116            } else if replace_linefeed_with_space {
117                sequence[end_pos - 1] = " ".to_string();
118                break;
119            }
120            end_pos -= 1;
121        }
122
123        if end_pos == start_position {
124            break;
125        }
126
127        sequence.insert(end_pos, tag_generator(tag, false));
128        if let Some(proc) = process_diffs {
129            sequence[end_pos - 1] = proc(&sequence[end_pos - 1]);
130        }
131        end_pos -= 1;
132
133        while end_pos > start_position {
134            if sequence[end_pos - 1] == "\n" {
135                if replace_linefeed_with_space {
136                    sequence[end_pos - 1] = " ".to_string();
137                } else {
138                    break;
139                }
140            }
141            if let Some(proc) = process_diffs {
142                sequence[end_pos - 1] = proc(&sequence[end_pos - 1]);
143            }
144            end_pos -= 1;
145        }
146
147        sequence.insert(end_pos, tag_generator(tag, true));
148        if end_pos == 0 {
149            break;
150        }
151        end_pos -= 1;
152    }
153}
154
155/// Primary generator for `DiffRow` side-by-side views.
156#[derive(Clone)]
157pub struct DiffRowGenerator {
158    column_width: usize,
159    equalizer: EqualizerFn,
160    ignore_white_spaces: bool,
161    inline_diff_splitter: SplitterFn,
162    merge_original_revised: bool,
163    old_tag: TagGeneratorFn,
164    new_tag: TagGeneratorFn,
165    report_lines_unchanged: bool,
166    line_normalizer: StringTransformFn,
167    process_diffs: Option<StringTransformFn>,
168    inline_delta_merger: InlineDeltaMergerFn,
169    equality_processor: Option<StringTransformFn>,
170    show_inline_diffs: bool,
171    replace_original_linefeed_in_changes_with_spaces: bool,
172    decompress_deltas: bool,
173}
174
175impl DiffRowGenerator {
176    /// Creates a builder to configure a `DiffRowGenerator`.
177    pub fn create() -> Builder {
178        Builder::new()
179    }
180
181    /// Helper to forward delimiter splitting.
182    pub fn split_string_preserve_delimiter(str_input: &str, pattern: &Regex) -> Vec<String> {
183        split_string_preserve_delimiter(str_input, pattern)
184    }
185
186    /// Returns whether whitespace differences are ignored.
187    pub fn ignore_white_spaces(&self) -> bool {
188        self.ignore_white_spaces
189    }
190
191    /// Generates `DiffRow` items comparing two string sequences.
192    pub fn generate_diff_rows(&self, original: &[String], revised: &[String]) -> Vec<DiffRow> {
193        let mut patch = crate::diff_utils::DiffUtils::diff_with_equalizer(
194            original,
195            revised,
196            Some({
197                let eq = self.equalizer.clone();
198                move |a: &String, b: &String| eq(a, b)
199            }),
200        );
201        self.generate_diff_rows_from_patch(original, &mut patch)
202    }
203
204    /// Generates `DiffRow` items comparing an original text sequence against an existing `Patch`.
205    pub fn generate_diff_rows_from_patch(
206        &self,
207        original: &[String],
208        patch: &mut Patch<String>,
209    ) -> Vec<DiffRow> {
210        let mut diff_rows = Vec::new();
211        let mut end_pos = 0;
212        let delta_list = Self::normalize_patch_deltas(patch.deltas().to_vec());
213
214        if self.decompress_deltas {
215            for original_delta in &delta_list {
216                for delta in self.decompress_deltas_internal(original_delta) {
217                    end_pos = self.transform_delta_into_diff_row(
218                        original,
219                        end_pos,
220                        &mut diff_rows,
221                        &delta,
222                    );
223                }
224            }
225        } else {
226            for delta in &delta_list {
227                end_pos =
228                    self.transform_delta_into_diff_row(original, end_pos, &mut diff_rows, delta);
229            }
230        }
231
232        if end_pos < original.len() {
233            for line in &original[end_pos..] {
234                let processed = self.process_equalities(line);
235                diff_rows.push(self.build_diff_row(Tag::Equal, &processed, &processed));
236            }
237        }
238
239        diff_rows
240    }
241
242    fn normalize_patch_deltas(deltas: Vec<Delta<String>>) -> Vec<Delta<String>> {
243        let mut normalized = Vec::with_capacity(deltas.len());
244        let mut index = 0;
245
246        while index < deltas.len() {
247            if index + 1 < deltas.len() {
248                let current = &deltas[index];
249                let next = &deltas[index + 1];
250
251                let same_source_position = current.source().position() == next.source().position();
252
253                let should_merge = same_source_position
254                    && ((current.delta_type() == DeltaType::Delete
255                        && next.delta_type() == DeltaType::Insert)
256                        || (current.delta_type() == DeltaType::Insert
257                            && next.delta_type() == DeltaType::Delete));
258
259                if should_merge {
260                    let merged_source = if current.delta_type() == DeltaType::Delete {
261                        current.source().clone()
262                    } else {
263                        next.source().clone()
264                    };
265                    let merged_target = if current.delta_type() == DeltaType::Insert {
266                        current.target().clone()
267                    } else {
268                        next.target().clone()
269                    };
270
271                    normalized.push(Delta::new(DeltaType::Change, merged_source, merged_target));
272                    index += 2;
273                    continue;
274                }
275            }
276
277            normalized.push(deltas[index].clone());
278            index += 1;
279        }
280
281        normalized
282    }
283
284    fn transform_delta_into_diff_row(
285        &self,
286        original: &[String],
287        end_pos: usize,
288        diff_rows: &mut Vec<DiffRow>,
289        delta: &Delta<String>,
290    ) -> usize {
291        let orig = delta.source();
292        let rev = delta.target();
293
294        let target_pos = orig.position().min(original.len());
295        if end_pos < target_pos {
296            for line in &original[end_pos..target_pos] {
297                let processed = self.process_equalities(line);
298                diff_rows.push(self.build_diff_row(Tag::Equal, &processed, &processed));
299            }
300        }
301
302        match delta.delta_type() {
303            DeltaType::Insert => {
304                for line in rev.lines() {
305                    diff_rows.push(self.build_diff_row(Tag::Insert, "", line));
306                }
307            }
308            DeltaType::Delete => {
309                for line in orig.lines() {
310                    diff_rows.push(self.build_diff_row(Tag::Delete, line, ""));
311                }
312            }
313            _ => {
314                if self.show_inline_diffs {
315                    diff_rows.extend(self.generate_inline_diffs(delta));
316                } else {
317                    let max_size = orig.lines().len().max(rev.lines().len());
318                    for j in 0..max_size {
319                        let orig_line = orig.lines().get(j).map(|s| s.as_str()).unwrap_or("");
320                        let rev_line = rev.lines().get(j).map(|s| s.as_str()).unwrap_or("");
321                        diff_rows.push(self.build_diff_row(Tag::Change, orig_line, rev_line));
322                    }
323                }
324            }
325        }
326
327        orig.position() + orig.len()
328    }
329
330    fn decompress_deltas_internal(&self, delta: &Delta<String>) -> Vec<Delta<String>> {
331        if delta.delta_type() == DeltaType::Change && delta.source().len() != delta.target().len() {
332            let mut deltas = Vec::new();
333            let min_size = delta.source().len().min(delta.target().len());
334            let orig = delta.source();
335            let rev = delta.target();
336
337            if min_size > 0 {
338                let orig_sub = orig.lines()[..min_size].to_vec();
339                let rev_sub = rev.lines()[..min_size].to_vec();
340
341                deltas.push(Delta::new(
342                    DeltaType::Change,
343                    Chunk::new(orig.position(), orig_sub, None),
344                    Chunk::new(rev.position(), rev_sub, None),
345                ));
346            }
347
348            if orig.lines().len() < rev.lines().len() {
349                deltas.push(Delta::new(
350                    DeltaType::Insert,
351                    Chunk::new(orig.position() + min_size, Vec::new(), None),
352                    Chunk::new(
353                        rev.position() + min_size,
354                        rev.lines()[min_size..].to_vec(),
355                        None,
356                    ),
357                ));
358            } else if orig.lines().len() > rev.lines().len() {
359                deltas.push(Delta::new(
360                    DeltaType::Delete,
361                    Chunk::new(
362                        orig.position() + min_size,
363                        orig.lines()[min_size..].to_vec(),
364                        None,
365                    ),
366                    Chunk::new(rev.position() + min_size, Vec::new(), None),
367                ));
368            }
369
370            deltas
371        } else {
372            vec![delta.clone()]
373        }
374    }
375
376    fn build_diff_row(&self, tag_type: Tag, orgline: &str, newline: &str) -> DiffRow {
377        if self.report_lines_unchanged {
378            DiffRow::new(tag_type, orgline, newline)
379        } else {
380            let mut wrap_org = self.preprocess_line(orgline);
381            if Tag::Delete == tag_type && (self.merge_original_revised || self.show_inline_diffs) {
382                wrap_org = format!(
383                    "{}{}{}",
384                    (self.old_tag)(tag_type, true),
385                    wrap_org,
386                    (self.old_tag)(tag_type, false)
387                );
388            }
389
390            let mut wrap_new = self.preprocess_line(newline);
391            if Tag::Insert == tag_type {
392                if self.merge_original_revised {
393                    wrap_org = format!(
394                        "{}{}{}",
395                        (self.new_tag)(tag_type, true),
396                        wrap_new,
397                        (self.new_tag)(tag_type, false)
398                    );
399                } else if self.show_inline_diffs {
400                    wrap_new = format!(
401                        "{}{}{}",
402                        (self.new_tag)(tag_type, true),
403                        wrap_new,
404                        (self.new_tag)(tag_type, false)
405                    );
406                }
407            }
408
409            DiffRow::new(tag_type, wrap_org, wrap_new)
410        }
411    }
412
413    fn build_diff_row_without_normalizing(
414        &self,
415        tag_type: Tag,
416        orgline: &str,
417        newline: &str,
418    ) -> DiffRow {
419        DiffRow::new(
420            tag_type,
421            string_utils::wrap_text(orgline, self.column_width),
422            string_utils::wrap_text(newline, self.column_width),
423        )
424    }
425
426    pub fn normalize_lines(&self, list: &[String]) -> Vec<String> {
427        if self.report_lines_unchanged {
428            list.to_vec()
429        } else {
430            list.iter()
431                .map(|line| (self.line_normalizer)(line))
432                .collect()
433        }
434    }
435
436    fn generate_inline_diffs(&self, delta: &Delta<String>) -> Vec<DiffRow> {
437        let orig = self.normalize_lines(delta.source().lines());
438        let rev = self.normalize_lines(delta.target().lines());
439
440        let joined_orig = orig.join("\n");
441        let joined_rev = rev.join("\n");
442
443        let mut orig_list = (self.inline_diff_splitter)(&joined_orig);
444        let mut rev_list = (self.inline_diff_splitter)(&joined_rev);
445
446        let patch = crate::diff_utils::DiffUtils::diff_with_equalizer(
447            &orig_list,
448            &rev_list,
449            Some({
450                let eq = self.equalizer.clone();
451                move |a: &String, b: &String| eq(a, b)
452            }),
453        );
454        let original_inline_deltas = patch.deltas().to_vec();
455
456        let inline_merge_info =
457            InlineDeltaMergeInfo::new(original_inline_deltas, orig_list.clone(), rev_list.clone());
458        let mut inline_deltas: Vec<Delta<String>> = (self.inline_delta_merger)(&inline_merge_info);
459        inline_deltas.reverse();
460
461        for inline_delta in &inline_deltas {
462            let inline_orig = inline_delta.source();
463            let inline_rev = inline_delta.target();
464
465            match inline_delta.delta_type() {
466                DeltaType::Delete => {
467                    wrap_in_tag(
468                        &mut orig_list,
469                        inline_orig.position(),
470                        inline_orig.position() + inline_orig.len(),
471                        Tag::Delete,
472                        &self.old_tag,
473                        self.process_diffs.as_ref(),
474                        self.replace_original_linefeed_in_changes_with_spaces
475                            && self.merge_original_revised,
476                    );
477                }
478                DeltaType::Insert => {
479                    if self.merge_original_revised {
480                        let mut temp_rev = rev_list.clone();
481                        let len_before = temp_rev.len();
482                        wrap_in_tag(
483                            &mut temp_rev,
484                            inline_rev.position(),
485                            inline_rev.position() + inline_rev.len(),
486                            Tag::Insert,
487                            &self.new_tag,
488                            self.process_diffs.as_ref(),
489                            false,
490                        );
491                        let tags_added = temp_rev.len() - len_before;
492                        let insert_start = inline_rev.position().min(temp_rev.len());
493                        let insert_end = (inline_rev.position() + inline_rev.len() + tags_added)
494                            .min(temp_rev.len());
495                        let sub = temp_rev[insert_start..insert_end].to_vec();
496                        let orig_pos = inline_orig.position().min(orig_list.len());
497                        orig_list.splice(orig_pos..orig_pos, sub);
498                    } else {
499                        wrap_in_tag(
500                            &mut rev_list,
501                            inline_rev.position(),
502                            inline_rev.position() + inline_rev.len(),
503                            Tag::Insert,
504                            &self.new_tag,
505                            self.process_diffs.as_ref(),
506                            false,
507                        );
508                    }
509                }
510                DeltaType::Change => {
511                    if self.merge_original_revised {
512                        let mut temp_rev = rev_list.clone();
513                        let rev_len_before = temp_rev.len();
514                        wrap_in_tag(
515                            &mut temp_rev,
516                            inline_rev.position(),
517                            inline_rev.position() + inline_rev.len(),
518                            Tag::Change,
519                            &self.new_tag,
520                            self.process_diffs.as_ref(),
521                            false,
522                        );
523                        let rev_tags_added = temp_rev.len() - rev_len_before;
524
525                        let orig_len_before = orig_list.len();
526                        wrap_in_tag(
527                            &mut orig_list,
528                            inline_orig.position(),
529                            inline_orig.position() + inline_orig.len(),
530                            Tag::Change,
531                            &self.old_tag,
532                            self.process_diffs.as_ref(),
533                            self.replace_original_linefeed_in_changes_with_spaces
534                                && self.merge_original_revised,
535                        );
536                        let orig_tags_added = orig_list.len() - orig_len_before;
537
538                        let insert_start = inline_rev.position().min(temp_rev.len());
539                        let insert_end =
540                            (inline_rev.position() + inline_rev.len() + rev_tags_added)
541                                .min(temp_rev.len());
542                        let sub = temp_rev[insert_start..insert_end].to_vec();
543                        let orig_pos =
544                            (inline_orig.position() + inline_orig.len() + orig_tags_added)
545                                .min(orig_list.len());
546                        orig_list.splice(orig_pos..orig_pos, sub);
547                    } else {
548                        wrap_in_tag(
549                            &mut rev_list,
550                            inline_rev.position(),
551                            inline_rev.position() + inline_rev.len(),
552                            Tag::Change,
553                            &self.new_tag,
554                            self.process_diffs.as_ref(),
555                            false,
556                        );
557                        wrap_in_tag(
558                            &mut orig_list,
559                            inline_orig.position(),
560                            inline_orig.position() + inline_orig.len(),
561                            Tag::Change,
562                            &self.old_tag,
563                            self.process_diffs.as_ref(),
564                            self.replace_original_linefeed_in_changes_with_spaces
565                                && self.merge_original_revised,
566                        );
567                    }
568                }
569                _ => {}
570            }
571        }
572
573        let orig_result: String = orig_list.concat();
574        let rev_result: String = rev_list.concat();
575
576        let mut original_split: Vec<String> =
577            orig_result.split('\n').map(|s| s.to_string()).collect();
578        let mut revised_split: Vec<String> =
579            rev_result.split('\n').map(|s| s.to_string()).collect();
580
581        while original_split.len() > orig.len()
582            && original_split.last().is_some_and(|s| s.is_empty())
583        {
584            original_split.pop();
585        }
586        while revised_split.len() > rev.len() && revised_split.last().is_some_and(|s| s.is_empty())
587        {
588            revised_split.pop();
589        }
590
591        let max_lines = original_split.len().max(revised_split.len());
592        let mut diff_rows = Vec::new();
593
594        for j in 0..max_lines {
595            let orig_line = original_split.get(j).map(|s| s.as_str()).unwrap_or("");
596            let rev_line = revised_split.get(j).map(|s| s.as_str()).unwrap_or("");
597            diff_rows.push(self.build_diff_row_without_normalizing(
598                Tag::Change,
599                orig_line,
600                rev_line,
601            ));
602        }
603
604        diff_rows
605    }
606
607    fn preprocess_line(&self, line: &str) -> String {
608        let normalized = (self.line_normalizer)(line);
609        if self.column_width == 0 {
610            normalized
611        } else {
612            string_utils::wrap_text(&normalized, self.column_width)
613        }
614    }
615
616    fn process_equalities(&self, text: &str) -> String {
617        if let Some(ref proc) = self.equality_processor {
618            proc(text)
619        } else {
620            text.to_string()
621        }
622    }
623}
624
625/// Builder for constructing customized `DiffRowGenerator` instances.
626pub struct Builder {
627    show_inline_diffs: bool,
628    ignore_white_spaces: bool,
629    decompress_deltas: bool,
630    old_tag: TagGeneratorFn,
631    new_tag: TagGeneratorFn,
632    column_width: usize,
633    merge_original_revised: bool,
634    report_lines_unchanged: bool,
635    inline_diff_splitter: SplitterFn,
636    line_normalizer: StringTransformFn,
637    process_diffs: Option<StringTransformFn>,
638    equalizer: Option<EqualizerFn>,
639    replace_original_linefeed_in_changes_with_spaces: bool,
640    inline_delta_merger: InlineDeltaMergerFn,
641    equality_processor: Option<StringTransformFn>,
642}
643
644impl Builder {
645    /// Creates a new builder with default settings.
646    pub fn new() -> Self {
647        Self {
648            show_inline_diffs: false,
649            ignore_white_spaces: false,
650            decompress_deltas: true,
651            old_tag: Arc::new(|_tag, f| {
652                if f {
653                    "<span class=\"editOldInline\">".to_string()
654                } else {
655                    "</span>".to_string()
656                }
657            }),
658            new_tag: Arc::new(|_tag, f| {
659                if f {
660                    "<span class=\"editNewInline\">".to_string()
661                } else {
662                    "</span>".to_string()
663                }
664            }),
665            column_width: 0,
666            merge_original_revised: false,
667            report_lines_unchanged: false,
668            inline_diff_splitter: splitter_by_character(),
669            line_normalizer: html_line_normalizer(),
670            process_diffs: None,
671            equalizer: None,
672            replace_original_linefeed_in_changes_with_spaces: false,
673            inline_delta_merger: default_inline_delta_merger(),
674            equality_processor: None,
675        }
676    }
677
678    pub fn show_inline_diffs(mut self, val: bool) -> Self {
679        self.show_inline_diffs = val;
680        self
681    }
682
683    pub fn ignore_white_spaces(mut self, val: bool) -> Self {
684        self.ignore_white_spaces = val;
685        self
686    }
687
688    pub fn report_lines_unchanged(mut self, val: bool) -> Self {
689        self.report_lines_unchanged = val;
690        self
691    }
692
693    pub fn old_tag<F>(mut self, generator: F) -> Self
694    where
695        F: Fn(Tag, bool) -> String + Send + Sync + 'static,
696    {
697        self.old_tag = Arc::new(generator);
698        self
699    }
700
701    pub fn old_tag_simple<F>(mut self, generator: F) -> Self
702    where
703        F: Fn(bool) -> String + Send + Sync + 'static,
704    {
705        self.old_tag = Arc::new(move |_tag, f| generator(f));
706        self
707    }
708
709    pub fn new_tag<F>(mut self, generator: F) -> Self
710    where
711        F: Fn(Tag, bool) -> String + Send + Sync + 'static,
712    {
713        self.new_tag = Arc::new(generator);
714        self
715    }
716
717    pub fn new_tag_simple<F>(mut self, generator: F) -> Self
718    where
719        F: Fn(bool) -> String + Send + Sync + 'static,
720    {
721        self.new_tag = Arc::new(move |_tag, f| generator(f));
722        self
723    }
724
725    pub fn process_diffs<F>(mut self, processor: F) -> Self
726    where
727        F: Fn(&str) -> String + Send + Sync + 'static,
728    {
729        self.process_diffs = Some(Arc::new(processor));
730        self
731    }
732
733    pub fn process_equalities<F>(mut self, processor: F) -> Self
734    where
735        F: Fn(&str) -> String + Send + Sync + 'static,
736    {
737        self.equality_processor = Some(Arc::new(processor));
738        self
739    }
740
741    pub fn column_width(mut self, width: usize) -> Self {
742        self.column_width = width;
743        self
744    }
745
746    pub fn merge_original_revised(mut self, merge: bool) -> Self {
747        self.merge_original_revised = merge;
748        self
749    }
750
751    pub fn decompress_deltas(mut self, decompress: bool) -> Self {
752        self.decompress_deltas = decompress;
753        self
754    }
755
756    pub fn inline_diff_by_word(mut self, inline_diff_by_word: bool) -> Self {
757        self.inline_diff_splitter = if inline_diff_by_word {
758            splitter_by_word()
759        } else {
760            splitter_by_character()
761        };
762        self
763    }
764
765    pub fn inline_diff_by_splitter<F>(mut self, splitter: F) -> Self
766    where
767        F: Fn(&str) -> Vec<String> + Send + Sync + 'static,
768    {
769        self.inline_diff_splitter = Arc::new(splitter);
770        self
771    }
772
773    pub fn line_normalizer<F>(mut self, normalizer: F) -> Self
774    where
775        F: Fn(&str) -> String + Send + Sync + 'static,
776    {
777        self.line_normalizer = Arc::new(normalizer);
778        self
779    }
780
781    pub fn equalizer<F>(mut self, equalizer: F) -> Self
782    where
783        F: Fn(&str, &str) -> bool + Send + Sync + 'static,
784    {
785        self.equalizer = Some(Arc::new(equalizer));
786        self
787    }
788
789    pub fn replace_original_linefeed_in_changes_with_spaces(mut self, replace: bool) -> Self {
790        self.replace_original_linefeed_in_changes_with_spaces = replace;
791        self
792    }
793
794    pub fn inline_delta_merger<F>(mut self, merger: F) -> Self
795    where
796        F: Fn(&InlineDeltaMergeInfo<String>) -> Vec<Delta<String>> + Send + Sync + 'static,
797    {
798        self.inline_delta_merger = Arc::new(merger);
799        self
800    }
801
802    pub fn inline_delta_merger_arc(mut self, merger: InlineDeltaMergerFn) -> Self {
803        self.inline_delta_merger = merger;
804        self
805    }
806
807    pub fn build(self) -> DiffRowGenerator {
808        let equalizer = match self.equalizer {
809            Some(eq) => eq,
810            None => {
811                if self.ignore_white_spaces {
812                    ignore_whitespace_equalizer()
813                } else {
814                    default_equalizer()
815                }
816            }
817        };
818
819        DiffRowGenerator {
820            column_width: self.column_width,
821            equalizer,
822            ignore_white_spaces: self.ignore_white_spaces,
823            inline_diff_splitter: self.inline_diff_splitter,
824            merge_original_revised: self.merge_original_revised,
825            old_tag: self.old_tag,
826            new_tag: self.new_tag,
827            report_lines_unchanged: self.report_lines_unchanged,
828            line_normalizer: self.line_normalizer,
829            process_diffs: self.process_diffs,
830            inline_delta_merger: self.inline_delta_merger,
831            equality_processor: self.equality_processor,
832            show_inline_diffs: self.show_inline_diffs,
833            replace_original_linefeed_in_changes_with_spaces: self
834                .replace_original_linefeed_in_changes_with_spaces,
835            decompress_deltas: self.decompress_deltas,
836        }
837    }
838}
839
840impl Default for Builder {
841    fn default() -> Self {
842        Self::new()
843    }
844}