680 lines · 23.0 KB · Rust
RawDownload
use gpui::{App, Context, Entity, EventEmitter};use std::{cmp::Ordering, ops::Range, sync::Arc};use text::{Anchor, BufferId, OffsetRangeExt as _}; pub struct ConflictSet {    pub has_conflict: bool,    pub snapshot: ConflictSetSnapshot,} #[derive(Clone, Debug, PartialEq, Eq)]pub struct ConflictSetUpdate {    pub buffer_range: Option<Range<Anchor>>,    pub old_range: Range<usize>,    pub new_range: Range<usize>,} #[derive(Debug, Clone)]pub struct ConflictSetSnapshot {    pub buffer_id: BufferId,    pub conflicts: Arc<[ConflictRegion]>,} impl ConflictSetSnapshot {    pub fn conflicts_in_range(        &self,        range: Range<Anchor>,        buffer: &text::BufferSnapshot,    ) -> &[ConflictRegion] {        let start_ix = self            .conflicts            .binary_search_by(|conflict| {                conflict                    .range                    .end                    .cmp(&range.start, buffer)                    .then(Ordering::Greater)            })            .unwrap_err();        let end_ix = start_ix            + self.conflicts[start_ix..]                .binary_search_by(|conflict| {                    conflict                        .range                        .start                        .cmp(&range.end, buffer)                        .then(Ordering::Less)                })                .unwrap_err();        &self.conflicts[start_ix..end_ix]    }     pub fn compare(&self, other: &Self, buffer: &text::BufferSnapshot) -> ConflictSetUpdate {        let common_prefix_len = self            .conflicts            .iter()            .zip(other.conflicts.iter())            .take_while(|(old, new)| old == new)            .count();        let common_suffix_len = self.conflicts[common_prefix_len..]            .iter()            .rev()            .zip(other.conflicts[common_prefix_len..].iter().rev())            .take_while(|(old, new)| old == new)            .count();        let old_conflicts =            &self.conflicts[common_prefix_len..(self.conflicts.len() - common_suffix_len)];        let new_conflicts =            &other.conflicts[common_prefix_len..(other.conflicts.len() - common_suffix_len)];        let old_range = common_prefix_len..(common_prefix_len + old_conflicts.len());        let new_range = common_prefix_len..(common_prefix_len + new_conflicts.len());        let start = match (old_conflicts.first(), new_conflicts.first()) {            (None, None) => None,            (None, Some(conflict)) => Some(conflict.range.start),            (Some(conflict), None) => Some(conflict.range.start),            (Some(first), Some(second)) => Some(first.range.start.min(&second.range.start, buffer)),        };        let end = match (old_conflicts.last(), new_conflicts.last()) {            (None, None) => None,            (None, Some(conflict)) => Some(conflict.range.end),            (Some(first), None) => Some(first.range.end),            (Some(first), Some(second)) => Some(first.range.end.max(&second.range.end, buffer)),        };        ConflictSetUpdate {            buffer_range: start.zip(end).map(|(start, end)| start..end),            old_range,            new_range,        }    }} #[derive(Debug, Clone, PartialEq, Eq)]pub struct ConflictRegion {    pub range: Range<Anchor>,    pub ours: Range<Anchor>,    pub theirs: Range<Anchor>,    pub base: Option<Range<Anchor>>,} impl ConflictRegion {    pub fn resolve(        &self,        buffer: Entity<language::Buffer>,        ranges: &[Range<Anchor>],        cx: &mut App,    ) {        let buffer_snapshot = buffer.read(cx).snapshot();        let mut deletions = Vec::new();        let empty = "";        let outer_range = self.range.to_offset(&buffer_snapshot);        let mut offset = outer_range.start;        for kept_range in ranges {            let kept_range = kept_range.to_offset(&buffer_snapshot);            if kept_range.start > offset {                deletions.push((offset..kept_range.start, empty));            }            offset = kept_range.end;        }        if outer_range.end > offset {            deletions.push((offset..outer_range.end, empty));        }         buffer.update(cx, |buffer, cx| {            buffer.edit(deletions, None, cx);        });    }} impl ConflictSet {    pub fn new(buffer_id: BufferId, has_conflict: bool, _: &mut Context<Self>) -> Self {        Self {            has_conflict,            snapshot: ConflictSetSnapshot {                buffer_id,                conflicts: Default::default(),            },        }    }     pub fn set_has_conflict(&mut self, has_conflict: bool, cx: &mut Context<Self>) -> bool {        if has_conflict != self.has_conflict {            self.has_conflict = has_conflict;            if !self.has_conflict {                cx.emit(ConflictSetUpdate {                    buffer_range: None,                    old_range: 0..self.snapshot.conflicts.len(),                    new_range: 0..0,                });                self.snapshot.conflicts = Default::default();            }            true        } else {            false        }    }     pub fn snapshot(&self) -> ConflictSetSnapshot {        self.snapshot.clone()    }     pub fn set_snapshot(        &mut self,        snapshot: ConflictSetSnapshot,        update: ConflictSetUpdate,        cx: &mut Context<Self>,    ) {        self.snapshot = snapshot;        cx.emit(update);    }     pub fn parse(buffer: &text::BufferSnapshot) -> ConflictSetSnapshot {        let mut conflicts = Vec::new();         let mut line_pos = 0;        let buffer_len = buffer.len();        let mut lines = buffer.text_for_range(0..buffer_len).lines();         let mut conflict_start: Option<usize> = None;        let mut ours_start: Option<usize> = None;        let mut ours_end: Option<usize> = None;        let mut base_start: Option<usize> = None;        let mut base_end: Option<usize> = None;        let mut theirs_start: Option<usize> = None;         while let Some(line) = lines.next() {            let line_end = line_pos + line.len();             if line.starts_with("<<<<<<< ") {                // If we see a new conflict marker while already parsing one,                // abandon the previous one and start a new one                conflict_start = Some(line_pos);                ours_start = Some(line_end + 1);            } else if line.starts_with("||||||| ")                && conflict_start.is_some()                && ours_start.is_some()            {                ours_end = Some(line_pos);                base_start = Some(line_end + 1);            } else if line.starts_with("=======")                && conflict_start.is_some()                && ours_start.is_some()            {                // Set ours_end if not already set (would be set if we have base markers)                if ours_end.is_none() {                    ours_end = Some(line_pos);                } else if base_start.is_some() {                    base_end = Some(line_pos);                }                theirs_start = Some(line_end + 1);            } else if line.starts_with(">>>>>>> ")                && conflict_start.is_some()                && ours_start.is_some()                && ours_end.is_some()                && theirs_start.is_some()            {                let theirs_end = line_pos;                let conflict_end = (line_end + 1).min(buffer_len);                 let range = buffer.anchor_after(conflict_start.unwrap())                    ..buffer.anchor_before(conflict_end);                let ours = buffer.anchor_after(ours_start.unwrap())                    ..buffer.anchor_before(ours_end.unwrap());                let theirs =                    buffer.anchor_after(theirs_start.unwrap())..buffer.anchor_before(theirs_end);                 let base = base_start                    .zip(base_end)                    .map(|(start, end)| buffer.anchor_after(start)..buffer.anchor_before(end));                 conflicts.push(ConflictRegion {                    range,                    ours,                    theirs,                    base,                });                 conflict_start = None;                ours_start = None;                ours_end = None;                base_start = None;                base_end = None;                theirs_start = None;            }             line_pos = line_end + 1;        }         ConflictSetSnapshot {            conflicts: conflicts.into(),            buffer_id: buffer.remote_id(),        }    }} impl EventEmitter<ConflictSetUpdate> for ConflictSet {} #[cfg(test)]mod tests {    use std::{path::Path, sync::mpsc};     use crate::{Project, project_settings::ProjectSettings};     use super::*;    use fs::FakeFs;    use git::status::{UnmergedStatus, UnmergedStatusCode};    use gpui::{BackgroundExecutor, TestAppContext};    use language::language_settings::AllLanguageSettings;    use serde_json::json;    use settings::Settings as _;    use text::{Buffer, BufferId, Point, ToOffset as _};    use unindent::Unindent as _;    use util::path;    use worktree::WorktreeSettings;     #[test]    fn test_parse_conflicts_in_buffer() {        // Create a buffer with conflict markers        let test_content = r#"            This is some text before the conflict.            <<<<<<< HEAD            This is our version            =======            This is their version            >>>>>>> branch-name             Another conflict:            <<<<<<< HEAD            Our second change            ||||||| merged common ancestors            Original content            =======            Their second change            >>>>>>> branch-name        "#        .unindent();         let buffer_id = BufferId::new(1).unwrap();        let buffer = Buffer::new(0, buffer_id, test_content);        let snapshot = buffer.snapshot();         let conflict_snapshot = ConflictSet::parse(&snapshot);        assert_eq!(conflict_snapshot.conflicts.len(), 2);         let first = &conflict_snapshot.conflicts[0];        assert!(first.base.is_none());        let our_text = snapshot            .text_for_range(first.ours.clone())            .collect::<String>();        let their_text = snapshot            .text_for_range(first.theirs.clone())            .collect::<String>();        assert_eq!(our_text, "This is our version\n");        assert_eq!(their_text, "This is their version\n");         let second = &conflict_snapshot.conflicts[1];        assert!(second.base.is_some());        let our_text = snapshot            .text_for_range(second.ours.clone())            .collect::<String>();        let their_text = snapshot            .text_for_range(second.theirs.clone())            .collect::<String>();        let base_text = snapshot            .text_for_range(second.base.as_ref().unwrap().clone())            .collect::<String>();        assert_eq!(our_text, "Our second change\n");        assert_eq!(their_text, "Their second change\n");        assert_eq!(base_text, "Original content\n");         // Test conflicts_in_range        let range = snapshot.anchor_before(0)..snapshot.anchor_before(snapshot.len());        let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);        assert_eq!(conflicts_in_range.len(), 2);         // Test with a range that includes only the first conflict        let first_conflict_end = conflict_snapshot.conflicts[0].range.end;        let range = snapshot.anchor_before(0)..first_conflict_end;        let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);        assert_eq!(conflicts_in_range.len(), 1);         // Test with a range that includes only the second conflict        let second_conflict_start = conflict_snapshot.conflicts[1].range.start;        let range = second_conflict_start..snapshot.anchor_before(snapshot.len());        let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);        assert_eq!(conflicts_in_range.len(), 1);         // Test with a range that doesn't include any conflicts        let range = buffer.anchor_after(first_conflict_end.to_offset(&buffer) + 1)            ..buffer.anchor_before(second_conflict_start.to_offset(&buffer) - 1);        let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);        assert_eq!(conflicts_in_range.len(), 0);    }     #[test]    fn test_nested_conflict_markers() {        // Create a buffer with nested conflict markers        let test_content = r#"            This is some text before the conflict.            <<<<<<< HEAD            This is our version            <<<<<<< HEAD            This is a nested conflict marker            =======            This is their version in a nested conflict            >>>>>>> branch-nested            =======            This is their version            >>>>>>> branch-name        "#        .unindent();         let buffer_id = BufferId::new(1).unwrap();        let buffer = Buffer::new(0, buffer_id, test_content.to_string());        let snapshot = buffer.snapshot();         let conflict_snapshot = ConflictSet::parse(&snapshot);         assert_eq!(conflict_snapshot.conflicts.len(), 1);         // The conflict should have our version, their version, but no base        let conflict = &conflict_snapshot.conflicts[0];        assert!(conflict.base.is_none());         // Check that the nested conflict was detected correctly        let our_text = snapshot            .text_for_range(conflict.ours.clone())            .collect::<String>();        assert_eq!(our_text, "This is a nested conflict marker\n");        let their_text = snapshot            .text_for_range(conflict.theirs.clone())            .collect::<String>();        assert_eq!(their_text, "This is their version in a nested conflict\n");    }     #[test]    fn test_conflict_markers_at_eof() {        let test_content = r#"            <<<<<<< ours            =======            This is their version            >>>>>>> "#            .unindent();        let buffer_id = BufferId::new(1).unwrap();        let buffer = Buffer::new(0, buffer_id, test_content.to_string());        let snapshot = buffer.snapshot();         let conflict_snapshot = ConflictSet::parse(&snapshot);        assert_eq!(conflict_snapshot.conflicts.len(), 1);    }     #[test]    fn test_conflicts_in_range() {        // Create a buffer with conflict markers        let test_content = r#"            one            <<<<<<< HEAD1            two            =======            three            >>>>>>> branch1            four            five            <<<<<<< HEAD2            six            =======            seven            >>>>>>> branch2            eight            nine            <<<<<<< HEAD3            ten            =======            eleven            >>>>>>> branch3            twelve            <<<<<<< HEAD4            thirteen            =======            fourteen            >>>>>>> branch4            fifteen        "#        .unindent();         let buffer_id = BufferId::new(1).unwrap();        let buffer = Buffer::new(0, buffer_id, test_content.clone());        let snapshot = buffer.snapshot();         let conflict_snapshot = ConflictSet::parse(&snapshot);        assert_eq!(conflict_snapshot.conflicts.len(), 4);         let range = test_content.find("seven").unwrap()..test_content.find("eleven").unwrap();        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);        assert_eq!(            conflict_snapshot.conflicts_in_range(range, &snapshot),            &conflict_snapshot.conflicts[1..=2]        );         let range = test_content.find("one").unwrap()..test_content.find("<<<<<<< HEAD2").unwrap();        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);        assert_eq!(            conflict_snapshot.conflicts_in_range(range, &snapshot),            &conflict_snapshot.conflicts[0..=1]        );         let range =            test_content.find("eight").unwrap() - 1..test_content.find(">>>>>>> branch3").unwrap();        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);        assert_eq!(            conflict_snapshot.conflicts_in_range(range, &snapshot),            &conflict_snapshot.conflicts[1..=2]        );         let range = test_content.find("thirteen").unwrap() - 1..test_content.len();        let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);        assert_eq!(            conflict_snapshot.conflicts_in_range(range, &snapshot),            &conflict_snapshot.conflicts[3..=3]        );    }     #[gpui::test]    async fn test_conflict_updates(executor: BackgroundExecutor, cx: &mut TestAppContext) {        zlog::init_test();        cx.update(|cx| {            settings::init(cx);            WorktreeSettings::register(cx);            ProjectSettings::register(cx);            AllLanguageSettings::register(cx);        });        let initial_text = "            one            two            three            four            five        "        .unindent();        let fs = FakeFs::new(executor);        fs.insert_tree(            path!("/project"),

Lines 1–500 of 680.