1102 lines · 35.8 KB · Rust
RawDownload
use ordered_float::OrderedFloat;use rope::{Point, Rope, TextSummary};use std::collections::{BTreeSet, HashMap};use std::{    cmp,    fmt::{self, Debug},    ops::Range,}; struct Matrix {    cells: Vec<f64>,    rows: usize,    cols: usize,} impl Matrix {    fn new() -> Self {        Self {            cells: Vec::new(),            rows: 0,            cols: 0,        }    }     fn resize(&mut self, rows: usize, cols: usize) {        self.cells.resize(rows * cols, 0.);        self.rows = rows;        self.cols = cols;    }     fn swap_columns(&mut self, col1: usize, col2: usize) {        if col1 == col2 {            return;        }         if col1 >= self.cols {            panic!("column out of bounds");        }         if col2 >= self.cols {            panic!("column out of bounds");        }         unsafe {            let ptr = self.cells.as_mut_ptr();            std::ptr::swap_nonoverlapping(                ptr.add(col1 * self.rows),                ptr.add(col2 * self.rows),                self.rows,            );        }    }     fn get(&self, row: usize, col: usize) -> f64 {        if row >= self.rows {            panic!("row out of bounds")        }         if col >= self.cols {            panic!("column out of bounds")        }        self.cells[col * self.rows + row]    }     fn set(&mut self, row: usize, col: usize, value: f64) {        if row >= self.rows {            panic!("row out of bounds")        }         if col >= self.cols {            panic!("column out of bounds")        }         self.cells[col * self.rows + row] = value;    }} impl Debug for Matrix {    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {        writeln!(f)?;        for i in 0..self.rows {            for j in 0..self.cols {                write!(f, "{:5}", self.get(i, j))?;            }            writeln!(f)?;        }        Ok(())    }} #[derive(Debug, Clone)]pub enum CharOperation {    Insert { text: String },    Delete { bytes: usize },    Keep { bytes: usize },} pub struct StreamingDiff {    old: Vec<char>,    new: Vec<char>,    scores: Matrix,    old_text_ix: usize,    new_text_ix: usize,    equal_runs: HashMap<(usize, usize), u32>,} impl StreamingDiff {    const INSERTION_SCORE: f64 = -1.;    const DELETION_SCORE: f64 = -20.;    const EQUALITY_BASE: f64 = 1.8;    const MAX_EQUALITY_EXPONENT: i32 = 16;     pub fn new(old: String) -> Self {        let old = old.chars().collect::<Vec<_>>();        let mut scores = Matrix::new();        scores.resize(old.len() + 1, 1);        for i in 0..=old.len() {            scores.set(i, 0, i as f64 * Self::DELETION_SCORE);        }        Self {            old,            new: Vec::new(),            scores,            old_text_ix: 0,            new_text_ix: 0,            equal_runs: Default::default(),        }    }     pub fn push_new(&mut self, text: &str) -> Vec<CharOperation> {        self.new.extend(text.chars());        self.scores.swap_columns(0, self.scores.cols - 1);        self.scores            .resize(self.old.len() + 1, self.new.len() - self.new_text_ix + 1);        self.equal_runs.retain(|(_i, j), _| *j == self.new_text_ix);         for j in self.new_text_ix + 1..=self.new.len() {            let relative_j = j - self.new_text_ix;             self.scores                .set(0, relative_j, j as f64 * Self::INSERTION_SCORE);            for i in 1..=self.old.len() {                let insertion_score = self.scores.get(i, relative_j - 1) + Self::INSERTION_SCORE;                let deletion_score = self.scores.get(i - 1, relative_j) + Self::DELETION_SCORE;                let equality_score = if self.old[i - 1] == self.new[j - 1] {                    let mut equal_run = self.equal_runs.get(&(i - 1, j - 1)).copied().unwrap_or(0);                    equal_run += 1;                    self.equal_runs.insert((i, j), equal_run);                     let exponent = cmp::min(equal_run as i32 / 4, Self::MAX_EQUALITY_EXPONENT);                    self.scores.get(i - 1, relative_j - 1) + Self::EQUALITY_BASE.powi(exponent)                } else {                    f64::NEG_INFINITY                };                 let score = insertion_score.max(deletion_score).max(equality_score);                self.scores.set(i, relative_j, score);            }        }         let mut max_score = f64::NEG_INFINITY;        let mut next_old_text_ix = self.old_text_ix;        let next_new_text_ix = self.new.len();        for i in self.old_text_ix..=self.old.len() {            let score = self.scores.get(i, next_new_text_ix - self.new_text_ix);            if score > max_score {                max_score = score;                next_old_text_ix = i;            }        }         let hunks = self.backtrack(next_old_text_ix, next_new_text_ix);        self.old_text_ix = next_old_text_ix;        self.new_text_ix = next_new_text_ix;        hunks    }     fn backtrack(&self, old_text_ix: usize, new_text_ix: usize) -> Vec<CharOperation> {        let mut pending_insert: Option<Range<usize>> = None;        let mut hunks = Vec::new();        let mut i = old_text_ix;        let mut j = new_text_ix;        while (i, j) != (self.old_text_ix, self.new_text_ix) {            let insertion_score = if j > self.new_text_ix {                Some((i, j - 1))            } else {                None            };            let deletion_score = if i > self.old_text_ix {                Some((i - 1, j))            } else {                None            };            let equality_score = if i > self.old_text_ix && j > self.new_text_ix {                if self.old[i - 1] == self.new[j - 1] {                    Some((i - 1, j - 1))                } else {                    None                }            } else {                None            };             let (prev_i, prev_j) = [insertion_score, deletion_score, equality_score]                .iter()                .max_by_key(|cell| {                    cell.map(|(i, j)| OrderedFloat(self.scores.get(i, j - self.new_text_ix)))                })                .unwrap()                .unwrap();             if prev_i == i && prev_j == j - 1 {                if let Some(pending_insert) = pending_insert.as_mut() {                    pending_insert.start = prev_j;                } else {                    pending_insert = Some(prev_j..j);                }            } else {                if let Some(range) = pending_insert.take() {                    hunks.push(CharOperation::Insert {                        text: self.new[range].iter().collect(),                    });                }                 let char_len = self.old[i - 1].len_utf8();                if prev_i == i - 1 && prev_j == j {                    if let Some(CharOperation::Delete { bytes: len }) = hunks.last_mut() {                        *len += char_len;                    } else {                        hunks.push(CharOperation::Delete { bytes: char_len })                    }                } else if let Some(CharOperation::Keep { bytes: len }) = hunks.last_mut() {                    *len += char_len;                } else {                    hunks.push(CharOperation::Keep { bytes: char_len })                }            }             i = prev_i;            j = prev_j;        }         if let Some(range) = pending_insert.take() {            hunks.push(CharOperation::Insert {                text: self.new[range].iter().collect(),            });        }         hunks.reverse();        hunks    }     pub fn finish(self) -> Vec<CharOperation> {        self.backtrack(self.old.len(), self.new.len())    }} #[derive(Debug, Clone, PartialEq)]pub enum LineOperation {    Insert { lines: u32 },    Delete { lines: u32 },    Keep { lines: u32 },} #[derive(Debug, Default)]pub struct LineDiff {    inserted_newline_at_end: bool,    /// The extent of kept and deleted text.    old_end: Point,    /// The extent of kept and inserted text.    new_end: Point,    /// Deleted rows, expressed in terms of the old text.    deleted_rows: BTreeSet<u32>,    /// Inserted rows, expressed in terms of the new text.    inserted_rows: BTreeSet<u32>,    buffered_insert: String,    /// After deleting a newline, we buffer deletion until we keep or insert a character.    buffered_delete: usize,} impl LineDiff {    pub fn push_char_operations<'a>(        &mut self,        operations: impl IntoIterator<Item = &'a CharOperation>,        old_text: &Rope,    ) {        for operation in operations {            self.push_char_operation(operation, old_text);        }    }     pub fn push_char_operation(&mut self, operation: &CharOperation, old_text: &Rope) {        match operation {            CharOperation::Insert { text } => {                self.flush_delete(old_text);                 if is_line_start(self.old_end) {                    if let Some(newline_ix) = text.rfind('\n') {                        let (prefix, suffix) = text.split_at(newline_ix + 1);                        self.buffered_insert.push_str(prefix);                        self.flush_insert(old_text);                        self.buffered_insert.push_str(suffix);                    } else {                        self.buffered_insert.push_str(&text);                    }                } else {                    self.buffered_insert.push_str(&text);                    if !text.ends_with('\n') {                        self.flush_insert(old_text);                    }                }            }            CharOperation::Delete { bytes } => {                self.buffered_delete += bytes;                 let common_suffix_len = self.trim_buffered_end(old_text);                self.flush_insert(old_text);                 if common_suffix_len > 0 || !is_line_end(self.old_end, old_text) {                    self.flush_delete(old_text);                    self.keep(common_suffix_len, old_text);                }            }            CharOperation::Keep { bytes } => {                self.flush_delete(old_text);                self.flush_insert(old_text);                self.keep(*bytes, old_text);            }        }    }     fn flush_insert(&mut self, old_text: &Rope) {        if self.buffered_insert.is_empty() {            return;        }         let new_start = self.new_end;        let lines = TextSummary::from(self.buffered_insert.as_str()).lines;        self.new_end += lines;         if is_line_start(self.old_end) {            if self.new_end.column == 0 {                self.inserted_rows.extend(new_start.row..self.new_end.row);            } else {                self.deleted_rows.insert(self.old_end.row);                self.inserted_rows.extend(new_start.row..=self.new_end.row);            }        } else if is_line_end(self.old_end, old_text) {            if self.buffered_insert.starts_with('\n') {                self.inserted_rows                    .extend(new_start.row + 1..=self.new_end.row);                self.inserted_newline_at_end = true;            } else {                if !self.inserted_newline_at_end {                    self.deleted_rows.insert(self.old_end.row);                }                self.inserted_rows.extend(new_start.row..=self.new_end.row);            }        } else {            self.deleted_rows.insert(self.old_end.row);            self.inserted_rows.extend(new_start.row..=self.new_end.row);        }         self.buffered_insert.clear();    }     fn flush_delete(&mut self, old_text: &Rope) {        if self.buffered_delete == 0 {            return;        }         let old_start = self.old_end;        self.old_end =            old_text.offset_to_point(old_text.point_to_offset(self.old_end) + self.buffered_delete);         if is_line_end(old_start, old_text) && is_line_end(self.old_end, old_text) {            self.deleted_rows                .extend(old_start.row + 1..=self.old_end.row);        } else if is_line_start(old_start)            && (is_line_start(self.old_end) && self.old_end < old_text.max_point())            && self.new_end.column == 0        {            self.deleted_rows.extend(old_start.row..self.old_end.row);        } else {            self.inserted_rows.insert(self.new_end.row);            self.deleted_rows.extend(old_start.row..=self.old_end.row);        }         self.inserted_newline_at_end = false;        self.buffered_delete = 0;    }     fn keep(&mut self, bytes: usize, old_text: &Rope) {        if bytes == 0 {            return;        }         let lines =            old_text.offset_to_point(old_text.point_to_offset(self.old_end) + bytes) - self.old_end;        self.old_end += lines;        self.new_end += lines;        self.inserted_newline_at_end = false;    }     fn trim_buffered_end(&mut self, old_text: &Rope) -> usize {        let old_start_offset = old_text.point_to_offset(self.old_end);        let old_end_offset = old_start_offset + self.buffered_delete;         let new_chars = self.buffered_insert.chars().rev();        let old_chars = old_text            .chunks_in_range(old_start_offset..old_end_offset)            .flat_map(|chunk| chunk.chars().rev());         let mut common_suffix_len = 0;        for (new_ch, old_ch) in new_chars.zip(old_chars) {            if new_ch == old_ch {                common_suffix_len += new_ch.len_utf8();            } else {                break;            }        }         self.buffered_delete -= common_suffix_len;        self.buffered_insert            .truncate(self.buffered_insert.len() - common_suffix_len);         common_suffix_len    }     pub fn finish(&mut self, old_text: &Rope) {        self.flush_insert(old_text);        self.flush_delete(old_text);         let old_start = self.old_end;        self.old_end = old_text.max_point();        self.new_end += self.old_end - old_start;    }     pub fn line_operations(&self) -> Vec<LineOperation> {        let mut ops = Vec::new();        let mut deleted_rows = self.deleted_rows.iter().copied().peekable();        let mut inserted_rows = self.inserted_rows.iter().copied().peekable();        let mut old_row = 0;        let mut new_row = 0;         while deleted_rows.peek().is_some() || inserted_rows.peek().is_some() {            // Check for a run of deleted lines at current old row.            if Some(old_row) == deleted_rows.peek().copied() {                if let Some(LineOperation::Delete { lines }) = ops.last_mut() {                    *lines += 1;                } else {                    ops.push(LineOperation::Delete { lines: 1 });                }                old_row += 1;                deleted_rows.next();            } else if Some(new_row) == inserted_rows.peek().copied() {                if let Some(LineOperation::Insert { lines }) = ops.last_mut() {                    *lines += 1;                } else {                    ops.push(LineOperation::Insert { lines: 1 });                }                new_row += 1;                inserted_rows.next();            } else {                // Keep lines until the next deletion, insertion, or the end of the old text.                let lines_to_next_deletion = inserted_rows                    .peek()                    .copied()                    .unwrap_or(self.new_end.row + 1)                    - new_row;                let lines_to_next_insertion =                    deleted_rows.peek().copied().unwrap_or(self.old_end.row + 1) - old_row;                let kept_lines =                    cmp::max(1, cmp::min(lines_to_next_insertion, lines_to_next_deletion));                if kept_lines > 0 {                    ops.push(LineOperation::Keep { lines: kept_lines });                    old_row += kept_lines;                    new_row += kept_lines;                }            }        }         if old_row < self.old_end.row + 1 {            ops.push(LineOperation::Keep {                lines: self.old_end.row + 1 - old_row,            });        }         ops    }} fn is_line_start(point: Point) -> bool {    point.column == 0} fn is_line_end(point: Point, text: &Rope) -> bool {    text.line_len(point.row) == point.column} 

Lines 1–500 of 1102.