pub struct CodeDiff {
pub file_path: Option<String>,
pub hunks: Vec<DiffHunk>,
pub config: DiffConfig,
pub scroll_offset: usize,
pub file_tree: DiffFileTree,
pub file_diffs: HashMap<String, Vec<DiffHunk>>,
pub show_sidebar: bool,
pub sidebar_split: ResizableSplit,
pub sidebar_focused: bool,
pub theme: AppTheme,
}Expand description
A widget for displaying code diffs in a terminal UI.
CodeDiff renders diff hunks in a side-by-side format (like VS Code) or
unified format, with support for syntax highlighting, line numbers, and
visual markers for added/removed lines.
§Layout
In side-by-side mode:
- Left panel shows the old/original version
- Right panel shows the new/modified version
- Lines are aligned horizontally for easy comparison
- Empty spaces fill gaps where lines were added/removed
When sidebar is enabled:
- Left panel shows file tree with status markers
- Right panel shows the diff for the selected file
[key toggles sidebar visibilityh/lkeys switch focus between sidebar and diff
§Visual Elements
- Green background for added lines (+)
- Red background for removed lines (-)
- Gray header bars for hunk information
- Line numbers on each side
§Fields
file_path- Optional path to the file being diffed (for single-file mode)hunks- Collection of diff hunks to display (for single-file mode)config- Display configuration (colors, style, sidebar options)scroll_offset- Current vertical scroll positionfile_tree- Internal file tree widget for sidebarfile_diffs- Map of file paths to their diff hunks (for multi-file mode)show_sidebar- Whether sidebar is currently visiblesidebar_split- Resizable split for sidebar/diff area division with mouse drag supportsidebar_focused- Whether sidebar has focus (vs diff view)
Fields§
§file_path: Option<String>Optional path to the file being diffed (single-file mode).
hunks: Vec<DiffHunk>The diff hunks to display (single-file mode).
config: DiffConfigDisplay configuration.
scroll_offset: usizeCurrent vertical scroll offset.
file_tree: DiffFileTreeInternal file tree widget for sidebar.
file_diffs: HashMap<String, Vec<DiffHunk>>Map of file paths to their diff hunks (multi-file mode).
Whether the sidebar is currently visible.
Resizable split for sidebar/diff area division with mouse drag support.
Whether the sidebar has focus (vs diff view).
theme: AppThemeApplication theme for styling.
Implementations§
Source§impl CodeDiff
impl CodeDiff
Sourcepub fn from_git() -> Self
pub fn from_git() -> Self
Create a CodeDiff from the current git repository’s diff.
Tries in order:
- Unstaged changes (
git diff) - Staged changes (
git diff --cached) - Last commit (
git diff HEAD~1)
Returns an empty CodeDiff if not in a git repo or no changes found.
§Example
use ratatui_toolkit::code_diff::CodeDiff;
// Create a diff widget from current git changes
let diff = CodeDiff::from_git();Sourcepub fn from_git_with_config(config: DiffConfig) -> Self
pub fn from_git_with_config(config: DiffConfig) -> Self
Create a CodeDiff from git with custom config.
This is a convenience method that combines from_git() with with_config().
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let diff = CodeDiff::from_git_with_config(
DiffConfig::new()
.sidebar_enabled(true)
.show_line_numbers(true)
);Source§impl CodeDiff
impl CodeDiff
Sourcepub fn from_multi_file_diff(diff_text: &str) -> Self
pub fn from_multi_file_diff(diff_text: &str) -> Self
Creates a diff widget by parsing multi-file git diff output.
Parses output from git diff that may contain multiple files:
diff --git a/file1.rs b/file1.rs
--- a/file1.rs
+++ b/file1.rs
@@ -1,4 +1,5 @@
...
diff --git a/file2.rs b/file2.rs
--- a/file2.rs
+++ b/file2.rs
@@ -1,3 +1,4 @@
...The sidebar is automatically enabled when multiple files are detected.
§Arguments
diff_text- The multi-file diff text to parse
§Returns
A new CodeDiff instance with parsed files and sidebar enabled
§Example
use ratatui_toolkit::code_diff::CodeDiff;
let diff_text = r#"diff --git a/src/lib.rs b/src/lib.rs
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
fn main() {
+ println!("Hello");
}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,3 @@
+pub fn helper() {
+}
"#;
let widget = CodeDiff::from_multi_file_diff(diff_text);Source§impl CodeDiff
impl CodeDiff
Sourcepub fn from_unified_diff(diff_text: &str) -> Self
pub fn from_unified_diff(diff_text: &str) -> Self
Creates a diff widget by parsing unified diff format text.
Parses the standard unified diff format used by git diff, diff -u, etc.
§Arguments
diff_text- The unified diff text to parse
§Returns
A new CodeDiff instance with parsed hunks
§Example
use ratatui_toolkit::code_diff::CodeDiff;
let diff_text = r#"
--- a/file.txt
+++ b/file.txt
@@ -1,4 +1,5 @@
context line
-removed line
+added line
more context
"#;
let diff = CodeDiff::from_unified_diff(diff_text);
assert!(!diff.hunks.is_empty());Source§impl CodeDiff
impl CodeDiff
Sourcepub fn with_config(self, config: DiffConfig) -> Self
pub fn with_config(self, config: DiffConfig) -> Self
Sets the configuration for this diff widget.
This also updates the sidebar state based on the new config:
show_sidebaris set fromconfig.sidebar_enabledsidebar_width_percentis set fromconfig.sidebar_default_width
§Arguments
config- The display configuration to use
§Returns
Self for method chaining
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let config = DiffConfig::new()
.show_line_numbers(false)
.sidebar_enabled(true);
let diff = CodeDiff::new().with_config(config);Source§impl CodeDiff
impl CodeDiff
Sourcepub fn with_file(self, path: &str, status: FileStatus, diff_text: &str) -> Self
pub fn with_file(self, path: &str, status: FileStatus, diff_text: &str) -> Self
Adds a file with its diff to the widget.
This is used for multi-file diffs. The file will appear in the sidebar file tree with the specified status, and its diff content will be stored for display when selected.
§Arguments
path- The file path to displaystatus- The file’s modification status (Modified, Added, Deleted, Renamed)diff_text- The unified diff text for this file
§Returns
Self for method chaining
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
use ratatui_toolkit::widgets::code_diff::diff_file_tree::FileStatus;
let diff_text = r#"--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,4 @@
fn main() {
+ println!("Hello");
}
"#;
let widget = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true))
.with_file("src/lib.rs", FileStatus::Modified, diff_text);Source§impl CodeDiff
impl CodeDiff
Sourcepub fn with_theme(self, theme: &AppTheme) -> Self
pub fn with_theme(self, theme: &AppTheme) -> Self
Sets the application theme for styling.
This method applies the theme colors to the diff widget, including borders, backgrounds, and text colors.
§Arguments
theme- The application theme to use
§Returns
Self for method chaining
§Example
use ratatui_toolkit::code_diff::CodeDiff;
use ratatui_toolkit::services::theme::AppTheme;
let theme = AppTheme::default();
let diff = CodeDiff::new().with_theme(&theme);Sourcepub fn apply_theme(&mut self, theme: &AppTheme)
pub fn apply_theme(&mut self, theme: &AppTheme)
Applies a theme to the existing widget (non-consuming).
§Arguments
theme- The application theme to apply
Source§impl CodeDiff
impl CodeDiff
Sourcepub fn handle_key(&mut self, key: KeyCode) -> bool
pub fn handle_key(&mut self, key: KeyCode) -> bool
Handles a keyboard event and returns whether it was consumed.
§Key Bindings
[- Toggle sidebar visibilityTab- Switch focus between sidebar and diff/- Enter filter mode (sidebar only)h- Collapse expanded directory, or go to parent if collapsed (sidebar only, no-op on files)l- Expand collapsed directory, or show diff for files (sidebar only)j/Down- Navigate down (files in sidebar, scroll in diff)k/Up- Navigate up (files in sidebar, scroll in diff)g- Go to top (first file or top of diff)G- Go to bottom (last file or bottom of diff)Space/Enter- Toggle directory expand / select file (sidebar only)H/<- Decrease sidebar widthL/>- Increase sidebar widthr- Refresh diff from git
§Filter Mode
When filter mode is active (triggered by /):
Esc- Clear filter and exit filter modeEnter- Exit filter mode (keep filter active)Backspace- Delete last character- Any character - Append to filter
§Arguments
key- The key code that was pressed
§Returns
true if the key was handled, false otherwise
§Example
use crossterm::event::KeyCode;
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true));
// Toggle sidebar
diff.handle_key(KeyCode::Char('['));
// Navigate
diff.handle_key(KeyCode::Char('j'));
// Enter filter mode
diff.handle_key(KeyCode::Char('/'));Source§impl CodeDiff
impl CodeDiff
Sourcepub fn handle_mouse(&mut self, event: MouseEvent, area: Rect) -> bool
pub fn handle_mouse(&mut self, event: MouseEvent, area: Rect) -> bool
Handles mouse events for sidebar resizing.
Supports mouse drag to resize the sidebar divider using ResizableSplit’s built-in drag handling.
§Arguments
event- The mouse event to handlearea- The area the widget is rendered in
§Returns
true if the event was handled (consumed), false otherwise
§Example
use crossterm::event::{Event, MouseEvent};
use ratatui::layout::Rect;
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true));
// In your event loop:
// if let Event::Mouse(mouse) = event {
// let area = Rect::new(0, 0, 100, 50);
// if diff.handle_mouse(mouse, area) {
// // Mouse event was handled
// }
// }Source§impl CodeDiff
impl CodeDiff
Returns whether the sidebar divider is currently being dragged.
This can be used to adjust polling rate for smooth dragging.
Source§impl CodeDiff
impl CodeDiff
Returns whether the mouse is hovering over the sidebar divider.
This can be used to change the cursor style.
Source§impl CodeDiff
impl CodeDiff
Sourcepub fn refresh(&mut self)
pub fn refresh(&mut self)
Refresh the diff from the current git repository.
This re-fetches the git diff and updates the widget state while preserving the current configuration and sidebar visibility.
§Example
use ratatui_toolkit::code_diff::CodeDiff;
let mut diff = CodeDiff::from_git();
// User makes changes to files...
// Refresh to pick up new changes
diff.refresh();Source§impl CodeDiff
impl CodeDiff
Sourcepub fn refresh_if_changed(&mut self, watcher: &mut RepoWatcher) -> bool
pub fn refresh_if_changed(&mut self, watcher: &mut RepoWatcher) -> bool
Refresh the diff if the repository watcher detected changes.
Returns true when a refresh occurred.
§Example
use ratatui_toolkit::code_diff::CodeDiff;
use ratatui_toolkit::services::repo_watcher::RepoWatcher;
use std::path::Path;
let mut diff = CodeDiff::from_git();
let mut watcher = RepoWatcher::new().unwrap();
watcher.watch(Path::new("."))?;
if diff.refresh_if_changed(&mut watcher) {
println!("Diff updated");
}Source§impl CodeDiff
impl CodeDiff
Adjusts the sidebar width by a delta percentage.
Positive delta increases width, negative decreases. The width is clamped to the ResizableSplit’s min/max values.
§Arguments
delta- The change in width percentage (positive = wider, negative = narrower)
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true));
diff.resize_sidebar(5); // Make 5% wider
diff.resize_sidebar(-3); // Make 3% narrowerSource§impl CodeDiff
impl CodeDiff
Sourcepub fn scroll_down(&mut self, lines: usize)
pub fn scroll_down(&mut self, lines: usize)
Scrolls down by the specified number of lines.
The scroll is clamped to the maximum scrollable position.
§Arguments
lines- Number of lines to scroll down
§Example
use ratatui_toolkit::code_diff::CodeDiff;
let mut diff = CodeDiff::new();
diff.scroll_down(5);
// scroll_offset is now 5 (or less if content is shorter)Source§impl CodeDiff
impl CodeDiff
Sourcepub fn select_next_file(&mut self)
pub fn select_next_file(&mut self)
Selects the next file in the sidebar file tree.
This also updates the current diff hunks to show the selected file. If already at the last file, stays at the last file.
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
use ratatui_toolkit::widgets::code_diff::diff_file_tree::FileStatus;
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true))
.with_file("file1.rs", FileStatus::Modified, "")
.with_file("file2.rs", FileStatus::Added, "");
diff.select_next_file();Source§impl CodeDiff
impl CodeDiff
Sourcepub fn select_prev_file(&mut self)
pub fn select_prev_file(&mut self)
Selects the previous file in the sidebar file tree.
This also updates the current diff hunks to show the selected file. If already at the first file, stays at the first file.
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
use ratatui_toolkit::widgets::code_diff::diff_file_tree::FileStatus;
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true))
.with_file("file1.rs", FileStatus::Modified, "")
.with_file("file2.rs", FileStatus::Added, "");
diff.select_next_file();
diff.select_prev_file(); // Back to first fileSource§impl CodeDiff
impl CodeDiff
Sourcepub fn stats_text(&self) -> String
pub fn stats_text(&self) -> String
Returns a formatted stats string (e.g., “+5 -3”).
§Returns
A string showing the added and removed line counts
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffHunk, DiffLine};
let mut diff = CodeDiff::new();
let mut hunk = DiffHunk::new(1, 1, 1, 2);
hunk.add_line(DiffLine::removed("old", 1));
hunk.add_line(DiffLine::added("new1", 1));
hunk.add_line(DiffLine::added("new2", 2));
diff.add_hunk(hunk);
assert_eq!(diff.stats_text(), "+2 -1");Source§impl CodeDiff
impl CodeDiff
Sourcepub fn toggle_focus(&mut self)
pub fn toggle_focus(&mut self)
Toggles focus between the sidebar and diff view.
When sidebar is focused, navigation keys move through files. When diff view is focused, navigation keys scroll the diff.
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true));
assert!(diff.sidebar_focused);
diff.toggle_focus();
assert!(!diff.sidebar_focused);Source§impl CodeDiff
impl CodeDiff
Toggles the sidebar visibility.
When toggled off, the entire area is used for the diff display. When toggled on, the file tree sidebar appears on the left.
This method only has an effect when config.sidebar_enabled is true.
§Example
use ratatui_toolkit::code_diff::{CodeDiff, DiffConfig};
let mut diff = CodeDiff::new()
.with_config(DiffConfig::new().sidebar_enabled(true));
assert!(diff.show_sidebar);
diff.toggle_sidebar();
assert!(!diff.show_sidebar);Trait Implementations§
Source§impl Widget for CodeDiff
impl Widget for CodeDiff
Source§fn render(self, area: Rect, buf: &mut Buffer)
fn render(self, area: Rect, buf: &mut Buffer)
Renders the diff widget to the given buffer.
The widget renders:
- If sidebar enabled and visible: file tree on left, diff on right
- Otherwise: just the diff content
Each panel includes:
- A header bar with file path and stats (if file path is set)
- Diff hunks in the configured style (side-by-side or unified)
§Arguments
area- The area to render the widget inbuf- The buffer to render to
Auto Trait Implementations§
impl Freeze for CodeDiff
impl RefUnwindSafe for CodeDiff
impl Send for CodeDiff
impl Sync for CodeDiff
impl Unpin for CodeDiff
impl UnwindSafe for CodeDiff
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<R, P> ReadPrimitive<R> for P
impl<R, P> ReadPrimitive<R> for P
Source§fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
fn read_from_little_endian(read: &mut R) -> Result<Self, Error>
ReadEndian::read_from_little_endian().