Tags: joske/mergers
Tags
Add patch file support (unified & context diffs) (#46) * Add unified diff parser with tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add context diff parser with tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add patch hunk application with tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add patch file auto-detection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add CompareMode::Patch variant and CLI detection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Implement patch window for single-file and multi-file patches Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add round-trip parse+apply integration tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Multi-file patches use dir comparison with symlinked originals - Multi-file patches now open as a directory comparison showing only files mentioned in the patch, not the entire base tree - Left side uses symlinks to originals so saves write through to disk - Added PatchKind (Modified/Added/Deleted) to handle /dev/null paths - Fixed out-of-bounds panic when patch references lines beyond file end - Added Kompare to README comparison table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add apply-all, fix temp paths, conflict coloring, and cleanup - Apply-all button (⏪ icon) for file diffs and dir patch view - File diff: single undo operation, skips conflict-marker chunks - Dir view: handles modified, new (RightOnly), and deleted (LeftOnly) files - Fix dir view showing temp paths in headers/tooltips for patch mode - Labels control display names, separate tooltip_dirs for hover text - Resolve symlinks in open_file_diff so diff headers show original paths - Wire FileStatus::Conflict to color conflicted files red in dir view - Clean up patch temp dirs on shutdown and SIGTERM/SIGINT (ctrlc crate) - Filter Same entries and empty dirs from patch dir view - Update README with patch usage examples and comparison table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix security and robustness issues in patch support - Sanitize patch file paths: reject `..` traversal and absolute paths - Use unpredictable temp dir name and create_dir (not create_dir_all) to prevent symlink race attacks - Read only 50 lines via BufReader for patch detection instead of entire file - Remove unused --patch-format CLI flag - Propagate temp dir creation errors instead of silently swallowing - Add /dev/null detection and a/b prefix stripping to context diff parser for correct Added/Deleted classification - Preserve trailing newlines in apply_hunks and apply_hunks_best_effort Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use line-start marker detection for apply-all conflict skip Check for lines starting with <<<<<<< and >>>>>>> instead of exact string matches like "<<<<<<< original". Avoids false positives on files that happen to contain those exact substrings mid-line. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Apply fmt and clippy pedantic fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix Windows path traversal, single-file patch mismatch, and missing base handling - sanitize_patch_path: reject backslash traversal (..\) and Windows drive letters (C:) in addition to Unix absolute paths - Single-file patch mode: match patch entry by filename instead of blindly using first entry; error if multi-file patch has no match - Missing base files in directory patch mode now write error to both left and right sides (shows as Modified/conflict, not as RightOnly which misleadingly looks like an added file) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Reject ambiguous single-file patch matches and surface missing deleted bases - Single-file mode: reject multi-file patches where multiple entries share the same basename (e.g. src/foo/config.rs vs tests/config.rs) instead of silently picking the first match - Deleted-file entries with missing base now write error to both sides and mark as conflict, so they appear in the UI instead of vanishing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 26 edge case tests for patch parsing, application, and conflict detection Cover: path sanitization (mixed separators, hidden files, bare ..), apply_hunks error paths (overlapping, past EOF, context mismatch), delete-everything and empty-hunks-list cases, best-effort all-fail and trailing newline on conflict, unified diff git prefixes and no-newline-at-EOF marker, added/deleted file detection, context diff multi-hunk, is_patch_file boundary at line 50, find_conflict_blocks unmatched/nested markers, and conflict_at_cursor_fast boundaries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump * bump to 0.8.2 * bump clap * Fix bench CI: replace deprecated criterion::black_box with std::hint Also fix clippy format_push_string warnings in patch tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add 13 UI integration tests for patch viewing feature Single-file patch (8 tests): window title, pane content, chunk label, tab label. Multi-file patch (3 tests): window opens, shows patched files and subdirectories. Detection (2 tests): .diff extension triggers patch mode, Ctrl+W closes cleanly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add patch viewing and apply-all test cases to TESTING.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Mark 13 automated patch UI tests in TESTING.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review: apply-all targets, dedup, docs, cleanup - RightOnly apply now copies to real base dir (not temp symlink dir) - LeftOnly delete resolves symlink to remove original file - sanitize_patch_path docs updated to match strip behavior - is_right_a_patch reuses patch::is_patch_file instead of duplicating - cleanup_patch_temp_dirs clears vec after removal Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review round 2: gate removals, permissions, error dialog - Apply-all file removal gated to patch mode only (non-patch keeps files) - LeftOnly delete gated to patch mode only - Temp dir created with 0o700 permissions on Unix - show_patch_error uses active window when available, closes temp window - PR description updated: sanitize_patch_path strips (not rejects) absolute paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review round 3: symlink recursion, range validation, tests - read_dir_entries uses entry.file_type() for is_dir (avoids following symlinks to dirs, preventing infinite recursion with cyclic links) - parse_context_range validates end >= start to prevent underflow - Add 6 unit tests for collect_actionable_files status filtering logic Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review round 4: Added fallback, LeftOnly filter, registry cleanup - PatchKind::Added falls back to apply_hunks_best_effort on error (shows conflict markers instead of empty content) - LeftOnly excluded from actionable set in non-patch mode (was a no-op) - Window destroy removes entry from PATCH_TEMP_DIRS registry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review round 5: registry timing, dir cleanup, conflicts, parse_patch - Register PATCH_TEMP_DIRS only after successful window creation (not on early error paths) - Multi-file patch window cleans up temp dir on destroy via on_destroy callback - Conflict coloring applies to LeftOnly/RightOnly entries in .mergers-conflicts - parse_patch returns error when no file headers found (not Ok(vec![])) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review round 6: tempfile, /dev/null detection, hunk validation - Use tempfile crate for secure temp dir creation (0o700, collision-resistant) - Check conflicts before content comparison so identical-content conflicts still show as Conflict - Use stripped paths (not raw headers) for /dev/null detection in both unified and context diff parsers - Validate hunk old_count/new_count against actual line counts in apply_hunks - parse_patch returns error when no file headers found Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix Copilot review round 7: swap safety, tooltip swap, write errors - Disable apply-all button when panes are swapped in patch mode - Swap tooltip overrides alongside dirs/labels in swap handler - Log and mark as conflict when fs::write fails for patched files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Replace placeholder collect_actionable_files tests with logic tests The GTK ListStore walker can't be unit tested without main-thread init. Replace stub matches!() tests with focused predicate tests covering: status filtering (Different/RightOnly/LeftOnly included, Conflict/Same excluded), mixed-status filtering, and non-patch mode LeftOnly exclusion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix trailing newline on empty output, normalize backslashes, unscope marker filter - sanitize_patch_path: normalize backslashes to forward slashes so Windows-produced patches create correct dir structure on Unix; return String instead of &str - apply_hunks/apply_hunks_best_effort: don't append trailing newline when output is empty (delete-everything case) - read_dir_entries: remove hardcoded .mergers-conflicts skip that hid real user files with that name in non-patch mode - tempfile::TempDir::keep() returns PathBuf in tempfile 3.26 — no fix needed (copilot comment was incorrect) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Tighten context diff detection, fix conflict skip, symlink-to-dir safety - is_context_diff/is_patch_file: require both separator and file header pair to avoid mis-routing unified diffs containing "***************" - parse_patch: return error when context parsing yields no file entries - apply-all: skip chunks containing any conflict marker line, not just paired markers, to handle conflict blocks split across chunks - Bulk apply: re-mark directories as saving per file to extend the 600ms watcher suppression window across the entire operation - read_dir_entries: use symlink_metadata for size/mtime; treat fs::read errors as Different instead of Same (fixes symlink-to-dir misclassification) - Remove unused Python test imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove empty-line hunk special case, scope conflict marker to patch mode, make right pane read-only - Unified diff parser: remove is_empty() special case that treated blank lines as context, which could misparse patches with blank separator lines between hunks - scan_tree: only read .mergers-conflicts marker file in patch mode to avoid misclassifying files in normal directory comparisons - Single-file patch: set right text view to non-editable since it shows generated patched output - Add right_text_view to DiffViewResult for patch mode access Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fmt * bump * bump --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Performance optimizations and correctness fixes (#42) * Fix stale conflict cache and zero-width conflict regions Eagerly invalidate conflict marker/block caches in connect_changed so cursor handler never reads stale data while async re-diff is pending. Filter zero-width regions in middle_conflict_regions to match the middle-pane editor which already skips tag_start == tag_end. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Avoid clone on conflict cache hit, binary search conflict regions - get_markers/get_blocks return Rc<Vec<>> instead of cloning the Vec on every cache access - Binary search (partition_point) for conflict region overlap in merged_gutter_chunks instead of linear scan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add unit tests for conflict region and merged gutter logic Tests cover middle_conflict_regions (no overlap, basic overlap, zero-width filtering, adjacent merging, equal-chunk skipping) and merged_gutter_chunks (no conflicts, conflict marking with binary search). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Eliminate redundant stat syscall in directory scanning Derive is_dir from the metadata already fetched by DirEntry::metadata() instead of calling Path::is_dir() which does a second stat syscall. Also enable debug symbols in release profile for flamegraph profiling. Flamegraph confirmed: read_dir_entries dropped from 26% to 11.5% of runtime, and total directory comparison time reduced by ~22%. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.8.1 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Performance optimizations and benchmarks (#41) * Add criterion benchmarks at large-project scale Expose internal pure functions via #[doc(hidden)] _bench module for benchmarking. Inputs sized for real monorepo workloads: 500k-line diffs, 50k chunks (O(n²) conflict detection), 100k navigation chunks, 50k VCS entries, 500k-line conflict marker scans. Baseline results show O(n²) conflict functions taking 0.4-1.2s per call at 30-50k chunks — these run every frame during scroll. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Optimize O(n²) conflict detection and navigation to O(n+m) - conflict_flags: two-pointer sweep instead of nested loops (3163x at 50k) - middle_conflict_regions: two-pointer merge (4517x at 50k) - find_next_chunk: iterator-only, no Vec allocation (8.6x at 100k) - format_chunk_label: single-pass counting, no Vec (1.3x at 100k) - myers: push prefix before reverse instead of insert(0) The three O(n²) functions that ran every frame during scroll now complete in <260µs at 50k chunks, down from 0.4-1.2 seconds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Cache conflict flags, binary-search conflict_at_cursor, O(n) tree restore - chunk_map: pass pre-computed conflict flags instead of recomputing every frame in the draw callback - conflict_at_cursor: add find_conflict_blocks + conflict_at_cursor_fast with O(log n) binary search (12ns vs 18ms at 490k lines) - dir_window: HashSet + single forward scan for expanded-state restoration instead of O(n*m) nested loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fmt * clippy * add bench to CI * Add conflict_flags proptest and cache conflict scans in merge view - Add 5 unit tests + 2 proptests verifying conflict_flags two-pointer matches naive O(n*m) reference implementation - Cache find_conflict_markers/find_conflict_blocks in merge view cursor handler to avoid 3 redundant O(n) buffer scans per cursor move Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add benchmark layer to README testing section Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.8.0 * Use criterion-compare-action for benchmark regression detection Replaces bare `cargo bench` with boa-dev/criterion-compare-action which benchmarks both the base branch and PR, then posts a comparison comment showing regressions/improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix bench CI: specify benchName to avoid running lib harness The `--save-baseline` flag is criterion-specific and fails when passed to the built-in lib bench harness. Specifying benchName=benchmarks targets only the criterion bench binary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix bench CI: handle base branch without benchmarks criterion-compare-action fails when the base branch doesn't have the bench target. Replace with manual script that gracefully skips comparison when base has no benchmarks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
vcs (#40) * Fix button role name portability in UI tests AT-SPI reports "button" on some platforms and "push button" on others. Add is_button() helper to conftest and use it in all test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add VCS backend: current_branch, staged_files, commit, commit_message_prefill Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Show current branch name in VCS window title and toolbar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add status filter toggles (Modified/Untracked/Ignored) to VCS view Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add commit dialog to VCS view Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add UI tests for VCS branch display, filters, and commit button Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update TESTING.md with VCS commit dialog and filter toggles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.7.4 * Fix current_branch test for detached HEAD in CI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix VCS UI tests: toggle button role, title with branch name Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Remember window size and state across sessions (#39) * Remember window size and maximized state across sessions Saves width, height, and maximized flag to settings.toml on window close. Restores on next launch. Size is only saved when not maximized/fullscreen (like Meld). Closes #9. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Also remember fullscreen state across sessions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add unit and UI tests for window state persistence Unit tests: defaults, roundtrip, backward compat with old config. UI test: verifies settings.toml contains window dimensions after close. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix clippy field_reassign_with_default in test Use struct init syntax instead of default + reassign to satisfy clippy -D warnings on CI (Rust 1.94). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address PR review: save-on-close ordering, sentinel defaults, hermetic test - Only persist window state when close actually proceeds (not cancelled) - Use 0 as sentinel for window_width/height so per-window defaults apply until user has actually resized (VCS=700x500, merge=1200x600, etc.) - UI test uses tmp_path for XDG_CONFIG_HOME instead of real user config - Assert process exited before checking settings file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.7.3 * Add kill fallback for SIGTERM timeout in window state test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add window state persistence section to TESTING.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add pane switching, pull/delete chunk, fullscreen, and focus border (#38 ) * Add missing merge save UI test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add design doc for pane switching, pull/delete change, fullscreen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add implementation plan for pane switch, pull/delete change, fullscreen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add key mappings and pane switching for Alt+PageUp/Down Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add pull chunk (Alt+Shift+Left/Right) and delete chunk (Alt+Delete) actions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add F11 fullscreen toggle and register new accelerators Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add UI tests for pane switching, delete chunk, and fullscreen features Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * remove * ignore the plan files * Add focused-pane border highlight to diff and merge views Reuse the directory view's accent-color border for the active pane. Renamed CSS classes from dir-pane-* to pane-* for shared use. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Mark automated test coverage in TESTING.md and ignore docs/plans/ 54 checklist items now marked ✅ as covered by UI integration tests. Added docs/plans/ to .gitignore. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.7.2 * Fix Alt+Shift fallthrough and guard delete_chunk against empty ranges Alt+Shift+keys other than Left/Right now fall through to normal Alt mappings instead of returning None. delete_chunk early-returns when start >= end to prevent unintended deletions on empty chunk sides. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add unit tests for map_key_to_action key binding dispatch Covers Alt+Shift fallthrough regression: Alt+Shift+Up/Down must still map to prev/next-chunk rather than returning None. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address PR review: deduplicate test helpers, strengthen assertions - Move copy_fixture and focus_and_type to conftest.py (DRY) - Assert "MERGE_EDIT" content in merge save test (not just file changed) - Verify focused pane index changes after Alt+PageUp/Down (not just window survives) - Use copy_fixture from conftest in delete chunk test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Guard focus_and_type against missing windows, fix TESTING.md markers - focus_and_type now raises if no X11 windows found for pid - Align [x] markers with ✅ convention for items with UI tests - Uncheck pull chunk items that lack automated tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove unused import and dead variable in test_merge_save Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Refactor: extract shared helpers, deduplicate views (#35) * Quick-win refactors: extract helpers, fix grammar, use Side enum - Extract draw_gutter_band helper to deduplicate cairo band drawing - Extract is_binary helper in file I/O code - Extract find_window helper replacing inline root/downcast pattern - Extract git_show helper to deduplicate head_content/stage_content - Replace is_left:bool with Side enum in chunk_map/diff_state - Fix "1 changes" grammar bug → "1 change" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Wire nav buttons to GActions, fix merge toolbar label jitter - Replace duplicated button connect_clicked handlers with thin wrappers that delegate to the corresponding GActions (-272 lines) - Add grab_focus() to action handlers so keyboard focus follows navigation (clone-first pattern to avoid RefCell double-borrow) - Set fixed width_chars on chunk/conflict labels in merge toolbar to prevent layout shift when label text changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Extract shared key bindings, chunk map helper, fix viewport indicator - Extract map_key_to_action + KeyBindings struct to deduplicate ~120 lines of key handler logic between diff_view and merge_view - Extract create_chunk_map helper to deduplicate chunk map setup - Simplify scroll redraw wiring with loops - Fix chunk map viewport indicator using widget foreground color instead of editor scheme to detect dark/light GTK theme Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Deduplicate dir_window focus handlers and copy actions - Parameterize focus handlers with (own_view, other_view, is_left) loop - Parameterize copy actions with (name, src_dir, dst_dir, status) loop - Replace inline root/downcast patterns with find_window helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Split build_new_comparison_tab, extract tab/scroll helpers - Extract make_closeable_tab_label and display_name helpers - Extract open_merge_comparison_tab (merge_view.rs) and open_dir_comparison_tab (dir_window.rs) paralleling open_file_diff_paths - Simplify build_new_comparison_tab from 332 to ~160 lines - Move setup_scroll_sync_3way from merge_view.rs to scroll_sync.rs - Extract sync_hscrolls helper, deduplicating horizontal sync wiring Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add context menu row targeting test case to TESTING.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix clippy warnings, formatting, and missing blank-path guard - Move DIFF_KEYS and MERGE_KEYS statics to module level (clippy items_after_statements) - Add is_blank_path guard in merge view open-externally action - Run cargo +nightly fmt on files committed without formatting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix reference cycles in scroll sync and page-removed handler - sync_hscrolls: capture Adjustments instead of full ScrolledWindows to avoid holding strong refs to pane widgets in signal closures - open_dir_comparison_tab: use weak ref for widget in page-removed handler and disconnect the signal after it fires to prevent handler accumulation and widget leaks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add dbus-run-session to test-release and document required UI test packages Wrap pytest in dbus-run-session so AT-SPI works in containers. List all system packages needed to run the UI integration tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix collapse-all segfault and disable context menu on missing-side files - Collapse-all iterated with a pre-computed bound; collapsing rows removed children and shrank the model, causing out-of-bounds access. Fixed by iterating in reverse. - Context menu actions (Delete, Copy, Open Externally, Copy Path) were available on the pane where a file doesn't exist, leading to wrong-side operations. Now all per-file actions are disabled on the missing side. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add collapse/expand all toolbar buttons, canonicalize dir header tooltips - Add linked collapse-all / expand-all button pair to directory toolbar - Canonicalize directory paths so pane header tooltips show full absolute paths - Update TESTING.md with collapse/expand, missing-side context menu test cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add copy-path buttons to directory pane headers Frameless edit-copy-symbolic button next to each header label copies the full absolute directory path to the clipboard. Path tracks swaps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Left-align modification time columns in directory comparison Bold text on "changed" rows made right-aligned dates shift relative to non-bold rows. Switch date columns to left-alignment (sizes stay right-aligned) and widen from 180px to 200px. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Make directory comparison columns resizable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Show conflict chunks in red on merge view chunk map strips Pass conflict flags through to chunk map drawing so chunks that overlap between left and right sides render with band_conflict() color instead of the regular blue/green. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix Replace All modifying read-only buffers in merge view Replace All now skips non-editable TextViews, so in 3-way merge it only replaces in the middle (editable) buffer. Updated TESTING.md to match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Return focus to active pane when closing find bar Clone the TextView out of the RefCell before calling grab_focus() to avoid a borrow conflict — grab_focus() triggers the focus-enter handler which needs borrow_mut() on the same RefCell. Also fix Replace All modifying read-only buffers in merge view: now skips non-editable TextViews. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Extra column, Unstage action, and stage-conflict warning to VCS view - Parse staged/partially-staged state from porcelain XY bytes - Skip staged detection for conflict entries (XY is conflict type, not index/worktree status) - Add "Extra" column showing Staged/Partially staged - Add "Unstage" context menu action (git restore --staged) - Stage on conflicted file shows confirmation dialog - Enable/disable Stage vs Unstage based on file state Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump * fmt * bump to 0.7.1 * Fix clippy warnings and Copilot review comments - Add #[must_use] to unstage_file - Add #[allow(too_many_arguments)] to draw_chunk_map - Add -- argument terminator in stage_file and unstage_file - Distinguish fully staged from partially staged for Stage action Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
refactoring + bugfixes (#34) * Extract shared helpers from diff/merge views and consolidate file watchers Move duplicated code into common.rs: - build_find_bar(): find/replace bar + goto-line + 5 GActions (~220 lines x2) - build_nav_button_group(): prev/next navigation buttons - build_undo_redo_box(): undo/redo buttons wired to active view - build_filter_toggles(): blank-line and whitespace toggle buttons - start_file_watcher(): generic file watcher with on_tick callback - build_app_window(): shared window builder with notebook and actions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Split common.rs (4,419 lines) into 12 focused submodules Convert the monolithic common.rs into a common/ directory module with dedicated files for each responsibility area: editor, gutter, helpers, tabs, find_bar, scroll_sync, navigation, toolbar, search, chunk_map, file_watcher, and tests. Pure code motion with no logic changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Refactor FileTab into Diff/Merge enum with PaneInfo Replace flat FileTab struct with a Diff/Merge enum. Introduce PaneInfo to hold per-pane (path, buffer, save button) state. Accessor methods (saveable_panes, is_reloadable) eliminate dummy save buttons and empty-path guards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Extract shared editor action helpers (refresh, save-as, save-all) Move duplicated refresh/save-as/save-all logic from diff_view and merge_view into shared helpers in common/helpers.rs. Each view still wires its own actions but calls the shared functions for the actual save/reload/dialog logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Deduplicate dir_window button handlers via action group Replace ~140 lines of duplicated copy-left/copy-right/delete button click handlers with one-liner activate_action calls. The action group definitions (already used for keyboard shortcuts) now serve both buttons and keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix merge Save As stale tab path and remove duplicate action group insert - Pass middle_tab_path through MergeViewResult so FileTab::Merge shares the same Rc as the save-as action, keeping close/unsaved dialogs in sync after Save As. - Remove duplicate vcs_tab.insert_action_group call in vcs_window.rs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix merge tab close prompt showing stale path after Save As Read the live middle_tab_path Rc instead of capturing a static String from second_path at construction time. The confirm_unsaved_dialog now shows the current file name after Save As. The merge watcher intentionally tracks original source paths (not the Save As target) since the watcher monitors source files being merged, matching Meld's behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update TESTING.md with missing test cases Add 42 test cases for features not previously covered: Save As, Save All, Open Externally, Refresh, tab navigation (Ctrl+1..9), New Comparison (Ctrl+N), blank comparison, wrap-around navigation, chunk map viewport indicator, and keyboard shortcuts dialog. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix error message for non-existent single path argument `path.is_dir()` returns false for non-existent paths, causing a misleading "single file argument not supported" error. Add an explicit existence check before the is_dir/is_file branches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Support -L/--label flags for directory comparison Wire labels through build_dir_window → build_dir_tab to override pane header text. Labels swap correctly with the swap-panes button. Tooltips still show the actual directory path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix CLI help: show <LABEL> instead of <LABELS> for -L flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Repaint chunk borders when clicking inside a chunk Cursor tracking updated current_chunk index but never triggered a redraw, so bold borders only appeared after keyboard navigation or scrolling. Now queue_draw on filler overlays and gutters when the current chunk changes. Fixed in both diff and merge views. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Scroll to cursor after toolbar undo/redo The toolbar undo/redo buttons called buf.undo()/redo() without scrolling the view, causing the viewport to jump away from the edit location. Now calls scroll_mark_onscreen after each operation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update TESTING.md: vertical scroll is synced by chunk Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix find/replace: cross-pane navigation, Escape, macOS shortcut, undo - Find next/prev now cycles across panes when no match in current pane - Escape closes find bar from widget level (not just text views) - Use Cmd+Shift+H for Find & Replace on macOS (Cmd+H is system Hide) - Replace All uses delete+insert in a user action group so undo works Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix find next/prev cycling across panes find_next_match wrapped within the same buffer, so cross-pane search was never reached. Split into find_next_match_no_wrap with a skip_current flag: current pane skips the cursor position, other panes search from start/end without skipping. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Clarify TESTING.md wording for Alt+Left/Right in dir file diff tab Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update TESTING.md: directory tab shows dir names, not "Directory" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Show full absolute path in pane header tooltip Canonicalize file_path so the tooltip always shows the full path instead of a relative one when launched with relative arguments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Prevent duplicate file tabs in directory comparison open_file_diff now checks if a tab with the same rel_path is already open and switches to it instead of opening a duplicate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix TESTING.md: tab switching is Alt+1..9, not Ctrl+1..9 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use native trash command on macOS instead of GIO GIO's File::trash() silently deletes directories permanently on macOS instead of moving them to Finder's Trash. Use the native macOS trash command which works correctly. GIO is still used on Linux. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix merge view chunk highlighting, conflict drawing, and navigation - Add conflict-aware cursor detection: clicking conflict regions on left/right panes now highlights the conflict band via fallback to merged_gutter_chunks when chunk_at_cursor returns None - Separate conflict drawing from regular chunk drawing: skip conflict chunks in draw_chunk_backgrounds via is_conflict flags, draw them with dedicated conflict stroke functions at full alpha - Add bold border support for conflict regions (current chunk highlight) - Fix navigation guard: prevent cursor_position_notify from overriding current_chunk during Alt+Up/Down navigation - Collapse conflict bands into single navigation targets on side panes so Alt+Up/Down skips entire conflict band in one step - Fix chunk map viewport indicators for merge view by redrawing on all three scroll adjustments - Focus middle pane on 3-way merge open (CLI and new-comparison tab) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix scroll sync skipping Equal regions in line mapping map_line_through_chunks advanced past Equal chunks without checking if the source line fell inside them, causing the mapped position to jump to the next non-equal chunk boundary. This made the left pane "stick" in 3-way merge when left and middle had few differences. Now returns the correct 1:1 mapped position when source line is within an Equal region. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Track Save As directory changes in file watcher for diff and merge views FileWatcher now supports dynamically adding watched paths via watch(). Both file_window and merge_view monitor save paths for directory changes and tell the watcher to pick up new directories after Save As. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Show conflicting files in VCS window and open them in 3-way merge view Add VcsStatus::Conflict for unmerged files (UU, AA, DD, DU, UD, AU, UA). Conflict files display with red "Conflict" label in the VCS file list. Opening a conflict launches a 3-way merge: ours (:2:) on left, working copy in middle, theirs (:3:) on right. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.7.0 * Fix flaky UI tests by retrying keystrokes and polling for label changes Centralize xdotool key-sending in conftest.py with a 200ms delay after focus to let GTK process the event. Add send_keys_until() that retries keystrokes up to 3 times if the expected label change doesn't appear within the timeout (default 1s per attempt, 5s max). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * focus left pane on window show --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add tab navigation, file actions, new comparison, and blank comparison ( #33) * Add Refresh, Open Externally, Save As, Save All, and dir context menu actions - Refresh (Ctrl+R / F5): reload files from disk with unsaved-changes confirmation - Open Externally (Ctrl+Shift+O): open focused file in system default app - Save As (Ctrl+Shift+S): save focused pane to a new path, updating label - Save All (Ctrl+Shift+L): save all dirty panes at once - Dir context menu: add Open Externally, Collapse All, Expand All, Copy File Path - Register accelerators in file, VCS, and merge windows - Update keyboard shortcuts dialog and README Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add tab navigation with Ctrl+Alt+PageUp/Down and Alt+1-9 Uses hardware keycodes for Alt+digit matching to avoid macOS input method translating Alt+number into special characters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add New Comparison (Ctrl+N) with notebook tabs everywhere Wrap file and merge windows in Notebook widgets so all window types support tabs. Ctrl+N opens a "New Comparison" picker tab where users can start file, directory, or 3-way merge comparisons — all opening as tabs in the current window rather than new windows. Extract build_dir_tab from build_dir_window to enable embedding directory comparisons as tabs. Set all window titles to "Mergers" with specific names shown in tab labels instead. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add Blank Comparison button to new comparison tab and welcome screen Opens an empty 2-way file diff with "Untitled" labels. Each pane's save button opens a pane-specific Save As dialog for blank paths; after saving, the pane becomes file-backed and Ctrl+S saves normally. Refresh and Open Externally are no-ops on blank panes, Save All skips them. The unsaved-changes confirmation dialog now processes saves sequentially — async Save As dialogs pause the chain via notify::sensitive and auto-resume, so each blank pane gets its own Save As prompt when closing with unsaved changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.6.1 * Fix tab_path tracking, merge tab registration, VCS tab index, and reload regression - Share tab_path Rc<RefCell<String>> between DiffPane and FileTab so Save As updates the path seen by close-tab unsaved checks (no more "Untitled" after save) - Register merge tabs in open_tabs so window-close unsaved guard covers them - Fix VCS "already open" tab lookup to use widget-based page_num instead of index arithmetic that breaks when non-file tabs are present - Skip tabs with empty paths in reload_file_tab to prevent persistent reload failures from merge tabs in directory windows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Address Copilot review: swap tab_path centrally, fix doc comment, normalize unsaved labels - Swap tab_path alongside save_path in build_diff_view's swap handler so all consumers (including file_window) get correct FileTab paths after pane swap - Remove redundant tab_path swaps from open_file_diff/open_file_diff_paths callbacks (now handled centrally, avoiding double-swap) - Fix doc comment on build_new_comparison_tab: dirs and merge open as tabs, not new windows - Normalize empty paths to "Untitled" in merge_view's window close-request unsaved collection, matching handle_notebook_close_request behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix window titles, dir tab watcher leak, and open_externally error reporting - Set initial window title to comparison title in file_window and merge_view instead of hardcoded "Mergers" - Stop dir tab filesystem watcher via connect_page_removed so it's cleaned up regardless of how the tab is closed (close button, Ctrl+W, window close) - Check non-zero exit status in open_externally, not just spawn errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add UI tests verifying window title stays 'Mergers' after pane swap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add preferences improvements and keyboard shortcuts dialog (#32) * Update README with recent features Add synchronized scrolling, wrap-around navigation, and chunk map viewport indicator to the features list and comparison table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add editor and comparison preferences Add insert spaces instead of tabs, show whitespace characters, ignore blank lines, and ignore whitespace settings to the preferences dialog. Per-view toggle buttons initialize from saved defaults while remaining independently toggleable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * bump to 0.6.0 * Use Cmd instead of Ctrl for shortcuts on macOS Replace <Ctrl> with <Primary> in GTK accelerator strings so they map to Cmd on macOS and Ctrl on Linux/Windows. Add a PRIMARY_MODIFIER constant for EventControllerKey handlers and update tooltip strings to show the platform-appropriate key name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Use Cmd instead of Ctrl for shortcuts on macOS Replace Ctrl with Cmd for all keyboard shortcuts on macOS via set_platform_accels helper and has_primary_modifier for key event handlers. Tooltips show the platform-appropriate modifier name. Ctrl continues to work on Linux/Windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add keyboard shortcuts dialog to preferences Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Update README with keyboard shortcuts reference feature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
PreviousNext