This adds a simple text diff for both tracked and untracked changes in the hover panel, which could be later expanded to include contextual actions such as amending and viewing hunks.
DUZBRB3U7ERDXRM6A7NFI4IGUB6IR4V2L7JJSLCQBSJYJDUZQR6AC }#[derive(Debug, thiserror::Error)]pub enum ActiveHunkError<C: std::error::Error + 'static> {#[error("File is not tracked by Pijul")]Untracked,#[error("Missing file contents for hunk: {hunk:#?}")]MissingFileContents {hunk: Box<BaseHunk<Atom<Option<libpijul::Hash>>, libpijul::change::Local>>,},#[error("Change not found: {change_id:?}")]ChangeNotFound { change_id: ChangeId },#[error("No hunk found for vertex: {vertex:?}")]NoHunkForVertex { vertex: Vertex<ChangeId> },#[error("Failed to get change contents: {0}")]ChangeContents(#[from] TextSerError<C>),#[error("Hunk not yet implemented: {hunk:#?}")]UnimplementedHunk {hunk: Box<BaseHunk<Atom<Option<libpijul::Hash>>, libpijul::change::Local>>,},
pub fn get_change_header(&self, change_id: &ChangeId) -> Option<&ChangeHeader> {self.channel_state.get_change_header(change_id)
pub fn get_change(&self, change_id: &ChangeId) -> Option<&Change> {self.channel_state.get_change(change_id)}pub fn get_active_hunk(&self,open_file: &OpenFile,credit_source: CreditSource,) -> Result<ActiveHunk, ActiveHunkError<C::Error>> {let Some(credits) = open_file.credits.as_ref() else {return Err(ActiveHunkError::Untracked);};let change_contents = match credit_source {CreditSource::Tracked { vertex } => {let Some(change) = self.channel_state.get_change(&vertex.change) else {return Err(ActiveHunkError::ChangeNotFound {change_id: vertex.change,});};&change.contents}CreditSource::Untracked { hunk_index: _ } => &credits.unrecorded_state.change_contents,};let (hunk_index, hunk) = match credit_source {CreditSource::Tracked { vertex } => self.channel_state.find_vertex_hunk(vertex).ok_or_else(|| ActiveHunkError::NoHunkForVertex { vertex })?,CreditSource::Untracked { hunk_index } => {// Return 1-indexed hunks(hunk_index + 1, &credits.unrecorded_state.hunks[hunk_index])}};// TODO: properly handle encodinglet hunk_diff = match hunk {BaseHunk::FileAdd {contents,add_name: _,add_inode: _,path: _,encoding,} => {let Some(contents) = contents else {return Err(ActiveHunkError::MissingFileContents {hunk: Box::new(hunk.clone()),});};let file_contents = libpijul::change::get_change_contents(&self.change_store,contents,change_contents,)?;let file_text = String::from_utf8(file_contents).unwrap();HunkDiff::TextChange {lines_added: file_text.lines().map(str::to_string).collect(),lines_removed: Vec::new(),}}BaseHunk::Replacement {change,replacement,local: _,encoding,} => {let old_contents = libpijul::change::get_change_contents(&self.change_store,change,change_contents,)?;let new_contents = libpijul::change::get_change_contents(&self.change_store,replacement,change_contents,)?;let old_text = String::from_utf8(old_contents).unwrap();let new_text = String::from_utf8(new_contents).unwrap();HunkDiff::TextChange {lines_added: new_text.lines().map(str::to_string).collect(),lines_removed: old_text.lines().map(str::to_string).collect(),}}BaseHunk::Edit {change,local: _,encoding,} => {let new_contents = libpijul::change::get_change_contents(&self.change_store,change,change_contents,)?;let text = String::from_utf8(new_contents).unwrap();if let Atom::EdgeMap(edge) = change&& (edge.edges.is_empty() || edge.edges[0].flag.is_deleted()){HunkDiff::TextChange {lines_added: Vec::new(),lines_removed: text.lines().map(str::to_string).collect(),}} else {HunkDiff::TextChange {lines_added: text.lines().map(str::to_string).collect(),lines_removed: Vec::new(),}}}_ => {return Err(ActiveHunkError::UnimplementedHunk {hunk: Box::new(hunk.clone()),});}};Ok(ActiveHunk {index: hunk_index,diff: hunk_diff,})
pub fn get_change_header(&self, change_id: &ChangeId) -> Option<&ChangeHeader> {
pub fn find_vertex_hunk(&self,vertex: Vertex<ChangeId>,) -> Option<(usize, &BaseHunk<Atom<Option<Hash>>, Local>)> {let change = self.changes.get(&vertex.change)?;change.changes.iter().enumerate().find_map(|(index, hunk)| {if hunk.iter().any(|atom| match atom {Atom::NewVertex(new_vertex) => {// Changes that depend on this one may later fragment the original contents,// so the current vertex may be a subset of the original contents.new_vertex.start <= vertex.start && new_vertex.end >= vertex.end}Atom::EdgeMap(_edge_map) => false,}) {// Hunks are 1-indexedSome((index + 1, hunk))} else {None}})}pub fn get_change(&self, change_id: &ChangeId) -> Option<&Change> {
{{ hunk_template }}
**{{ heading }}**```diff{%- for removed_line in lines_removed %}- {{removed_line}}{%- endfor %}{%- for added_line in lines_added %}+ {{added_line}}{%- endfor %}```
CreditSource::Tracked(change_id) => {let change_header = repository.get_change_header(change_id).ok_or_else(|| {napi::Error::from_reason(format!("unable to get change {change_id:#?}"))
CreditSource::Tracked { vertex } => {let change = repository.get_change(&vertex.change).ok_or_else(|| {napi::Error::from_reason(format!("Unable to get change for verte {vertex:#?}"))
let active_hunk = repository.get_active_hunk(open_file, credit_source).map_err(|error| napi::Error::from_reason(format!("Unable to get active hunk: {error}")))?;let mut hunk_heading_buffer = String::new();let hunk_heading = HunkHeading {index: active_hunk.index,};hunk_heading.localize(localization_context, &mut hunk_heading_buffer);let hunk_template = match active_hunk.diff {HunkDiff::TextChange {lines_added,lines_removed,} => HunkHoverTemplate {heading: hunk_heading_buffer,lines_added,lines_removed,},};
CreditSource::Tracked(change_id) => {let change_header = repository.get_change_header(change_id).ok_or_else(|| {napi::Error::from_reason(format!("unable to get change {change_id:#?}"))
CreditSource::Tracked { vertex } => {let change = repository.get_change(&vertex.change).ok_or_else(|| {napi::Error::from_reason(format!("Unable to get change for vertex: {vertex:#?}"))