diff options
Diffstat (limited to 'src/components/diff.rs')
-rw-r--r-- | src/components/diff.rs | 1528 |
1 files changed, 764 insertions, 764 deletions
diff --git a/src/components/diff.rs b/src/components/diff.rs index 8b715599..c46ab2d0 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -1,801 +1,801 @@ use super::{ - utils::scroll_vertical::VerticalScroll, CommandBlocking, - Direction, DrawableComponent, ScrollType, + utils::scroll_vertical::VerticalScroll, CommandBlocking, + Direction, DrawableComponent, ScrollType, }; use crate::{ - components::{CommandInfo, Component, EventState}, - keys::SharedKeyConfig, - queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, - strings, try_or_popup, - ui::style::SharedTheme, + components::{CommandInfo, Component, EventState}, + keys::SharedKeyConfig, + queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, + strings, try_or_popup, + ui::style::SharedTheme, }; use anyhow::Result; use asyncgit::{ - hash, - sync::{self, diff::DiffLinePosition}, - DiffLine, DiffLineType, FileDiff, CWD, + hash, + sync::{self, diff::DiffLinePosition}, + DiffLine, DiffLineType, FileDiff, CWD, }; use bytesize::ByteSize; use crossterm::event::Event; use std::{borrow::Cow, cell::Cell, cmp, path::Path}; use tui::{ - backend::Backend, - layout::Rect, - symbols, - text::{Span, Spans}, - widgets::{Block, Borders, Paragraph}, - Frame, + backend::Backend, + layout::Rect, + symbols, + text::{Span, Spans}, + widgets::{Block, Borders, Paragraph}, + Frame, }; #[derive(Default)] struct Current { - path: String, - is_stage: bool, - hash: u64, + path: String, + is_stage: bool, + hash: u64, } /// #[derive(Clone, Copy)] enum Selection { - Single(usize), - Multiple(usize, usize), + Single(usize), + Multiple(usize, usize), } impl Selection { - const fn get_start(&self) -> usize { - match self { - Self::Single(start) | Self::Multiple(start, _) => *start, - } - } - - const fn get_end(&self) -> usize { - match self { - Self::Single(end) | Self::Multiple(_, end) => *end, - } - } - - fn get_top(&self) -> usize { - match self { - Self::Single(start) => *start, - Self::Multiple(start, end) => cmp::min(*start, *end), - } - } - - fn get_bottom(&self) -> usize { - match self { - Self::Single(start) => *start, - Self::Multiple(start, end) => cmp::max(*start, *end), - } - } - - fn modify(&mut self, direction: Direction, max: usize) { - let start = self.get_start(); - let old_end = self.get_end(); - - *self = match direction { - Direction::Up => { - Self::Multiple(start, old_end.saturating_sub(1)) - } - - Direction::Down => { - Self::Multiple(start, cmp::min(old_end + 1, max)) - } - }; - } - - fn contains(&self, index: usize) -> bool { - match self { - Self::Single(start) => index == *start, - Self::Multiple(start, end) => { - if start <= end { - *start <= index && index <= *end - } else { - *end <= index && index <= *start - } - } - } - } + const fn get_start(&self) -> usize { + match self { + Self::Single(start) | Self::Multiple(start, _) => *start, + } + } + + const fn get_end(&self) -> usize { + match self { + Self::Single(end) | Self::Multiple(_, end) => *end, + } + } + + fn get_top(&self) -> usize { + match self { + Self::Single(start) => *start, + Self::Multiple(start, end) => cmp::min(*start, *end), + } + } + + fn get_bottom(&self) -> usize { + match self { + Self::Single(start) => *start, + Self::Multiple(start, end) => cmp::max(*start, *end), + } + } + + fn modify(&mut self, direction: Direction, max: usize) { + let start = self.get_start(); + let old_end = self.get_end(); + + *self = match direction { + Direction::Up => { + Self::Multiple(start, old_end.saturating_sub(1)) + } + + Direction::Down => { + Self::Multiple(start, cmp::min(old_end + 1, max)) + } + }; + } + + fn contains(&self, index: usize) -> bool { + match self { + Self::Single(start) => index == *start, + Self::Multiple(start, end) => { + if start <= end { + *start <= index && index <= *end + } else { + *end <= index && index <= *start + } + } + } + } } /// pub struct DiffComponent { - diff: Option<FileDiff>, - pending: bool, - selection: Selection, - selected_hunk: Option<usize>, - current_size: Cell<(u16, u16)>, - focused: bool, - current: Current, - scroll: VerticalScroll, - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - is_immutable: bool, + diff: Option<FileDiff>, + pending: bool, + selection: Selection, + selected_hunk: Option<usize>, + current_size: Cell<(u16, u16)>, + focused: bool, + current: Current, + scroll: VerticalScroll, + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + is_immutable: bool, } impl DiffComponent { - /// - pub fn new( - queue: Queue, - theme: SharedTheme, - key_config: SharedKeyConfig, - is_immutable: bool, - ) -> Self { - Self { - focused: false, - queue, - current: Current::default(), - pending: false, - selected_hunk: None, - diff: None, - current_size: Cell::new((0, 0)), - selection: Selection::Single(0), - scroll: VerticalScroll::new(), - theme, - key_config, - is_immutable, - } - } - /// - fn can_scroll(&self) -> bool { - self.diff - .as_ref() - .map(|diff| diff.lines > 1) - .unwrap_or_default() - } - /// - pub fn current(&self) -> (String, bool) { - (self.current.path.clone(), self.current.is_stage) - } - /// - pub fn clear(&mut self, pending: bool) { - self.current = Current::default(); - self.diff = None; - self.scroll.reset(); - self.selection = Selection::Single(0); - self.selected_hunk = None; - self.pending = pending; - } - /// - pub fn update( - &mut self, - path: String, - is_stage: bool, - diff: FileDiff, - ) { - self.pending = false; - - let hash = hash(&diff); - - if self.current.hash != hash { - let reset_selection = self.current.path != path; - - self.current = Current { - path, - is_stage, - hash, - }; - - self.diff = Some(diff); - - if reset_selection { - self.scroll.reset(); - self.selection = Selection::Single(0); - self.update_selection(0); - } else { - let old_selection = match self.selection { - Selection::Single(line) => line, - Selection::Multiple(start, _) => start, - }; - self.update_selection(old_selection); - } - } - } - - fn move_selection(&mut self, move_type: ScrollType) { - if let Some(diff) = &self.diff { - let max = diff.lines.saturating_sub(1) as usize; - - let new_start = match move_type { - ScrollType::Down => { - self.selection.get_bottom().saturating_add(1) - } - ScrollType::Up => { - self.selection.get_top().saturating_sub(1) - } - ScrollType::Home => 0, - ScrollType::End => max, - ScrollType::PageDown => { - self.selection.get_bottom().saturating_add( - self.current_size.get().1.saturating_sub(1) - as usize, - ) - } - ScrollType::PageUp => { - self.selection.get_top().saturating_sub( - self.current_size.get().1.saturating_sub(1) - as usize, - ) - } - }; - - self.update_selection(new_start); - } - } - - fn update_selection(&mut self, new_start: usize) { - if let Some(diff) = &self.diff { - let max = diff.lines.saturating_sub(1) as usize; - let new_start = cmp::min(max, new_start); - self.selection = Selection::Single(new_start); - self.selected_hunk = - Self::find_selected_hunk(diff, new_start); - } - } - - fn lines_count(&self) -> usize { - self.diff.as_ref().map_or(0, |diff| diff.lines) - } - - fn modify_selection(&mut self, direction: Direction) { - if self.diff.is_some() { - self.selection.modify(direction, self.lines_count()); - } - } - - fn copy_selection(&self) { - if let Some(diff) = &self.diff { - let lines_to_copy: Vec<&str> = - diff.hunks - .iter() - .flat_map(|hunk| hunk.lines.iter()) - .enumerate() - .filter_map(|(i, line)| { - if self.selection.contains(i) { - Some(line.content.trim_matches(|c| { - c == '\n' || c == '\r' - })) - } else { - None - } - }) - .collect(); - - try_or_popup!( - self, - "copy to clipboard error:", - crate::clipboard::copy_string( - &lines_to_copy.join("\n") - ) - ); - } - } - - fn find_selected_hunk( - diff: &FileDiff, - line_selected: usize, - ) -> Option<usize> { - let mut line_cursor = 0_usize; - for (i, hunk) in diff.hunks.iter().enumerate() { - let hunk_len = hunk.lines.len(); - let hunk_min = line_cursor; - let hunk_max = line_cursor + hunk_len; - - let hunk_selected = - hunk_min <= line_selected && hunk_max > line_selected; - - if hunk_selected { - return Some(i); - } - - line_cursor += hunk_len; - } - - None - } - - fn get_text(&self, width: u16, height: u16) -> Vec<Spans> { - let mut res: Vec<Spans> = Vec::new(); - if let Some(diff) = &self.diff { - if diff.hunks.is_empty() { - let is_positive = diff.size_delta >= 0; - let delta_byte_size = - ByteSize::b(diff.size_delta.abs() as u64); - let sign = if is_positive { "+" } else { "-" }; - res.extend(vec![Spans::from(vec![ - Span::raw(Cow::from("size: ")), - Span::styled( - Cow::from(format!( - "{}", - ByteSize::b(diff.sizes.0) - )), - self.theme.text(false, false), - ), - Span::raw(Cow::from(" -> ")), - Span::styled( - Cow::from(format!( - "{}", - ByteSize::b(diff.sizes.1) - )), - self.theme.text(false, false), - ), - Span::raw(Cow::from(" (")), - Span::styled( - Cow::from(format!( - "{}{:}", - sign, delta_byte_size - )), - self.theme.diff_line( - if is_positive { - DiffLineType::Add - } else { - DiffLineType::Delete - }, - false, - ), - ), - Span::raw(Cow::from(")")), - ])]); - } else { - let min = self.scroll.get_top(); - let max = min + height as usize; - - let mut line_cursor = 0_usize; - let mut lines_added = 0_usize; - - for (i, hunk) in diff.hunks.iter().enumerate() { - let hunk_selected = self.focused() - && self - .selected_hunk - .map_or(false, |s| s == i); - - if lines_added >= height as usize { - break; - } - - let hunk_len = hunk.lines.len(); - let hunk_min = line_cursor; - let hunk_max = line_cursor + hunk_len; - - if Self::hunk_visible( - hunk_min, hunk_max, min, max, - ) { - for (i, line) in hunk.lines.iter().enumerate() - { - if line_cursor >= min - && line_cursor <= max - { - res.push(Self::get_line_to_add( - width, - line, - self.focused() - && self - .selection - .contains(line_cursor), - hunk_selected, - i == hunk_len as usize - 1, - &self.theme, - )); - lines_added += 1; - } - - line_cursor += 1; - } - } else { - line_cursor += hunk_len; - } - } - } - } - res - } - - fn get_line_to_add<'a>( - width: u16, - line: &'a DiffLine, - selected: bool, - selected_hunk: bool, - end_of_hunk: bool, - theme: &SharedTheme, - ) -> Spans<'a> { - let style = theme.diff_hunk_marker(selected_hunk); - - let left_side_of_line = if end_of_hunk { - Span::styled(Cow::from(symbols::line::BOTTOM_LEFT), style) - } else { - match line.line_type { - DiffLineType::Header => Span::styled( - Cow::from(symbols::line::TOP_LEFT), - style, - ), - _ => Span::styled( - Cow::from(symbols::line::VERTICAL), - style, - ), - } - }; - - let trimmed = - line.content.trim_matches(|c| c == '\n' || c == '\r'); - - let filled = if selected { - // selected line - format!("{:w$}\n", trimmed, w = width as usize) - } else { - // weird eof missing eol line - format!("{}\n", trimmed) - }; - //TODO: allow customize tabsize - let content = Cow::from(filled.replace("\t", " ")); - - Spans::from(vec![ - left_side_of_line, - Span::styled( - content, - theme.diff_line(line.line_type, selected), - ), - ]) - } - - const fn hunk_visible( - hunk_min: usize, - hunk_max: usize, - min: usize, - max: usize, - ) -> bool { - // full overlap - if hunk_min <= min && hunk_max >= max { - return true; - } - - // partly overlap - if (hunk_min >= min && hunk_min <= max) - || (hunk_max >= min && hunk_max <= max) - { - return true; - } - - false - } - - fn unstage_hunk(&mut self) -> Result<()> { - if let Some(diff) = &self.diff { - if let Some(hunk) = self.selected_hunk { - let hash = diff.hunks[hunk].header_hash; - sync::unstage_hunk(CWD, &self.current.path, hash)?; - self.queue_update(); - } - } - - Ok(()) - } - - fn stage_hunk(&mut self) -> Result<()> { - if let Some(diff) = &self.diff { - if let Some(hunk) = self.selected_hunk { - if diff.untracked { - sync::stage_add_file( - CWD, - Path::new(&self.current.path), - )?; - } else { - let hash = diff.hunks[hunk].header_hash; - sync::stage_hunk(CWD, &self.current.path, hash)?; - } - - self.queue_update(); - } - } - - Ok(()) - } - - fn queue_update(&self) { - self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); - } - - fn reset_hunk(&self) { - if let Some(diff) = &self.diff { - if let Some(hunk) = self.selected_hunk { - let hash = diff.hunks[hunk].header_hash; - - self.queue.push(InternalEvent::ConfirmAction( - Action::ResetHunk( - self.current.path.clone(), - hash, - ), - )); - } - } - } - - fn reset_lines(&self) { - self.queue.push(InternalEvent::ConfirmAction( - Action::ResetLines( - self.current.path.clone(), - self.selected_lines(), - ), - )); - } - - fn stage_lines(&self) { - if let Some(diff) = &self.diff { - //TODO: support untracked files aswell - if !diff.untracked { - let selected_lines = self.selected_lines(); - - try_or_popup!( - self, - "(un)stage lines:", - sync::stage_lines( - CWD, - &self.current.path, - self.is_stage(), - &selected_lines, - ) - ); - - self.queue_update(); - } - } - } - - fn selected_lines(&self) -> Vec<DiffLinePosition> { - self.diff - .as_ref() - .map(|diff| { - diff.hunks - .iter() - .flat_map(|hunk| hunk.lines.iter()) - .enumerate() - .filter_map(|(i, line)| { - let is_add_or_delete = line.line_type - == DiffLineType::Add - || line.line_type == DiffLineType::Delete; - if self.selection.contains(i) - && is_add_or_delete - { - Some(line.position) - } else { - None - } - }) - .collect() - }) - .unwrap_or_default() - } - - fn reset_untracked(&self) { - self.queue.push(InternalEvent::ConfirmAction(Action::Reset( - ResetItem { - path: self.current.path.clone(), - is_folder: false, - }, - ))); - } - - fn stage_unstage_hunk(&mut self) -> Result<()> { - if self.current.is_stage { - self.unstage_hunk()?; - } else { - self.stage_hunk()?; - } - - Ok(()) - } - - const fn is_stage(&self) -> bool { - self.current.is_stage - } + /// + pub fn new( + queue: Queue, + theme: SharedTheme, + key_config: SharedKeyConfig, + is_immutable: bool, + ) -> Self { + Self { + focused: false, + queue, + current: Current::default(), + pending: false, + selected_hunk: None, + diff: None, + current_size: Cell::new((0, 0)), + selection: Selection::Single(0), + scroll: VerticalScroll::new(), + theme, + key_config, + is_immutable, + } + } + /// + fn can_scroll(&self) -> bool { + self.diff + .as_ref() + .map(|diff| diff.lines > 1) + .unwrap_or_default() + } + /// + pub fn current(&self) -> (String, bool) { + (self.current.path.clone(), self.current.is_stage) + } + /// + pub fn clear(&mut self, pending: bool) { + self.current = Current::default(); + self.diff = None; + self.scroll.reset(); + self.selection = Selection::Single(0); + self.selected_hunk = None; + self.pending = pending; + } + /// + pub fn update( + &mut self, + path: String, + is_stage: bool, + diff: FileDiff, + ) { + self.pending = false; + + let hash = hash(&diff); + + if self.current.hash != hash { + let reset_selection = self.current.path != path; + + self.current = Current { + path, + is_stage, + hash, + }; + + self.diff = Some(diff); + + if reset_selection { + self.scroll.reset(); + self.selection = Selection::Single(0); + self.update_selection(0); + } else { + let old_selection = match self.selection { + Selection::Single(line) => line, + Selection::Multiple(start, _) => start, + }; + self.update_selection(old_selection); + } + } + } + + fn move_selection(&mut self, move_type: ScrollType) { + if let Some(diff) = &self.diff { + let max = diff.lines.saturating_sub(1) as usize; + + let new_start = match move_type { + ScrollType::Down => { + self.selection.get_bottom().saturating_add(1) + } + ScrollType::Up => { + self.selection.get_top().saturating_sub(1) + } + ScrollType::Home => 0, + ScrollType::End => max, + ScrollType::PageDown => { + self.selection.get_bottom().saturating_add( + self.current_size.get().1.saturating_sub(1) + as usize, + ) + } + ScrollType::PageUp => { + self.selection.get_top().saturating_sub( + self.current_size.get().1.saturating_sub(1) + as usize, + ) + } + }; + + self.update_selection(new_start); + } + } + + fn update_selection(&mut self, new_start: usize) { + if let Some(diff) = &self.diff { + let max = diff.lines.saturating_sub(1) as usize; + let new_start = cmp::min(max, new_start); + self.selection = Selection::Single(new_start); + self.selected_hunk = + Self::find_selected_hunk(diff, new_start); + } + } + + fn lines_count(&self) -> usize { + self.diff.as_ref().map_or(0, |diff| diff.lines) + } + + fn modify_selection(&mut self, direction: Direction) { + if self.diff.is_some() { + self.selection.modify(direction, self.lines_count()); + } + } + + fn copy_selection(&self) { + if let Some(diff) = &self.diff { + let lines_to_copy: Vec<&str> = + diff.hunks + .iter() + .flat_map(|hunk| hunk.lines.iter()) + .enumerate() + .filter_map(|(i, line)| { + if self.selection.contains(i) { + Some(line.content.trim_matches(|c| { + c == '\n' || c == '\r' + })) + } else { + None + } + }) + .collect(); + + try_or_popup!( + self, + "copy to clipboard error:", + crate::clipboard::copy_string( + &lines_to_copy.join("\n") + ) + ); + } + } + + fn find_selected_hunk( + diff: &FileDiff, + line_selected: usize, + ) -> Option<usize> { + let mut line_cursor = 0_usize; + for (i, hunk) in diff.hunks.iter().enumerate() { + let hunk_len = hunk.lines.len(); + let hunk_min = line_cursor; + let hunk_max = line_cursor + hunk_len; + + let hunk_selected = + hunk_min <= line_selected && hunk_max > line_selected; + + if hunk_selected { + return Some(i); + } + + line_cursor += hunk_len; + } + + None + } + + fn get_text(&self, width: u16, height: u16) -> Vec<Spans> { + let mut res: Vec<Spans> = Vec::new(); + if let Some(diff) = &self.diff { + if diff.hunks.is_empty() { + let is_positive = diff.size_delta >= 0; + let delta_byte_size = + ByteSize::b(diff.size_delta.abs() as u64); + let sign = if is_positive { "+" } else { "-" }; + res.extend(vec![Spans::from(vec![ + Span::raw(Cow::from("size: ")), + Span::styled( + Cow::from(format!( + "{}", + ByteSize::b(diff.sizes.0) + )), + self.theme.text(false, false), + ), + Span::raw(Cow::from(" -> ")), + Span::styled( + Cow::from(format!( + "{}", + ByteSize::b(diff.sizes.1) + )), + self.theme.text(false, false), + ), + Span::raw(Cow::from(" (")), + Span::styled( + Cow::from(format!( + "{}{:}", + sign, delta_byte_size + )), + self.theme.diff_line( + if is_positive { + DiffLineType::Add + } else { + DiffLineType::Delete + }, + false, + ), + ), + Span::raw(Cow::from(")")), + ])]); + } else { + let min = self.scroll.get_top(); + let max = min + height as usize; + + let mut line_cursor = 0_usize; + let mut lines_added = 0_usize; + + for (i, hunk) in diff.hunks.iter().enumerate() { + let hunk_selected = self.focused() + && self + .selected_hunk + .map_or(false, |s| s == i); + + if lines_added >= height as usize { + break; + } + + let hunk_len = hunk.lines.len(); + let hunk_min = line_cursor; + let hunk_max = line_cursor + hunk_len; + + if Self::hunk_visible( + hunk_min, hunk_max, min, max, + ) { + for (i, line) in hunk.lines.iter().enumerate() + { + if line_cursor >= min + && line_cursor <= max + { + res.push(Self::get_line_to_add( + width, + line, + self.focused() + && self + .selection + .contains(line_cursor), + hunk_selected, + i == hunk_len as usize - 1, + &self.theme, + )); + lines_added += 1; + } + + line_cursor += 1; + } + } else { + line_cursor += hunk_len; + } + } + } + } + res + } + + fn get_line_to_add<'a>( + width: u16, + line: &'a DiffLine, + selected: bool, + selected_hunk: bool, + end_of_hunk: bool, + theme: &SharedTheme, + ) -> Spans<'a> { + let style = theme.diff_hunk_marker(selected_hunk); + + let left_side_of_line = if end_of_hunk { + Span::styled(Cow::from(symbols::line::BOTTOM_LEFT), style) + } else { + match line.line_type { + DiffLineType::Header => Span::styled( + Cow::from(symbols::line::TOP_LEFT), + style, + ), + _ => Span::styled( + Cow::from(symbols::line::VERTICAL), + style, + ), + } + }; + + let trimmed = + line.content.trim_matches(|c| c == '\n' || c == '\r'); + + let filled = if selected { + // selected line + format!("{:w$}\n", trimmed, w = width as usize) + } else { + // weird eof missing eol line + format!("{}\n", trimmed) + }; + //TODO: allow customize tabsize + let content = Cow::from(filled.replace("\t", " ")); + + Spans::from(vec![ + left_side_of_line, + Span::styled( + content, + theme.diff_line(line.line_type, selected), + ), + ]) + } + + const fn hunk_visible( + hunk_min: usize, + hunk_max: usize, + min: usize, + max: usize, + ) -> bool { + // full overlap + if hunk_min <= min && hunk_max >= max { + return true; + } + + // partly overlap + if (hunk_min >= min && hunk_min <= max) + || (hunk_max >= min && hunk_max <= max) + { + return true; + } + + false + } + + fn unstage_hunk(&mut self) -> Result<()> { + if let Some(diff) = &self.diff { + if let Some(hunk) = self.selected_hunk { + let hash = diff.hunks[hunk].header_hash; + sync::unstage_hunk(CWD, &self.current.path, hash)?; + self.queue_update(); + } + } + + Ok(()) + } + + fn stage_hunk(&mut self) -> Result<()> { + if let Some(diff) = &self.diff { + if let Some(hunk) = self.selected_hunk { + if diff.untracked { + sync::stage_add_file( + CWD, + Path::new(&self.current.path), + )?; + } else { + let hash = diff.hunks[hunk].header_hash; + sync::stage_hunk(CWD, &self.current.path, hash)?; + } + + self.queue_update(); + } + } + + Ok(()) + } + + fn queue_update(&self) { + self.queue.push(InternalEvent::Update(NeedsUpdate::ALL)); + } + + fn reset_hunk(&self) { + if let Some(diff) = &self.diff { + if let Some(hunk) = self.selected_hunk { + let hash = diff.hunks[hunk].header_hash; + + self.queue.push(InternalEvent::ConfirmAction( + Action::ResetHunk( + self.current.path.clone(), + hash, + ), + )); + } + } + } + + fn reset_lines(&self) { + self.queue.push(InternalEvent::ConfirmAction( + Action::ResetLines( + self.current.path.clone(), + self.selected_lines(), + ), + )); + } + + fn stage_lines(&self) { + if let Some(diff) = &self.diff { + //TODO: support untracked files aswell + if !diff.untracked { + let selected_lines = self.selected_lines(); + + try_or_popup!( + self, + "(un)stage lines:", + sync::stage_lines( + CWD, + &self.current.path, + self.is_stage(), + &selected_lines, + ) + ); + + self.queue_update(); + } + } + } + + fn selected_lines(&self) -> Vec<DiffLinePosition> { + self.diff + .as_ref() + .map(|diff| { + diff.hunks + .iter() + .flat_map(|hunk| hunk.lines.iter()) + .enumerate() + .filter_map(|(i, line)| { + let is_add_or_delete = line.line_type + == DiffLineType::Add + || line.line_type == DiffLineType::Delete; + if self.selection.contains(i) + && is_add_or_delete + { + Some(line.position) + } else { + None + } + }) + .collect() + }) + .unwrap_or_default() + } + + fn reset_untracked(&self) { + self.queue.push(InternalEvent::ConfirmAction(Action::Reset( + ResetItem { + path: self.current.path.clone(), + is_folder: false, + }, + ))); + } + + fn stage_unstage_hunk(&mut self) -> Result<()> { + if self.current.is_stage { + self.unstage_hunk()?; + } else { + self.stage_hunk()?; + } + + Ok(()) + } + + const fn is_stage(&self) -> bool { + self.current.is_stage + } } impl DrawableComponent for DiffComponent { - fn draw<B: Backend>( - &self, - f: &mut Frame<B>, - r: Rect, - ) -> Result<()> { - self.current_size.set(( - r.width.saturating_sub(2), - r.height.saturating_sub(2), - )); - - let current_height = self.current_size.get().1; - - self.scroll.update( - self.selection.get_end(), - sel |