use camino::Utf8Path;
use libpijul::change::{Atom, BaseHunk, TextSerError};
use libpijul::changestore::ChangeStore;
use libpijul::pristine::sanakirja::{MutTxn, SanakirjaError};
use libpijul::working_copy::WorkingCopyRead;
use libpijul::{ArcTxn, ChannelRef, RecordBuilder};
use crate::file_system::changes::{HunkOffset, Span};
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub enum UnrecordedChangesError<C: std::error::Error + 'static, W: std::error::Error + 'static> {
ChangeContents(#[from] TextSerError<C>),
Globalize(#[from] SanakirjaError),
Record(#[from] libpijul::record::RecordError<C, W, MutTxn<()>>),
}
#[derive(Clone, Debug)]
pub struct UnrecordedState {
pub change_contents: Vec<u8>,
pub hunks: Vec<BaseHunk<Atom<Option<libpijul::Hash>>, libpijul::change::Local>>,
pub spans: Vec<Span<HunkOffset>>,
}
impl UnrecordedState {
pub fn new<C, W>(
path: &Utf8Path,
transaction: &ArcTxn<MutTxn<()>>,
channel: &ChannelRef<MutTxn<()>>,
change_store: &C,
working_copy: &W,
) -> Result<Self, UnrecordedChangesError<C::Error, W::Error>>
where
C: ChangeStore + Clone + Send + 'static,
W: WorkingCopyRead + Clone + Send + Sync + 'static,
{
let mut unrecorded_changes = RecordBuilder::new();
unrecorded_changes.record(
transaction.clone(),
libpijul::Algorithm::default(),
false, &libpijul::DEFAULT_SEPARATOR,
channel.clone(),
working_copy,
change_store,
path.as_str(),
1, )?;
let unrecorded_state = unrecorded_changes.finish();
let change_contents = unrecorded_state.contents.lock();
let mut unrecorded_spans = Vec::new();
let mut hunks = Vec::with_capacity(unrecorded_state.actions.len());
for hunk in unrecorded_state.actions {
let globalized_hunk = hunk.globalize(&*transaction.read())?;
debug_assert_eq!(globalized_hunk.path(), path.as_str());
match &globalized_hunk {
BaseHunk::Replacement {
change,
replacement,
local,
encoding,
} => {
let old_contents = libpijul::change::get_change_contents(
change_store,
change,
&change_contents,
)?;
let new_contents = libpijul::change::get_change_contents(
change_store,
replacement,
&change_contents,
)?;
let old_text = String::from_utf8(old_contents).unwrap();
let new_text = String::from_utf8(new_contents).unwrap();
let old_line_count = old_text.lines().count().strict_sub(1);
let new_line_count = new_text.lines().count().strict_sub(1);
let span_start = local.line - 1;
unrecorded_spans.push(Span {
value: HunkOffset::Replacement {
old_line_count,
new_line_count,
},
lines: span_start..=span_start.strict_add(new_line_count),
});
}
BaseHunk::Edit {
change,
local,
encoding,
} => {
let new_contents = libpijul::change::get_change_contents(
change_store,
change,
&change_contents,
)?;
let text = String::from_utf8(new_contents).unwrap();
let line_count = text.lines().count();
let span_start = local.line - 1;
let span = if let Atom::EdgeMap(edge) = change
&& (edge.edges.is_empty() || edge.edges[0].flag.is_deleted())
{
Span {
value: HunkOffset::Deletion {
lines_removed: line_count,
},
lines: span_start..=span_start,
}
} else {
Span {
value: HunkOffset::Addition {
lines_added: line_count,
},
lines: span_start..=(span_start + line_count - 1),
}
};
unrecorded_spans.push(span);
}
_ => {
tracing::warn!(message = "Skipping unrecorded hunk", ?globalized_hunk);
}
}
hunks.push(globalized_hunk);
}
unrecorded_spans.sort_by_key(|span| *span.lines.start());
Ok(Self {
change_contents: change_contents.clone(),
hunks,
spans: unrecorded_spans,
})
}
}