Skip to content

Commit d69fe60

Browse files
joskeclaude
andauthored
improve testing + UI fixes
* proptests * VCS tests * refactor: UI logic extraction, theme persistence fixes, and clippy - Extracted pure logic from VCS and Directory windows into testable forms. - Fixed theme settings not persisting due to case-sensitivity and overrides. - Shared settings instance across all windows for consistent behavior. - Fixed all pedantic clippy warnings and applied nightly formatting. * bump to 0.5.0 * add testing checklist * Fix UI bugs: keyboard shortcuts, focus handling, find/search, preferences - Fix Alt+Right on file diff tabs triggering dir file copy instead of chunk copy - Fix save with swapped panes undoing the swap (use swap-aware paths in reload) - Fix hidden files toggle only applying after closing preferences - Add Ctrl+S save shortcut via capture-phase handlers - Fix light color schemes not applying on Linux dark mode (CSS provider override) - Fix Enter on dir view folders (expand/collapse) and after closing file tabs - Fix file diff tab not focusing left pane with cursor at line 1 - Fix Ctrl+E/D/F/H/L not working (sourceview consuming keys before app accels) - Fix F3 find-next stuck on same match (advance cursor before forward search) - Add F3/Shift+F3 support from find/replace entries - Add Escape to close find bar from source pane and preferences dialog - Add Close button to preferences dialog - Add hide_hidden_files setting with live-apply - Update TESTING.md with new test cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix dir view scroll jump when switching panes with Left/Right Replace the model-swapping approach (SingleSelection vs NoSelection) with a CSS-based solution. Both panes now share the same SingleSelection model and a `dir-pane-unfocused` CSS class hides the selection highlight in the inactive pane. This eliminates scroll position resets, focus tracking crashes, and timing issues caused by set_model destroying row widgets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Improve dir view pane focus: borders on ScrolledWindows, opacity dimming, keyboard cursor sync - Move dir-pane-focused/dir-pane-inactive CSS classes to ScrolledWindows (ColumnView clips borders internally) - Both panes share SingleSelection so selected row is highlighted on both sides - Inactive pane dimmed to 55% opacity to visually distinguish from active - Sync keyboard cursor via scroll_to(FOCUS) + double-idle scroll restore when switching panes, preventing navigation jumps - Disable swap/nav/blanks/spaces/undo/redo buttons for binary file diffs - Guard connect_changed with !any_binary to prevent re-diff on binary swap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix merge view navigation: don't place cursor on uninvolved pane When navigating to a left chunk, only place cursor on left + middle panes. When navigating to a right chunk, only place cursor on right + middle panes. Previously the third pane's cursor was set to the middle pane's line number, which is wrong for that buffer and caused subsequent Alt+Up/Down navigation to jump to unexpected positions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update TESTING.md with dir pane focus, binary buttons, merge nav fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7f5f34e commit d69fe60

17 files changed

Lines changed: 2014 additions & 482 deletions

Cargo.lock

Lines changed: 439 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mergers"
3-
version = "0.4.8"
3+
version = "0.5.0"
44
edition = "2024"
55
description = "A visual diff and merge tool for files and directories"
66
license = "GPL-2.0"
@@ -16,3 +16,7 @@ clap = { version = "4.5", features = ["derive"] }
1616
sourceview5 = { version = "0.11", features = ["gtk_v4_14"] }
1717
serde = { version = "1.0", features = ["derive"] }
1818
toml = "1.0"
19+
20+
[dev-dependencies]
21+
proptest = "1.6"
22+
tempfile = "3.17"

TESTING.md

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# Manual Testing Checklist
2+
3+
## CLI / Launch Modes
4+
5+
- [ ] No arguments: launches Welcome window
6+
- [ ] Two file paths: opens file diff window
7+
- [ ] Two directory paths: opens directory comparison window
8+
- [ ] Three file paths: opens 3-way merge window
9+
- [ ] One directory (git repo): opens VCS window
10+
- [ ] One directory (not a git repo): prints error and exits
11+
- [ ] Single file path: prints error and exits
12+
- [ ] Non-existent path: prints error and exits
13+
- [ ] File + directory mix: prints error
14+
- [ ] `-L`/`--label` flags: custom pane labels shown for 2-file and 3-file modes
15+
- [ ] `--version` / `--help`: correct output
16+
17+
## Welcome Window
18+
19+
- [ ] "Compare Files" button: two file dialogs, then opens diff; welcome closes
20+
- [ ] "Compare Directories" button: two folder dialogs, then opens dir window; welcome closes
21+
- [ ] "3-way Merge" button: three file dialogs, then opens merge; welcome closes
22+
- [ ] Preferences button / Ctrl+,: opens Preferences dialog
23+
- [ ] Ctrl+W: closes welcome window
24+
- [ ] Cancel any file dialog: nothing happens
25+
26+
## File Diff (2-way)
27+
28+
### Display
29+
- [ ] Chunk backgrounds: blue for replace, green for insert/delete
30+
- [ ] Current chunk: bold dark borders when navigating
31+
- [ ] Inline word-level highlighting: yellow (changed), pink (deleted), green (inserted)
32+
- [ ] Filler lines: thin colored lines where the other side has more content
33+
- [ ] Gutter: curved connecting bands between panes
34+
- [ ] Chunk map strips: minimap of changes at far edges; clickable to jump
35+
- [ ] Syntax highlighting: detected from filename
36+
- [ ] Line numbers: shown/hidden per settings
37+
38+
### Toolbar
39+
- [ ] Undo (Ctrl+Z) / Redo (Ctrl+Shift+Z): acts on last-focused pane
40+
- [ ] Previous change (Alt+Up / Ctrl+E): navigates to previous diff chunk
41+
- [ ] Next change (Alt+Down / Ctrl+D): navigates to next diff chunk
42+
- [ ] Chunk label: shows "N changes" or "Change X of N"
43+
- [ ] Go to line (Ctrl+L): entry appears; type number + Enter to jump; Escape to dismiss; focus returns to active pane
44+
- [ ] Blanks toggle: re-diffs ignoring blank lines
45+
- [ ] Spaces toggle: re-diffs ignoring whitespace
46+
- [ ] Export patch (Ctrl+Shift+P): save dialog; generates unified diff
47+
- [ ] Swap panes: swaps content, labels, save paths, dirty state; title updates
48+
- [ ] Preferences (Ctrl+,): opens Preferences
49+
50+
### Gutter Interactions
51+
- [ ] Click right arrow: copies left chunk to right
52+
- [ ] Click left arrow: copies right chunk to left
53+
- [ ] Right-click gutter: context menu with both copy options
54+
- [ ] Paste highlight flash: 500ms blue flash on inserted text
55+
56+
### Scroll Sync
57+
- [ ] Horizontal scroll synced between panes
58+
- [ ] Vertical scroll independent per pane
59+
60+
### Find / Replace
61+
- [ ] Ctrl+F: opens find bar; Ctrl+H: opens find + replace
62+
- [ ] Typing highlights all matches in both buffers; shows match count
63+
- [ ] Find next (F3 / Enter) / Find prev (Shift+F3): wraps around
64+
- [ ] F3/Shift+F3 works from find entry, replace entry, and source pane
65+
- [ ] Escape closes find bar from source pane (not just from find entry)
66+
- [ ] Replace: replaces current selection if it matches
67+
- [ ] Replace All: replaces in both buffers
68+
- [ ] Close (X / Escape): hides bar, clears highlights
69+
70+
### Save
71+
- [ ] Save button insensitive until buffer changes
72+
- [ ] Save writes correct file; button goes insensitive; error dialog on failure
73+
- [ ] Save path tracks swaps: after swap, save writes to correct original file
74+
- [ ] Save after swap: pane contents and labels remain consistent (no un-swap)
75+
- [ ] Save after swap in dir file tab: watcher reload preserves swapped state
76+
77+
### File Watcher
78+
- [ ] External file change: auto-reloads if no unsaved changes
79+
- [ ] External change while unsaved: reload skipped
80+
- [ ] Save suppresses reload for 600ms (no reload loop)
81+
82+
### Close / Dialogs
83+
- [ ] Ctrl+W: closes window
84+
- [ ] Close with no unsaved changes: closes immediately
85+
- [ ] Close with unsaved changes: dialog with per-file checkboxes, Save/Close without Saving/Cancel
86+
- [ ] Save failure in dialog: window does NOT close
87+
- [ ] Overwrite confirm dialog: Escape key closes it
88+
- [ ] Ctrl+Shift+P: export patch dialog opens (sourceview doesn't consume it)
89+
90+
### Chunk Copy (Alt+Left / Alt+Right)
91+
- [ ] Alt+Right on a diff chunk: copies left chunk content to right (no file copy dialog)
92+
- [ ] Alt+Left on a diff chunk: copies right chunk content to left (no file copy dialog)
93+
- [ ] Alt+Left/Right with no current chunk: nothing happens
94+
- [ ] In dir window file tab: Alt+Left/Right copies chunks, does NOT trigger dir file copy
95+
96+
### Edge Cases
97+
- [ ] Identical files: "Files are identical" info bar; edit one side -> bar disappears; undo -> reappears
98+
- [ ] Binary files: info bar, panes read-only, patch button disabled, swap/nav/blanks/spaces/undo/redo buttons disabled
99+
- [ ] Empty files: no chunks, label shows "No changes"
100+
- [ ] Empty file vs non-empty file: all-insert chunks on one side
101+
- [ ] Very large files (10K+ lines): background diff, UI stays responsive
102+
- [ ] UTF-8 with multi-byte characters: inline highlighting correct
103+
- [ ] Files with only whitespace differences + Spaces toggle on: no differences shown
104+
- [ ] Files with only blank line differences + Blanks toggle on: no differences shown
105+
- [ ] Swap panes twice: returns to original state
106+
107+
## Navigation
108+
109+
- [ ] Alt+Up/Down: cursor moves to chunk start line
110+
- [ ] Click in pane, then Alt+Down: navigates from cursor position, not from top
111+
- [ ] Navigate chunks with cursor in different positions: cursor-aware seeking
112+
- [ ] Alt+Up/Down intercepted: sourceview move-lines action does not fire
113+
114+
## Directory Comparison
115+
116+
### Display
117+
- [ ] Tree view with expandable directories
118+
- [ ] Columns: Name (with icon), Size, Modification time
119+
- [ ] Directory headers above each pane with full path as tooltip
120+
- [ ] Status colors: blue (different), orange (left-only), green (right-only), dim italic (missing side)
121+
122+
### Navigation
123+
- [ ] Double-click file row: opens diff in new notebook tab
124+
- [ ] Enter on file row: opens diff in new notebook tab
125+
- [ ] Enter on directory row: expands/collapses folder
126+
- [ ] Enter after closing a file tab: still works (no broken activation state)
127+
- [ ] Left/Right arrow keys: switch focus between panes
128+
- [ ] Selection syncs between panes (highlighted on both sides)
129+
- [ ] Active pane: accent-color border; inactive pane: dim border + reduced opacity
130+
- [ ] Switching panes: no scroll jump, keyboard cursor syncs to selected row
131+
132+
### Toolbar
133+
- [ ] Copy to left (Alt+Left): copies right to left; confirms if overwriting
134+
- [ ] Copy to right (Alt+Right): copies left to right; confirms if overwriting
135+
- [ ] Delete (Delete key): trashes selected file with confirmation
136+
- [ ] Swap panes: rescans, updates headers and window title
137+
- [ ] Preferences (Ctrl+,)
138+
139+
### Context Menu
140+
- [ ] Right-click selects the clicked row first
141+
- [ ] "Open Diff": opens diff tab (disabled for directories)
142+
- [ ] "Copy to Left" / "Copy to Right": enabled for appropriate statuses
143+
- [ ] "Delete": always available
144+
145+
### Notebook Tabs
146+
- [ ] Directory tab labeled "Directory"
147+
- [ ] File tabs labeled with dir-relative names
148+
- [ ] Opening file diff tab: left pane has focus, cursor at line 1
149+
- [ ] Tab close (X): prompts for unsaved changes
150+
- [ ] Ctrl+W on Directory tab: closes entire window
151+
- [ ] Ctrl+W on file tab: closes that tab
152+
- [ ] Same file not opened twice: switches to existing tab
153+
154+
### File Watcher
155+
- [ ] Directory rescans on FS changes (500ms poll)
156+
- [ ] Expanded directories and selection restored after rescan
157+
- [ ] Open tab buffers reloaded unless unsaved
158+
159+
### Filters
160+
- [ ] Filtered names (.git, node_modules, etc.) excluded from tree
161+
- [ ] Custom filters from preferences applied
162+
- [ ] Hide hidden files on (default): dotfiles excluded from tree
163+
- [ ] Hide hidden files off: dotfiles shown in tree
164+
165+
### Edge Cases
166+
- [ ] Deeply nested directory trees
167+
- [ ] Copy directory recursively: all nested files copied
168+
- [ ] Dir -> file tab -> swap -> edit -> save dialog shows correct filename
169+
- [ ] Window close with unsaved tabs: dialog lists all unsaved files
170+
171+
## 3-Way Merge
172+
173+
### Display
174+
- [ ] Three panes: left (read-only), middle (editable), right (read-only)
175+
- [ ] Left/right save buttons hidden; only middle has save button
176+
- [ ] Left gutter: bands connecting left to middle
177+
- [ ] Right gutter: bands connecting middle to right
178+
- [ ] Conflict backgrounds: red/pink overlays on middle where left/right overlap
179+
- [ ] Chunk maps on both edges
180+
181+
### Toolbar
182+
- [ ] Undo/Redo (Ctrl+Z / Ctrl+Shift+Z): acts on active pane
183+
- [ ] Previous/Next change (Alt+Up/Down): per-pane cursor-based navigation
184+
- [ ] Navigation only places cursor on involved panes (not the third uninvolved pane)
185+
- [ ] Previous/Next conflict (Ctrl+J / Ctrl+K): navigates `<<<<<<<` markers in middle
186+
- [ ] Conflict label: "N conflicts" / "Conflict X of N" / "No conflicts"
187+
- [ ] Go to line (Ctrl+L)
188+
- [ ] Blanks / Spaces toggles
189+
- [ ] Preferences (Ctrl+,)
190+
191+
### Gutter Interactions
192+
- [ ] Only inward arrows: left gutter has right-arrows only, right gutter has left-arrows only
193+
- [ ] Click left gutter arrow: copies left chunk into middle
194+
- [ ] Click right gutter arrow: copies right chunk into middle
195+
- [ ] Right-click context menus: "Copy Left -> Middle" / "Copy Right -> Middle"
196+
197+
### Scroll Sync
198+
- [ ] Horizontal sync across all three panes
199+
- [ ] Vertical independent per pane
200+
201+
### Find / Replace
202+
- [ ] Ctrl+F/H: find bar highlights matches across all three buffers
203+
- [ ] Match count sums all three buffers
204+
- [ ] Replace All replaces in all three buffers
205+
206+
### Edge Cases
207+
- [ ] Binary files: all panes show info bar; all read-only; middle save hidden
208+
- [ ] All three identical: no changes, no chunks
209+
- [ ] Both sides modify same region: conflict overlay on middle
210+
- [ ] Copy left then copy right to same region: middle updates; undo works
211+
- [ ] Undo/redo across panes: applies to active pane's buffer
212+
213+
## VCS (Git) Window
214+
215+
### Display
216+
- [ ] Window title: "mergers -- reponame (git)"
217+
- [ ] ColumnView: Status and File columns
218+
- [ ] Status colors: Modified/Renamed = blue, Added/Untracked = green, Deleted = orange
219+
- [ ] Repo path label and changed file count
220+
221+
### Opening Diffs
222+
- [ ] Double-click / Enter: opens diff tab (HEAD vs working copy)
223+
- [ ] Modified/Renamed: HEAD content on left, working file on right
224+
- [ ] Added/Untracked: empty left, working file on right
225+
- [ ] Deleted: HEAD content on left, empty right
226+
- [ ] Already-open tab: switches to existing tab
227+
228+
### Context Menu
229+
- [ ] Right-click selects clicked row
230+
- [ ] "Open Diff": opens diff (disabled for Untracked)
231+
- [ ] "Discard Changes": runs `git checkout --` with confirmation (disabled for Untracked)
232+
- [ ] "Stage": runs `git add`
233+
- [ ] "Trash": moves to trash with confirmation (Untracked only)
234+
235+
### File Watcher
236+
- [ ] Auto-refresh on FS changes (500ms poll)
237+
- [ ] `.git` directory events ignored
238+
- [ ] Smart update: skips rebuild if status unchanged
239+
240+
### Notebook / Close
241+
- [ ] "Changes" tab first; Ctrl+W on it closes window
242+
- [ ] Ctrl+W on file tab: closes tab with unsaved check
243+
- [ ] Window close: checks all tabs for unsaved changes
244+
- [ ] Temp directory cleaned up on window destroy
245+
246+
### Security
247+
- [ ] Paths with `..` components refused
248+
249+
## Preferences
250+
251+
- [ ] Font: chooser dialog; live-apply
252+
- [ ] Color scheme: dropdown; live-apply; case-insensitive matching
253+
- [ ] Show line numbers: toggle; live-apply
254+
- [ ] Highlight current line: toggle; live-apply
255+
- [ ] Word wrap (None/Word/Character): dropdown; live-apply
256+
- [ ] Tab width (1-16): spin button; live-apply
257+
- [ ] Hide hidden files: toggle; default on; saved to settings
258+
- [ ] File filters: add/remove entries; saved on dialog close
259+
- [ ] Settings persisted to `~/.config/mergers/settings.toml`
260+
- [ ] Changes applied to all views in the parent window
261+
- [ ] Escape closes preferences dialog
262+
- [ ] Close button at bottom closes preferences dialog
263+
264+
## Dark Mode
265+
266+
- [ ] Linux/GNOME: auto-detects system dark mode
267+
- [ ] macOS: falls back to `defaults read -g AppleInterfaceStyle`
268+
- [ ] Sets `gtk_application_prefer_dark_theme` for GTK widgets
269+
270+
## Global Shortcuts
271+
272+
| Shortcut | Action |
273+
|---|---|
274+
| Ctrl+Q | Quit (close all windows) |
275+
| Ctrl+W | Close current tab/window |
276+
| Ctrl+, | Preferences |
277+
| Ctrl+Z | Undo |
278+
| Ctrl+Shift+Z | Redo |
279+
| Alt+Up / Ctrl+E | Previous change |
280+
| Alt+Down / Ctrl+D | Next change |
281+
| Ctrl+J | Previous conflict (merge only) |
282+
| Ctrl+K | Next conflict (merge only) |
283+
| Ctrl+F | Find |
284+
| Ctrl+H | Find + Replace |
285+
| F3 | Find next |
286+
| Shift+F3 | Find previous |
287+
| Ctrl+L | Go to line |
288+
| Ctrl+Shift+P | Export patch (file diff only) |
289+
| Alt+Left | Copy chunk right→left (file diff) / Copy file to left (dir) |
290+
| Alt+Right | Copy chunk left→right (file diff) / Copy file to right (dir) |
291+
| Ctrl+S | Save active pane |
292+
| Delete | Delete selected (dir only) |
293+
| Enter | Open selected file / expand-collapse dir (dir/VCS) |
294+
| Escape | Close find bar / go-to-line / preferences dialog |

src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::path::PathBuf;
2+
3+
pub mod myers;
4+
pub mod settings;
5+
pub mod ui;
6+
pub mod vcs;
7+
8+
#[derive(Clone, Debug)]
9+
pub enum CompareMode {
10+
Files {
11+
left: PathBuf,
12+
right: PathBuf,
13+
labels: Vec<String>,
14+
},
15+
Dirs {
16+
left: PathBuf,
17+
right: PathBuf,
18+
labels: Vec<String>,
19+
},
20+
Merge {
21+
left: PathBuf,
22+
middle: PathBuf,
23+
right: PathBuf,
24+
labels: Vec<String>,
25+
},
26+
Vcs {
27+
dir: PathBuf,
28+
},
29+
Welcome,
30+
}

src/main.rs

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ use clap::Parser;
66
use gio::prelude::ApplicationExtManual;
77
use gtk4::{Application, glib};
88

9-
mod myers;
10-
mod settings;
11-
mod ui;
12-
mod vcs;
9+
use mergers::{CompareMode, ui, vcs};
1310

1411
#[derive(Parser)]
1512
#[command(name = "mergers", version, about = "Visual diff and merge tool")]
@@ -22,30 +19,6 @@ struct Cli {
2219
labels: Vec<String>,
2320
}
2421

25-
#[derive(Clone)]
26-
pub enum CompareMode {
27-
Files {
28-
left: PathBuf,
29-
right: PathBuf,
30-
labels: Vec<String>,
31-
},
32-
Dirs {
33-
left: PathBuf,
34-
right: PathBuf,
35-
labels: Vec<String>,
36-
},
37-
Merge {
38-
left: PathBuf,
39-
middle: PathBuf,
40-
right: PathBuf,
41-
labels: Vec<String>,
42-
},
43-
Vcs {
44-
dir: PathBuf,
45-
},
46-
Welcome,
47-
}
48-
4922
fn main() -> glib::ExitCode {
5023
let cli = Cli::parse();
5124

0 commit comments

Comments
 (0)