#![expect(missing_docs)]
use std::borrow::Borrow;
use std::mem;
use bstr::BString;
use itertools::Itertools as _;
use crate::backend::BackendResult;
use crate::conflicts::MaterializedFileValue;
use crate::diff::CompareBytesExactly;
use crate::diff::CompareBytesIgnoreAllWhitespace;
use crate::diff::CompareBytesIgnoreWhitespaceAmount;
use crate::diff::ContentDiff;
use crate::diff::DiffHunk;
use crate::diff::DiffHunkKind;
use crate::diff::find_line_ranges;
use crate::merge::Diff;
use crate::repo_path::RepoPath;
pub mod unified;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DiffTokenType {
Matching,
Different,
}
type DiffTokenVec<'content> = Vec<(DiffTokenType, &'content [u8])>;
#[derive(Clone, Debug)]
pub struct FileContent<T> {
pub is_binary: bool,
pub contents: T,
}
pub async fn file_content_for_diff<T>(
path: &RepoPath,
file: &mut MaterializedFileValue,
map_resolved: impl FnOnce(BString) -> T,
) -> BackendResult<FileContent<T>> {
const PEEK_SIZE: usize = 8000;
let contents = BString::new(file.read_all(path).await?);
let start = &contents[..PEEK_SIZE.min(contents.len())];
Ok(FileContent {
is_binary: start.contains(&b'\0'),
contents: map_resolved(contents),
})
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum LineCompareMode {
#[default]
Exact,
IgnoreAllSpace,
IgnoreSpaceChange,
}
pub fn diff_by_line<'input, T: AsRef<[u8]> + ?Sized + 'input>(
inputs: impl IntoIterator<Item = &'input T>,
options: &LineCompareMode,
) -> ContentDiff<'input> {
match options {
LineCompareMode::Exact => {
ContentDiff::for_tokenizer(inputs, find_line_ranges, CompareBytesExactly)
}
LineCompareMode::IgnoreAllSpace => {
ContentDiff::for_tokenizer(inputs, find_line_ranges, CompareBytesIgnoreAllWhitespace)
}
LineCompareMode::IgnoreSpaceChange => {
ContentDiff::for_tokenizer(inputs, find_line_ranges, CompareBytesIgnoreWhitespaceAmount)
}
}
}
pub fn unzip_diff_hunks_to_lines<'content, I>(diff_hunks: I) -> Diff<Vec<DiffTokenVec<'content>>>
where
I: IntoIterator,
I::Item: Borrow<DiffHunk<'content>>,
{
let mut left_lines: Vec<DiffTokenVec<'content>> = vec![];
let mut right_lines: Vec<DiffTokenVec<'content>> = vec![];
let mut left_tokens: DiffTokenVec<'content> = vec![];
let mut right_tokens: DiffTokenVec<'content> = vec![];
for hunk in diff_hunks {
let hunk = hunk.borrow();
match hunk.kind {
DiffHunkKind::Matching => {
debug_assert!(hunk.contents.iter().all_equal());
for token in hunk.contents[0].split_inclusive(|b| *b == b'\n') {
left_tokens.push((DiffTokenType::Matching, token));
right_tokens.push((DiffTokenType::Matching, token));
if token.ends_with(b"\n") {
left_lines.push(mem::take(&mut left_tokens));
right_lines.push(mem::take(&mut right_tokens));
}
}
}
DiffHunkKind::Different => {
let [left, right] = hunk.contents[..]
.try_into()
.expect("hunk should have exactly two inputs");
for token in left.split_inclusive(|b| *b == b'\n') {
left_tokens.push((DiffTokenType::Different, token));
if token.ends_with(b"\n") {
left_lines.push(mem::take(&mut left_tokens));
}
}
for token in right.split_inclusive(|b| *b == b'\n') {
right_tokens.push((DiffTokenType::Different, token));
if token.ends_with(b"\n") {
right_lines.push(mem::take(&mut right_tokens));
}
}
}
}
}
if !left_tokens.is_empty() {
left_lines.push(left_tokens);
}
if !right_tokens.is_empty() {
right_lines.push(right_tokens);
}
Diff::new(left_lines, right_lines)
}