Git · conflict markers take their width and whole lines; marked, resolve and conflicts share it
This commit is contained in:
3 files changed
+54
-25
No files matched your search
+3
-3
@@ -8,7 +8,7 @@ use gpui::{
|
||||
|
||||
use similar::DiffableStr;
|
||||
|
||||
use super::merging::{Region, RegionKind, Take, conflicts, resolve};
|
||||
use super::merging::{MARKER, Region, RegionKind, Take, conflicts, resolve};
|
||||
use crate::{
|
||||
buttons::{Button, ButtonVariant},
|
||||
i18n,
|
||||
@@ -298,12 +298,12 @@ impl RenderOnce for ConflictResolver {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
let found = conflicts(&self.text);
|
||||
let found = conflicts(&self.text, MARKER);
|
||||
let text = self.text.to_string();
|
||||
let on_take: Option<OnTake> = self.on_resolve.map(|on_resolve| {
|
||||
Rc::new(
|
||||
move |ix: usize, take: Take, window: &mut Window, cx: &mut App| {
|
||||
if let Some(settled) = resolve(&text, ix, take) {
|
||||
if let Some(settled) = resolve(&text, ix, take, MARKER) {
|
||||
on_resolve(settled, window, cx)
|
||||
}
|
||||
},
|
||||
|
||||
+50
-21
@@ -136,18 +136,28 @@ pub struct Conflict {
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
/// The conflicts marked in `text`, markers at line starts.
|
||||
pub fn conflicts(text: &str) -> Vec<Conflict> {
|
||||
/// Git's conflict marker width, unless `conflict-marker-size` says otherwise.
|
||||
pub const MARKER: usize = 7;
|
||||
|
||||
/// A marker line: `size` of `mark` exactly, then a label if `labelled`.
|
||||
fn marker(line: &str, mark: char, size: usize, labelled: bool) -> bool {
|
||||
let line = line.trim_end_matches(['\n', '\r']);
|
||||
let run = line.chars().take_while(|c| *c == mark).count();
|
||||
run == size && (line.len() == size || (labelled && line[size..].starts_with(' ')))
|
||||
}
|
||||
|
||||
/// The conflicts marked in `text` with markers `size` wide.
|
||||
pub fn conflicts(text: &str, size: usize) -> Vec<Conflict> {
|
||||
let mut found = Vec::new();
|
||||
let (mut start, mut base, mut middle) = (None, None, None);
|
||||
for (ix, line) in text.tokenize_lines().into_iter().enumerate() {
|
||||
if line.starts_with("<<<<<<<") {
|
||||
if marker(line, '<', size, true) {
|
||||
(start, base, middle) = (Some(ix), None, None);
|
||||
} else if line.starts_with("|||||||") && start.is_some() && middle.is_none() {
|
||||
} else if marker(line, '|', size, true) && start.is_some() && middle.is_none() {
|
||||
base = Some(ix);
|
||||
} else if line.starts_with("=======") && start.is_some() {
|
||||
} else if marker(line, '=', size, false) && start.is_some() && middle.is_none() {
|
||||
middle = Some(ix);
|
||||
} else if line.starts_with(">>>>>>>")
|
||||
} else if marker(line, '>', size, true)
|
||||
&& let (Some(start), Some(middle)) = (start.take(), middle.take())
|
||||
{
|
||||
found.push(Conflict {
|
||||
@@ -162,7 +172,7 @@ pub fn conflicts(text: &str) -> Vec<Conflict> {
|
||||
}
|
||||
|
||||
/// Regions as Git grouped marked `text`; base from diff3.
|
||||
pub fn marked(text: &str) -> Vec<Region> {
|
||||
pub fn marked(text: &str, size: usize) -> Vec<Region> {
|
||||
let lines: Vec<String> = text
|
||||
.tokenize_lines()
|
||||
.into_iter()
|
||||
@@ -176,7 +186,7 @@ pub fn marked(text: &str) -> Vec<Region> {
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
let mut at = 0;
|
||||
for conflict in conflicts(text) {
|
||||
for conflict in conflicts(text, size) {
|
||||
if conflict.start > at {
|
||||
out.push(same(at..conflict.start));
|
||||
}
|
||||
@@ -198,8 +208,8 @@ pub fn marked(text: &str) -> Vec<Region> {
|
||||
}
|
||||
|
||||
/// `text` with conflict `ix` settled; none if absent.
|
||||
pub fn resolve(text: &str, ix: usize, take: Take) -> Option<String> {
|
||||
let Some(conflict) = conflicts(text).get(ix).copied() else {
|
||||
pub fn resolve(text: &str, ix: usize, take: Take, size: usize) -> Option<String> {
|
||||
let Some(conflict) = conflicts(text, size).get(ix).copied() else {
|
||||
log::error!("no conflict {ix} to resolve in this text");
|
||||
return None;
|
||||
};
|
||||
@@ -227,10 +237,22 @@ pub fn resolve(text: &str, ix: usize, take: Take) -> Option<String> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn markers_hold_their_width_and_whole_lines() {
|
||||
let wide = "a\n<<<<<<<<<< ours\nmine\n==========\ntop\n======= literal\nend\n>>>>>>>>>> theirs\nz\n";
|
||||
assert!(conflicts(wide, MARKER).is_empty(), "seven is not ten");
|
||||
assert_eq!(
|
||||
resolve(wide, 0, Take::Theirs, 10).as_deref(),
|
||||
Some("a\ntop\n======= literal\nend\nz\n")
|
||||
);
|
||||
let five = "<<<<< ours\nx\n=====\ny\n>>>>> theirs\n";
|
||||
assert_eq!(conflicts(five, 5).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marked_text_keeps_gits_grouping_and_its_base() {
|
||||
let text = "first\n<<<<<<< ours\nOURS a\nunchanged\nOURS b\n||||||| base\na\nunchanged\nb\n=======\nTHEIRS a\nunchanged\nTHEIRS b\n>>>>>>> theirs\nlast\n";
|
||||
let found = marked(text);
|
||||
let found = marked(text, MARKER);
|
||||
let kinds: Vec<RegionKind> = found.iter().map(|region| region.kind).collect();
|
||||
assert_eq!(
|
||||
kinds,
|
||||
@@ -249,13 +271,13 @@ mod tests {
|
||||
#[test]
|
||||
fn a_diff3_conflict_leaves_its_base_out() {
|
||||
let text = "a\n<<<<<<< HEAD\nours\n||||||| base\nold\n=======\ntheirs\n>>>>>>> side\nz\n";
|
||||
assert_eq!(conflicts(text)[0].base, Some(3));
|
||||
assert_eq!(conflicts(text, MARKER)[0].base, Some(3));
|
||||
assert_eq!(
|
||||
resolve(text, 0, Take::Ours).as_deref(),
|
||||
resolve(text, 0, Take::Ours, MARKER).as_deref(),
|
||||
Some("a\nours\nz\n")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(text, 0, Take::Both).as_deref(),
|
||||
resolve(text, 0, Take::Both, MARKER).as_deref(),
|
||||
Some("a\nours\ntheirs\nz\n")
|
||||
);
|
||||
}
|
||||
@@ -291,7 +313,7 @@ mod tests {
|
||||
assert_eq!(result(&bare, &[Some(Take::Both)]).0, "b\nc");
|
||||
let marked = "a\r\n<<<<<<< HEAD\r\nours\r\n=======\r\ntheirs\r\n>>>>>>> topic\r\nz";
|
||||
assert_eq!(
|
||||
resolve(marked, 0, Take::Ours).as_deref(),
|
||||
resolve(marked, 0, Take::Ours, MARKER).as_deref(),
|
||||
Some("a\r\nours\r\nz")
|
||||
);
|
||||
}
|
||||
@@ -302,8 +324,15 @@ mod tests {
|
||||
assert_eq!(result(&changed, &[]).0, "a\rB\n");
|
||||
let held = result(®ions("a\r", "b\r", "c\r"), &[]).0;
|
||||
assert_eq!(held, "<<<<<<< ours\rb\r=======\rc\r>>>>>>> theirs\r");
|
||||
assert_eq!(resolve(&held, 0, Take::Theirs).as_deref(), Some("c\r"));
|
||||
assert_eq!(resolve("plain\n", 0, Take::Ours), None, "no conflict left");
|
||||
assert_eq!(
|
||||
resolve(&held, 0, Take::Theirs, MARKER).as_deref(),
|
||||
Some("c\r")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve("plain\n", 0, Take::Ours, MARKER),
|
||||
None,
|
||||
"no conflict left"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -316,7 +345,7 @@ mod tests {
|
||||
fn markers_resolve_to_one_side_or_both() {
|
||||
let marked = "a\n<<<<<<< HEAD\nours\n=======\ntheirs\n>>>>>>> topic\nz\n";
|
||||
assert_eq!(
|
||||
conflicts(marked),
|
||||
conflicts(marked, MARKER),
|
||||
[Conflict {
|
||||
start: 1,
|
||||
base: None,
|
||||
@@ -325,15 +354,15 @@ mod tests {
|
||||
}]
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(marked, 0, Take::Ours).as_deref(),
|
||||
resolve(marked, 0, Take::Ours, MARKER).as_deref(),
|
||||
Some("a\nours\nz\n")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve(marked, 0, Take::Both).as_deref(),
|
||||
resolve(marked, 0, Take::Both, MARKER).as_deref(),
|
||||
Some("a\nours\ntheirs\nz\n")
|
||||
);
|
||||
assert!(
|
||||
conflicts("a\n=======\nb\n").is_empty(),
|
||||
conflicts("a\n=======\nb\n", MARKER).is_empty(),
|
||||
"a rule alone is no conflict"
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -197,7 +197,6 @@ impl RenderOnce for BranchSelector {
|
||||
}
|
||||
}
|
||||
|
||||
/// A row's name, detail, trailing words and hover actions.
|
||||
/// A pickable row: its spoken name, whether picked, the pick.
|
||||
pub(super) struct Pick {
|
||||
pub spoken: SharedString,
|
||||
@@ -205,6 +204,7 @@ pub(super) struct Pick {
|
||||
pub run: Run,
|
||||
}
|
||||
|
||||
/// A row's name, detail, trailing words and hover actions.
|
||||
pub(super) fn listed(
|
||||
id: ElementId,
|
||||
icon: IconName,
|
||||
|
||||
Reference in new issue
Block a user