use std::ops::RangeInclusive;
use std::sync::OnceLock;
use camino::Utf8Path;
use libpijul::changestore::ChangeStore;
use libpijul::pristine::sanakirja::{MutTxn, SanakirjaError};
use libpijul::vertex_buffer::VertexBuffer;
use libpijul::{ArcTxn, ChangeId, ChannelRef, TxnTExt, Vertex};
use crate::file_system::changes::{CreditSource, Span};
#[derive(Debug, thiserror::Error)]
pub enum RecordedStateError<C: std::error::Error + 'static> {
#[error("unable to find oldest vertex: {0:#?}")]
OldestVertex(#[from] libpijul::fs::FsErrorC<C, MutTxn<()>>),
#[error("unable to output file: {0:#?}")]
OutputFile(#[from] libpijul::output::FileError<C, MutTxn<()>>),
#[error("unable to get external hash for {change_id:#?}")]
ExternalHash {
change_id: libpijul::pristine::ChangeId,
error: libpijul::pristine::TxnErr<SanakirjaError>,
},
#[error("no matching external hash for change ID: {0:#?}")]
MissingExternalHash(libpijul::pristine::ChangeId),
#[error("unable to get hash from changestore: {0:#?}")]
ChangeStore(C),
}
struct ChangesBuffer {
introduced_by: OnceLock<ChangeId>,
original_contents: String,
spans: Vec<Span<Vertex<ChangeId>>>,
}
#[derive(Clone, Debug)]
pub struct RecordedState {
pub introduced_by: ChangeId,
pub original_contents: String,
pub spans: Vec<Span<Vertex<ChangeId>>>,
}
impl RecordedState {
pub fn new<C>(
path: &Utf8Path,
transaction: &ArcTxn<MutTxn<()>>,
channel: &ChannelRef<MutTxn<()>>,
change_store: &C,
) -> Result<Self, RecordedStateError<C::Error>>
where
C: ChangeStore,
{
let (oldest_vertex, _ambiguous) =
transaction
.read()
.follow_oldest_path(change_store, channel, path.as_str())?;
let mut changes_buffer = ChangesBuffer {
introduced_by: OnceLock::new(),
original_contents: String::new(),
spans: Vec::new(),
};
libpijul::output::output_file(
change_store,
transaction,
channel,
oldest_vertex,
&mut changes_buffer,
)?;
Ok(Self {
introduced_by: changes_buffer.introduced_by.take().unwrap(),
original_contents: changes_buffer.original_contents,
spans: changes_buffer.spans,
})
}
pub fn spans_within_lines(
&self,
untracked_line_range: RangeInclusive<usize>,
offset: isize,
) -> impl Iterator<Item = Span<CreditSource>> {
let tracked_line_range = untracked_line_range.start().strict_add_signed(offset)
..=untracked_line_range.end().strict_add_signed(offset);
Span::spans_within_lines(&self.spans, tracked_line_range).map(move |span| Span {
value: CreditSource::Tracked { vertex: span.value },
lines: (span.lines.start().strict_sub_signed(offset))
..=(span.lines.end().strict_sub_signed(offset)),
})
}
}
impl VertexBuffer for ChangesBuffer {
fn output_line<E, F>(&mut self, vertex: Vertex<ChangeId>, contents: F) -> Result<(), E>
where
E: From<std::io::Error>,
F: FnOnce(&mut [u8]) -> Result<(), E>,
{
if self.introduced_by.get().is_none() {
self.introduced_by.set(vertex.change).unwrap();
assert!(vertex.is_empty());
return Ok(());
}
if vertex.change.0.as_u64() == 0 {
return Ok(());
}
assert!(!vertex.is_empty());
assert!(!vertex.is_root());
let mut buffer = vec![0; vertex.end - vertex.start];
contents(&mut buffer)?;
let contents = String::from_utf8(buffer).unwrap();
self.original_contents.push_str(&contents);
let start_line = match self.spans.last() {
Some(previous_span) => previous_span.next_span_start(),
None => 0,
};
let length = contents.lines().count();
self.spans.push(Span {
value: vertex,
lines: start_line..=(start_line + length - 1),
});
Ok(())
}
fn output_conflict_marker<C: libpijul::changestore::ChangeStore>(
&mut self,
s: &str,
id: usize,
sides: Option<(&C, &[&libpijul::Hash])>,
) -> Result<(), std::io::Error> {
todo!("Conflict markers")
}
}