From 191ff0b2ce069861fddc2aeec0de3beb815d86b6 Mon Sep 17 00:00:00 2001 From: Georg Bauer Date: Tue, 28 Jul 2026 19:07:59 +0200 Subject: [PATCH] Persist the default Git diff layout --- src/app.rs | 22 +++++++++++++++------- src/app/git.rs | 9 ++------- src/app/preferences.rs | 3 +++ src/app/view.rs | 2 +- src/app/view/git.rs | 11 ++++++----- src/app/view/preferences.rs | 14 ++++++++++++++ src/config.rs | 24 +++++++++++++++++++++++- 7 files changed, 64 insertions(+), 21 deletions(-) diff --git a/src/app.rs b/src/app.rs index ba55c6e..4a2fab5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,14 +8,15 @@ mod view; pub(crate) use view::app_theme; use generation::ChatMessage; -use git::{ActiveGitOperation, GitDiff, GitDiffMode, GitWorktree}; +use git::{ActiveGitOperation, GitDiff, GitWorktree}; use model_manager::{ActiveDownload, ModelDownload, ModelOperation}; use preferences::PreferenceDraft; #[cfg(test)] use preferences::{parse_optional_gib, parse_streaming_cache}; use crate::config::{ - Config, DevBrainConfig, EndpointConfig, GitConfig, GitDiffAlgorithm, GitDiffWhitespace, + Config, DevBrainConfig, EndpointConfig, GitConfig, GitDiffAlgorithm, GitDiffLayout, + GitDiffWhitespace, }; use crate::database::{Database, ProjectWithSessions, SessionState, StoredMessage}; #[cfg(target_os = "macos")] @@ -77,7 +78,7 @@ pub(crate) struct App { git_selected_files: HashSet, git_commit_message: String, git_diff: Option, - git_diff_mode: GitDiffMode, + git_diff_layout: GitDiffLayout, git_commit_all_confirmation: bool, git_operation: Option, last_git_scan: Instant, @@ -350,6 +351,7 @@ pub(crate) enum Message { PreferenceEndpointCorsChanged(bool), PreferenceDevBrainEnabledChanged(bool), PreferenceDevBrainVaultChanged(String), + PreferenceGitDiffLayoutChanged(GitDiffLayout), PreferenceGitDiffAlgorithmChanged(GitDiffAlgorithm), PreferenceGitContextLinesChanged(String), PreferenceGitInterhunkLinesChanged(String), @@ -437,7 +439,7 @@ pub(crate) enum Message { SwitchGitBranch(String), ToggleGitFile(PathBuf), OpenGitDiff(PathBuf), - SetGitDiffMode(GitDiffMode), + SetGitDiffLayout(GitDiffLayout), GitDiffScrolled(scrollable::Viewport), GitCommitMessageChanged(String), GitStageSelected, @@ -503,6 +505,7 @@ impl App { let metrics = Arc::new(Metrics::new(&application_support_path().join("kv-cache"))); let metrics_snapshot = metrics.snapshot(); + let git_diff_layout = config.git.diff_layout; #[cfg(target_os = "macos")] let (runtime_config, generation_service, endpoint, service_error) = spawn_services(&config, Arc::clone(&metrics)); @@ -532,7 +535,7 @@ impl App { git_selected_files: HashSet::new(), git_commit_message: String::new(), git_diff: None, - git_diff_mode: GitDiffMode::default(), + git_diff_layout, git_commit_all_confirmation: false, git_operation: None, last_git_scan: Instant::now() - GIT_SCAN_INTERVAL, @@ -641,6 +644,7 @@ impl App { let context_limit = config.generation.context_tokens.max(0) as u32; let metrics = Arc::new(Metrics::new(&application_support_path().join("kv-cache"))); let metrics_snapshot = metrics.snapshot(); + let git_diff_layout = config.git.diff_layout; #[cfg(target_os = "macos")] let (runtime_config, generation_service, endpoint, service_error) = spawn_services(&config, Arc::clone(&metrics)); @@ -674,7 +678,7 @@ impl App { git_selected_files: HashSet::new(), git_commit_message: String::new(), git_diff: None, - git_diff_mode: GitDiffMode::default(), + git_diff_layout, git_commit_all_confirmation: false, git_operation: None, last_git_scan: Instant::now() - GIT_SCAN_INTERVAL, @@ -1187,6 +1191,10 @@ impl App { self.preference_draft.dev_brain_vault_path = value; self.preference_error = None; } + Message::PreferenceGitDiffLayoutChanged(layout) => { + self.preference_draft.git_diff_layout = layout; + self.preference_error = None; + } Message::PreferenceGitDiffAlgorithmChanged(algorithm) => { self.preference_draft.git_diff_algorithm = algorithm; self.preference_error = None; @@ -1798,7 +1806,7 @@ impl App { Message::SwitchGitBranch(branch) => self.switch_git_branch(&branch), Message::ToggleGitFile(path) => self.toggle_git_file(path), Message::OpenGitDiff(path) => self.open_git_diff(&path), - Message::SetGitDiffMode(mode) => self.git_diff_mode = mode, + Message::SetGitDiffLayout(layout) => self.git_diff_layout = layout, Message::GitDiffScrolled(viewport) => { let offset = scrollable::AbsoluteOffset { x: viewport.absolute_offset().x, diff --git a/src/app/git.rs b/src/app/git.rs index 03bb809..48dd423 100644 --- a/src/app/git.rs +++ b/src/app/git.rs @@ -40,13 +40,6 @@ pub(super) struct GitWorktree { pub(super) files: Vec, } -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub(crate) enum GitDiffMode { - #[default] - Unified, - Split, -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(super) enum GitDiffLineKind { Section, @@ -186,6 +179,7 @@ impl App { match load_diff(&worktree.root, file, &self.config.git) { Ok(diff) => { self.git_diff = Some(diff); + self.git_diff_layout = self.config.git.diff_layout; self.error = None; } Err(error) => self.error = Some(error), @@ -1113,6 +1107,7 @@ mod tests { .find(|file| file.display_path == "changed.txt") .unwrap(); let settings = GitConfig { + diff_layout: GitDiffLayout::Unified, diff_algorithm: GitDiffAlgorithm::Patience, context_lines: 0, interhunk_lines: 0, diff --git a/src/app/preferences.rs b/src/app/preferences.rs index 50e6501..913ac43 100644 --- a/src/app/preferences.rs +++ b/src/app/preferences.rs @@ -12,6 +12,7 @@ pub(super) struct PreferenceDraft { pub(super) endpoint_cors: bool, pub(super) dev_brain_enabled: bool, pub(super) dev_brain_vault_path: String, + pub(super) git_diff_layout: GitDiffLayout, pub(super) git_diff_algorithm: GitDiffAlgorithm, pub(super) git_context_lines: String, pub(super) git_interhunk_lines: String, @@ -70,6 +71,7 @@ impl PreferenceDraft { endpoint_cors: config.endpoint.cors, dev_brain_enabled: config.dev_brain.enabled, dev_brain_vault_path: config.dev_brain.vault_path.clone().unwrap_or_default(), + git_diff_layout: config.git.diff_layout, git_diff_algorithm: config.git.diff_algorithm, git_context_lines: config.git.context_lines.to_string(), git_interhunk_lines: config.git.interhunk_lines.to_string(), @@ -140,6 +142,7 @@ impl PreferenceDraft { pub(super) fn git(&self) -> Result { Ok(GitConfig { + diff_layout: self.git_diff_layout, diff_algorithm: self.git_diff_algorithm, context_lines: parse_u32("Git diff context lines", &self.git_context_lines)?, interhunk_lines: parse_u32("Git diff interhunk lines", &self.git_interhunk_lines)?, diff --git a/src/app/view.rs b/src/app/view.rs index 73077f2..97f9957 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -12,7 +12,7 @@ use super::{ ModelDownload, ModelOperation, PreferenceSection, ProjectChoice, chat_scroll_id, composer_id, models_path, preferences_scroll_id, }; -use crate::config::{GIT_DIFF_ALGORITHMS, GIT_DIFF_WHITESPACE_MODES}; +use crate::config::{GIT_DIFF_ALGORITHMS, GIT_DIFF_LAYOUTS, GIT_DIFF_WHITESPACE_MODES}; use crate::database::{ProjectWithSessions, Session, SessionState}; use crate::model::{ self, DownloadPhase, MODEL_CHOICES, ManagedArtifact, ManagedArtifactState, ModelChoice, diff --git a/src/app/view/git.rs b/src/app/view/git.rs index c839255..6042f11 100644 --- a/src/app/view/git.rs +++ b/src/app/view/git.rs @@ -1,6 +1,7 @@ use super::*; -use crate::app::git::{GitChangeKind, GitDiffLine, GitDiffLineKind, GitDiffMode, GitDiffRow}; +use crate::app::git::{GitChangeKind, GitDiffLine, GitDiffLineKind, GitDiffRow}; use crate::app::{git_diff_new_scroll_id, git_diff_old_scroll_id}; +use crate::config::GitDiffLayout; use iced::widget::column; impl App { @@ -230,13 +231,13 @@ impl App { pub(super) fn git_diff_panel(&self) -> Element<'_, Message> { let diff = self.git_diff.as_ref().unwrap(); - let unified_active = self.git_diff_mode == GitDiffMode::Unified; - let split_active = self.git_diff_mode == GitDiffMode::Split; + let unified_active = self.git_diff_layout == GitDiffLayout::Unified; + let split_active = self.git_diff_layout == GitDiffLayout::Split; let mode = container( row![ button(text("1 column").size(12)) .padding([4, 10]) - .on_press(Message::SetGitDiffMode(GitDiffMode::Unified)) + .on_press(Message::SetGitDiffLayout(GitDiffLayout::Unified)) .style(move |theme, status| segmented_button_style( theme, status, @@ -244,7 +245,7 @@ impl App { )), button(text("2 columns").size(12)) .padding([4, 10]) - .on_press(Message::SetGitDiffMode(GitDiffMode::Split)) + .on_press(Message::SetGitDiffLayout(GitDiffLayout::Split)) .style(move |theme, status| segmented_button_style( theme, status, diff --git a/src/app/view/preferences.rs b/src/app/view/preferences.rs index 5139251..030a045 100644 --- a/src/app/view/preferences.rs +++ b/src/app/view/preferences.rs @@ -199,6 +199,20 @@ impl App { PreferenceSection::Git, "GIT DIFFS", column![ + row![ + hint( + text("Default layout").size(13).width(Length::Fill), + "Layout used whenever a file diff is opened. The control in the diff can still change that one view.", + ), + pick_list( + &GIT_DIFF_LAYOUTS[..], + Some(self.preference_draft.git_diff_layout), + Message::PreferenceGitDiffLayoutChanged, + ) + .width(240), + ] + .spacing(12) + .align_y(Alignment::Center), row![ hint( text("Algorithm").size(13).width(Length::Fill), diff --git a/src/config.rs b/src/config.rs index f8830f8..bc381ee 100644 --- a/src/config.rs +++ b/src/config.rs @@ -46,6 +46,25 @@ pub const GIT_DIFF_ALGORITHMS: [GitDiffAlgorithm; 3] = [ GitDiffAlgorithm::Minimal, ]; +pub const GIT_DIFF_LAYOUTS: [GitDiffLayout; 2] = [GitDiffLayout::Unified, GitDiffLayout::Split]; + +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum GitDiffLayout { + #[default] + Unified, + Split, +} + +impl fmt::Display for GitDiffLayout { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(match self { + Self::Unified => "1 column", + Self::Split => "2 columns", + }) + } +} + #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "kebab-case")] pub enum GitDiffAlgorithm { @@ -96,6 +115,7 @@ impl fmt::Display for GitDiffWhitespace { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(default, deny_unknown_fields)] pub struct GitConfig { + pub diff_layout: GitDiffLayout, pub diff_algorithm: GitDiffAlgorithm, pub context_lines: u32, pub interhunk_lines: u32, @@ -107,6 +127,7 @@ pub struct GitConfig { impl Default for GitConfig { fn default() -> Self { Self { + diff_layout: GitDiffLayout::Unified, diff_algorithm: GitDiffAlgorithm::Default, context_lines: 3, interhunk_lines: 0, @@ -282,6 +303,7 @@ mod tests { ..RuntimePreferences::default() }, git: GitConfig { + diff_layout: GitDiffLayout::Split, diff_algorithm: GitDiffAlgorithm::Patience, context_lines: 5, whitespace: GitDiffWhitespace::IgnoreEndOfLine, @@ -297,7 +319,7 @@ mod tests { "model: glm-5.2\na2ui_enabled: false\n\ generation:\n context_tokens: 65536\n reasoning_mode: none\n\ runtime:\n ssd:\n enabled: true\n cache: 64GB\n\ - git:\n diff_algorithm: patience\n context_lines: 5\n whitespace: ignore-end-of-line\n" + git:\n diff_layout: split\n diff_algorithm: patience\n context_lines: 5\n whitespace: ignore-end-of-line\n" ); assert_eq!(Config::load(&path).unwrap(), config); fs::remove_dir_all(&directory).unwrap();