Skip to content

fix: resolve 20 bugs and performance issues from scan audit#56

Merged
leszek3737 merged 2 commits into
mainfrom
fix52
May 24, 2026
Merged

fix: resolve 20 bugs and performance issues from scan audit#56
leszek3737 merged 2 commits into
mainfrom
fix52

Conversation

@leszek3737

@leszek3737 leszek3737 commented May 24, 2026

Copy link
Copy Markdown
Owner

sorting: wrap full tuple in Reverse() for tiebreaker ordering
viewer: fix empty file line_offsets, remove RefCell render mutation
main: log leave error before resume, drop leave propagation
reader: signed_duration_since prevents panic on future timestamps
mime: eliminate double I/O in detect_mime, .lock->text/config
chunk_copy: remove duplicate set_file_times, cancel flush, TOCTOU
watcher: fix path_cache self-mapping, remove dead fallback branch
watcher_sync: skip non-canonicalizable paths in desired set
search: remove redundant contains, limit visibility, drop allow
path: add clean_path normalization, reject ${123} env var names
command_line: clear command_draft on execute/cancel/esc
menu_actions: join warning and local-menu messages
mouse: rename _viewer_state, replace unreachable!() with no-op
user_menu: strip_prefix char-aware, single-strip leading +
theme: remove redundant hex digit pre-validation
dialogs: saturate u16 scroll, fix status message double-wrap
types: bounds check in hotlist_remove before Vec::remove
pickers: clarify hotlist error message for non-directory case
input/dialogs: fix is_same_file fallback exhaustiveness
mode_dispatch: guard menu_total_count % 0 and underflow

Summary by Sourcery

Fix multiple scan-audit issues across sorting, file operations, paths, UI, and input handling to improve correctness, robustness, and UX.

Bug Fixes:

  • Correct reverse file size, modification time, and birth time sorting to apply reverse ordering to the full tiebreaker tuple.
  • Prevent panics and incorrect behavior in hotlist removal, menu navigation, date formatting for future timestamps, and path validation by adding bounds checks and safer time and math handling.
  • Normalize and clean expanded paths, and treat invalid brace environment variable names literally instead of expanding them.
  • Ensure file watcher path caching and watcher sync desired paths use proper canonical mappings and skip non-canonicalizable paths.
  • Fix MIME detection to avoid double I/O when checking directories and update .lock files to be treated as text configuration.
  • Ensure temporary copy publishing cleans up correctly on all rename errors, avoids duplicate time-setting, and removes unnecessary flush on cancellation.
  • Make the file viewer handle empty files and non-text modes correctly when computing line offsets.
  • Improve same-file detection fallback logic to avoid incorrect equality when only one side canonicalizes successfully.
  • Fix command-line state and hotlist picker messaging by clearing drafts on exit and clarifying error messages for non-directory entries.
  • Guard menu navigation against division-by-zero and underflow when there are no menu pages and clamp dialog scrolling to u16 limits.
  • Ensure user menu parsing and status messaging handle prefixes and warnings correctly without overwriting messages.
  • Adjust help dialog scroll offset casting and viewer line offsets to avoid rendering issues with large offsets and empty files.

Enhancements:

  • Refine watcher fallback creation to use a single initialization path and simplify error handling.
  • Simplify reverse sort key types by introducing type aliases that wrap the full tiebreaker tuple in Reverse.
  • Clarify invalid path error messages presented to users and reuse those messages directly in dialogs.
  • Limit visibility of recursive search APIs and remove redundant configuration attributes for cleaner search internals.
  • Make mouse handling use a properly named viewer state parameter and route scroll and click events consistently.
  • Log terminal leave errors without aborting recovery while still ensuring resume is attempted.
  • Simplify theme color parsing by relying on hex parsing for validation rather than pre-validating each digit.
  • Adjust viewer line offset computation to always provide an initial offset for empty text buffers.

Documentation:

  • Add a detailed scan-results report documenting identified bugs, technical debt, and optimizations across the codebase.

@sourcery-ai

sourcery-ai Bot commented May 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes multiple bugs and polish items identified by a scan audit: normalizes and hardens path/env expansion semantics, corrects reverse sort key implementations, tightens watcher and watcher_sync behavior, improves MIME detection and config classification, fixes viewer edge cases and mouse handling, adjusts chunked copy publishing semantics, clarifies UI messages and dialog behavior, and plugs several small panics/logic gaps across input, dialogs, hotlist, menu, and mode dispatch paths.

Sequence diagram for updated chunked copy publish_temp behavior

sequenceDiagram
    participant App as copy_with_progress
    participant Temp as copy_to_temp
    participant Pub as publish_temp
    participant FS as fs

    App->>Temp: copy_to_temp(src, temp_dest, cancel)
    Temp-->>App: io::Result<()> or Interrupted

    App->>Pub: publish_temp(temp_dest, dest, cancel, overwrite)
    alt rename succeeds
        Pub->>FS: rename(temp_dest, dest)
        FS-->>Pub: Ok(())
        Pub-->>App: Ok(())
    else dest already exists
        Pub->>FS: rename(temp_dest, dest)
        FS-->>Pub: Err(AlreadyExists)
        Pub->>Pub: cleanup_file(temp_dest)
        Pub-->>App: Err(AlreadyExists)
    else other error
        Pub->>FS: rename(temp_dest, dest)
        FS-->>Pub: Err(other)
        Pub->>Pub: cleanup_file(temp_dest)
        Pub-->>App: Err(other)
    end
Loading

File-Level Changes

Change Details Files
Fix reverse sort key implementations to reverse full composite keys instead of just the primary field.
  • Introduce SizeKeyReverse and MtimeKeyReverse tuple aliases wrapping the full (size/time, name tiebreakers) in Reverse
  • Refactor reverse_size_sort_key, reverse_mtime_sort_key, and reverse_btime_sort_key to construct these composite Reverse keys from the forward key helpers, preserving correct tiebreaker ordering
src/ops/sorting.rs
Adjust chunked copy publishing semantics to rely on rename errors, avoid redundant work, and simplify cancellation behavior.
  • Remove src_metadata parameter from publish_temp and its callers, dropping redundant permission/time copying now handled elsewhere
  • Handle AlreadyExists, other errors, and success cases from fs::rename via a match that always cleans up temp_dest on error
  • Remove a redundant writer.flush() on cancellation in copy_to_temp
  • Update tests to call the new publish_temp signature without metadata
src/ops/chunk_copy.rs
Normalize and harden path/environment expansion semantics and is_same_file fallback behavior.
  • Change expand_path to always pass expanded results through clean_path and to clean normalized tilde-expanded paths rooted at the home directory
  • Tighten expand_brace_var to treat ${123}-style names as literals when they don’t start with a valid env var character instead of silently dropping them
  • Adjust dialogs::is_same_file fallback match to only call clean_path comparison in the partial-error cases, making match arms exhaustive and explicit
src/fs/path.rs
src/input/dialogs.rs
Improve filesystem watcher behavior and watcher_sync desired path handling.
  • Simplify create_fallback by returning an io::Result<&mut Fallback> via ok_or_else instead of manually unpacking and returning an error on None
  • Fix path_cache to map original input paths to canonicalized paths instead of self-mapping them on cache miss
  • Change watcher_sync canonical_desired_paths to skip (log-only) non-canonicalizable paths instead of inserting raw paths into the desired set
src/fs/watcher.rs
src/app/watcher_sync.rs
Refine MIME detection logic and configuration MIME classification.
  • Avoid a preliminary path.is_dir() call and instead check directory-ness via file.metadata() after opening, removing an extra stat
  • Short-circuit directory detection by returning inode/directory when metadata indicates a directory
  • Reclassify .lock files as text/config instead of application/json in config_mime
src/app/mime.rs
Tighten viewer behavior for empty files and binary/text distinction, and fix mouse handling naming/use.
  • Ensure compute_line_offsets returns vec![0] for empty byte slices so line_offsets is consistent with a single logical empty line
  • Change line_offsets initialization to only depend on open_as_text (not emptiness), so binary mode yields no offsets while empty text files still do
  • Rename _viewer_state parameter in handle_mouse_event to viewer_state and propagate the new name throughout scroll and click handlers, removing a misleading unused-parameter underscore
src/ui/viewer.rs
src/input/mouse.rs
Improve date formatting robustness and leave/resume terminal recovery logging.
  • Use Local::now().signed_duration_since(datetime).num_days() and an inclusive 0..=365 check to avoid panics with future timestamps and to format only reasonable ranges with the recent format
  • In recover_terminal_state, log any error from leave_tui_stdout but do not propagate it if resume succeeds; always propagate resume_result errors
src/fs/reader.rs
src/main.rs
Make UI and input behavior clearer and more robust in dialogs, command line, pickers, menu actions, and menus.
  • Improve validate_path_name error to explicitly mention '..' disallowance and pass the human-readable string directly to status_message instead of wrapping it again
  • Refine is_same_file non-canonical case to only use clean_path comparison on specific error combinations instead of blanket fallback
  • Clamp help dialog scroll offset to u16::MAX when casting from usize to avoid potential overflow, and adjust scroll tuple construction accordingly
  • Clear command_draft whenever the command line is executed, canceled via Ctrl-C, or exited with Esc so stale drafts don’t leak across uses
  • Improve hotlist picker error text to say ‘is no longer a valid directory’ instead of ‘no longer exists’
  • Preserve both user menu warnings and the ‘local menu requires confirmation’ note by aggregating them into a single status_message joined with a separator
src/input/dialogs.rs
src/ui/dialogs.rs
src/input/command_line.rs
src/input/pickers.rs
src/input/menu_actions.rs
Harden user menu parsing and hotlist operations against edge cases and panics.
  • Update parse_menu_with_warnings body continuation handling to use strip_prefix over a small set of leading characters (tab, space, '+'), removing a brittle fixed-slice index on next[1..]
  • Change parse_condition to strip a single leading '+' via strip_prefix rather than trimming all '+' characters, preserving meaningful content
  • Add a bounds check at the start of hotlist_remove and no-op when index is out-of-range instead of panicking on Vec::remove
src/app/user_menu.rs
src/app/types.rs
Minor correctness/polish fixes across search, theme, and input mode dispatch.
  • Limit search_content_recursive visibility to the impl (fn), removing an unnecessary public API surface and dropping a no-longer-needed clippy allow
  • Simplify case-sensitive search path by treating the presence of a memmem match as sufficient and setting match_found directly, removing a redundant line_text.contains
  • Drop redundant pre-validation of hex digits in parse_color and rely on u8::from_str_radix with ok() to determine validity, keeping behavior but reducing checks
  • Cache menu_total_count into a local variable in handle_menu_mode and reuse it for left/right navigation and modulo math, preventing a potential divide-by-zero on recomputation or inconsistency
src/ops/search.rs
src/ui/theme.rs
src/input/mode_dispatch.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues, and left some high level feedback:

  • In app/mime::detect_mime, switching from path.is_dir() to checking file.metadata().is_ok_and(|m| m.is_dir()) means directories that can't be opened (permission errors, special files) will no longer be classified as inode/directory and will fall back to extension-based detection; consider keeping the cheap path.is_dir() fast path or handling metadata errors explicitly to preserve the previous behavior.
  • In ops/chunk_copy::publish_temp you removed preserve_permissions(temp_dest, src_metadata) and the subsequent set_file_times call, which changes copy semantics so the destination no longer inherits the source's permissions (and only indirectly gets timestamps via copy_with_progress); if this behavior change isn't intentional, it may be better to keep a single, clearly located permissions/timestamps preservation step.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `app/mime::detect_mime`, switching from `path.is_dir()` to checking `file.metadata().is_ok_and(|m| m.is_dir())` means directories that can't be opened (permission errors, special files) will no longer be classified as `inode/directory` and will fall back to extension-based detection; consider keeping the cheap `path.is_dir()` fast path or handling metadata errors explicitly to preserve the previous behavior.
- In `ops/chunk_copy::publish_temp` you removed `preserve_permissions(temp_dest, src_metadata)` and the subsequent `set_file_times` call, which changes copy semantics so the destination no longer inherits the source's permissions (and only indirectly gets timestamps via `copy_with_progress`); if this behavior change isn't intentional, it may be better to keep a single, clearly located permissions/timestamps preservation step.

## Individual Comments

### Comment 1
<location path="src/app/mime.rs" line_range="8-14" />
<code_context>
 use crate::app::types::FileCategory;

 pub fn detect_mime(path: &Path) -> Option<String> {
-    if path.is_dir() {
-        return Some("inode/directory".to_string());
-    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Directory MIME detection can now fail for plain directory paths

The new flow opens the path before checking if it’s a directory, so on platforms where `File::open` fails for directories, directory paths now go through the `Err(_) => fallback()` branch and are guessed by filename instead of returning `inode/directory`. To preserve the previous behaviour, consider checking `path.metadata().map(|m| m.is_dir())` / `path.is_dir()` before `File::open`, or falling back to path-based metadata when `open` fails with `IsADirectory`.
</issue_to_address>

### Comment 2
<location path="src/ops/search.rs" line_range="905" />
<code_context>
                         }
                     };
                     let match_found = if case_sensitive {
-                        line_text.contains(pattern)
+                        true
</code_context>
<issue_to_address>
**issue (bug_risk):** Case-sensitive content search now matches every line unconditionally

With this change, `case_sensitive` mode no longer uses `pattern` and instead treats every line as a match, breaking case-sensitive search. To preserve the intended behavior while keeping the refactor, this should remain:

```rust
let match_found = if case_sensitive {
    line_text.contains(pattern)
} else {
    Self::contains_case_insensitive(&line_text, pattern_lower)
};
```
</issue_to_address>

### Comment 3
<location path="scan-results.md" line_range="26" />
<code_context>
+- [line 113-120] `delete_word_backward()` — podwójna iteracja graphemes.
+- [line 1022-1027] `sync_unfiltered_selection()` tworzy HashMap co wywołanie. Cache/dirty flag.
+
+**Summary:** Zdrowy strukturalnie. Jeden potential panic, kilka niepotrzebnych klonowań.
+
+---
</code_context>
<issue_to_address>
**nitpick (typo):** Poprawa wyrażenia „potential panic” na polską formę „potencjalny panic” / „potencjalny błąd/panic”.

W zdaniu „Jeden potential panic, kilka niepotrzebnych klonowań” słowo „potential” wybija się z polskiego kontekstu. Warto zmienić je na „potencjalny panic” lub „potencjalny błąd (panic)”, spójnie z resztą dokumentu.

```suggestion
**Summary:** Zdrowy strukturalnie. Jeden potencjalny błąd (panic), kilka niepotrzebnych klonowań.
```
</issue_to_address>

### Comment 4
<location path="scan-results.md" line_range="69" />
<code_context>
+- [line 234-238] `WildcardSimple::matches` tworzy `Vec<char>` z name每次 wywołania.
</code_context>
<issue_to_address>
**issue (typo):** Usunięcie chińskich znaków („每次”, „每个”) użytych omyłkowo w opisach.

Znaki „每次” / „每个” (np. w „Vec<char> z name每次 wywołania”) wyglądają na artefakt wklejania/enkodowania, a nie zamierzone słownictwo. Proponuję zastąpić je polskimi odpowiednikami („każdorazowo”, „dla każdego wywołania” itp.), żeby opis był czytelny.

```suggestion
- [line 234-238] `WildcardSimple::matches` tworzy `Vec<char>` z name przy każdym wywołaniu.
```
</issue_to_address>

### Comment 5
<location path="scan-results.md" line_range="582" />
<code_context>
+**Technical Debt**
+- Brak log rotation — 10 MB danych znika.
+- `Ordering::SeqCst` w teście vs `Relaxed` w produkcji — niespójność.
+- `format!` na error path — `write!` bez allokuje mniej.
+
+**Optimizations**
</code_context>
<issue_to_address>
**nitpick (typo):** Poprawka literówki „allokuje” na „alokuje”.

W innych miejscach używasz formy „alokuje”, więc dla spójności zastosuj ją także tutaj („write! bez alokuje mniej”).

```suggestion
- `format!` na error path — `write!` bez alokuje mniej.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/app/mime.rs
Comment thread src/ops/search.rs
@@ -904,7 +903,7 @@ impl FileSearch {
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Case-sensitive content search now matches every line unconditionally

With this change, case_sensitive mode no longer uses pattern and instead treats every line as a match, breaking case-sensitive search. To preserve the intended behavior while keeping the refactor, this should remain:

let match_found = if case_sensitive {
    line_text.contains(pattern)
} else {
    Self::contains_case_insensitive(&line_text, pattern_lower)
};

Comment thread scan-results.md Outdated
- [line 113-120] `delete_word_backward()` — podwójna iteracja graphemes.
- [line 1022-1027] `sync_unfiltered_selection()` tworzy HashMap co wywołanie. Cache/dirty flag.

**Summary:** Zdrowy strukturalnie. Jeden potential panic, kilka niepotrzebnych klonowań.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): Poprawa wyrażenia „potential panic” na polską formę „potencjalny panic” / „potencjalny błąd/panic”.

W zdaniu „Jeden potential panic, kilka niepotrzebnych klonowań” słowo „potential” wybija się z polskiego kontekstu. Warto zmienić je na „potencjalny panic” lub „potencjalny błąd (panic)”, spójnie z resztą dokumentu.

Suggested change
**Summary:** Zdrowy strukturalnie. Jeden potential panic, kilka niepotrzebnych klonowań.
**Summary:** Zdrowy strukturalnie. Jeden potencjalny błąd (panic), kilka niepotrzebnych klonowań.

Comment thread scan-results.md Outdated
- [line 827] `search_in_file` — 106 linii, przekracza limit 100.

**Optimizations**
- [line 234-238] `WildcardSimple::matches` tworzy `Vec<char>` z name每次 wywołania.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (typo): Usunięcie chińskich znaków („每次”, „每个”) użytych omyłkowo w opisach.

Znaki „每次” / „每个” (np. w „Vec z name每次 wywołania”) wyglądają na artefakt wklejania/enkodowania, a nie zamierzone słownictwo. Proponuję zastąpić je polskimi odpowiednikami („każdorazowo”, „dla każdego wywołania” itp.), żeby opis był czytelny.

Suggested change
- [line 234-238] `WildcardSimple::matches` tworzy `Vec<char>` z name每次 wywołania.
- [line 234-238] `WildcardSimple::matches` tworzy `Vec<char>` z name przy każdym wywołaniu.

Comment thread scan-results.md Outdated
**Technical Debt**
- Brak log rotation — 10 MB danych znika.
- `Ordering::SeqCst` w teście vs `Relaxed` w produkcji — niespójność.
- `format!` na error path — `write!` bez allokuje mniej.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): Poprawka literówki „allokuje” na „alokuje”.

W innych miejscach używasz formy „alokuje”, więc dla spójności zastosuj ją także tutaj („write! bez alokuje mniej”).

Suggested change
- `format!` na error path — `write!` bez allokuje mniej.
- `format!` na error path — `write!` bez alokuje mniej.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a wide range of fixes and optimizations based on a repository scan, including bounds checking for hotlist removal, corrected reverse sorting logic, and improved date formatting for future-dated files. It also refactors path expansion, MIME detection, and environment variable parsing while addressing various technical debts. Review feedback identifies a potential logic error in file renaming that could bypass overwrite protections on certain filesystems and recommends explicit zero-checks in menu dispatching to prevent arithmetic errors.

Comment thread src/ops/chunk_copy.rs Outdated
Comment on lines 148 to 158
match fs::rename(temp_dest, dest) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
cleanup_file(temp_dest);
return Err(err);
}
Err(err) => {
cleanup_file(temp_dest);
return Err(err);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

On Unix-like systems, fs::rename silently overwrites the destination if it exists. Since the try_exists check was removed to avoid TOCTOU, and AlreadyExists is typically not returned by rename on Unix, the overwrite=false requirement will be violated if hard_link fails (e.g., on filesystems like FAT32). Consider performing a try_exists check before rename in this fallback path to respect the overwrite flag, even if it is not perfectly atomic.

References
  1. Verify code functionality, handle edge cases, and ensure alignment between function descriptions and implementations.

terminal_height: u16,
terminal: &mut ratatui::Terminal<B>,
) {
let total = menu_total_count();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While max_items is checked later, it is safer to explicitly check if total is zero here. This prevents potential underflow at line 243 (total - 1) or division by zero at line 250 (% total) if the menu state is inconsistent.

References
  1. Ensure that invalid inputs or states are safely handled in all cases.

@greptile-apps

greptile-apps Bot commented May 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR addresses 20 bugs and performance issues discovered during a repository-wide audit (scan-results.md), covering modules across ops/, fs/, input/, ui/, and app/. Each fix is targeted and self-contained; the majority of the changes are 5–15 lines.

  • Correctness fixes: sorting tiebreaker wrapping in Reverse(), future-timestamp panic prevention in format_date, bounds check before Vec::remove in hotlist_remove, path_cache self-mapping in unwatch, empty-file line_offsets consistency, menu_total_count % 0 guard, and duplicate set_file_times in chunk_copy.
  • Safety / clarity improvements: char-aware strip_prefix in user_menu, non-canonicalizable path skipping in watcher_sync, signed_duration_since for future timestamps, _viewer_state rename, command_draft clearing on exit, and status_message double-wrap removal.
  • Code quality: redundant hex pre-validation removed from theme parser, search_content_recursive visibility narrowed, dead #[allow] annotation removed, lock extension mapped to text/config instead of application/json.

Confidence Score: 4/5

Safe to merge; all 20 stated fixes are correct and no new breakage was introduced.

Every fix was verified against its context: the true replacement in search.rs is safe because memmem::find already skips non-matching lines, preserve_permissions is still called (moved into copy_to_temp), the sorting Reverse() wrapping is consistent with the two functions that were already correct, and the menu_total_count guard is sufficient because MENU_ACTIONS.len() is a compile-time non-zero constant. The only items worth a second look are the duplicate Err arms in publish_temp (style, no functional impact) and the explicit pattern in is_same_file that is equivalent to _ and could draw a clippy warning under -D warnings.

src/ops/chunk_copy.rs (duplicate Err arms after rename), src/input/dialogs.rs (explicit wildcard pattern in is_same_file)

Important Files Changed

Filename Overview
src/ops/search.rs Replaces line_text.contains(pattern) with true for case-sensitive search; valid because line 887 already continues when memmem::find finds no match, so reaching the check guarantees a hit. Also narrows search_content_recursive to private visibility and removes a stale #[allow].
src/ops/chunk_copy.rs Removes duplicate set_file_times call from publish_temp (kept in copy_with_progress), moves preserve_permissions to copy_to_temp, removes pointless flush on cancel, and replaces try_exists+rename TOCTOU with atomic hard_link+rename fallback. Minor: both Err arms in the final rename match perform identical cleanup and can be merged.
src/ops/sorting.rs Wraps the full (size/mtime/btime, nk0, nk1) tuple in Reverse() so the name tiebreaker sorts descending in reverse modes, matching the behaviour already present in reverse_ext_sort_key and reverse_natural_sort_key.
src/fs/reader.rs Replaces now - datetime (which could be negative for future timestamps) with signed_duration_since, and guards with (0..=365) so future-dated files correctly fall through to the year format.
src/ui/viewer.rs Returns vec![0] instead of Vec::new() from compute_line_offsets for empty files, fixing the inconsistency between line_count=1 and line_offsets=[]. Also removes the redundant raw_bytes.is_empty() branch in the line_offsets assignment.
src/input/dialogs.rs Moves the "'..' not allowed" message into ValidationResult::InvalidPath so the handler no longer double-wraps it. Also makes the is_same_file fallback arm exhaustive with explicit alternatives instead of _.
src/input/mode_dispatch.rs Computes menu_total_count() once before the max_items == 0 guard to prevent both % 0 division and usize underflow on Left arrow navigation.
src/fs/watcher.rs Fixes path_cache self-mapping in unwatch (was inserting canonical→canonical; now correctly inserts original→canonical). Also removes the dead Some(f) fallback branch after initialisation.
scan-results.md A 765-line Polish-language audit report added to the repository root. Tracks bugs, technical debt, and optimisations found during the scan that drove this PR. Not code, no correctness concerns.

Reviews (1): Last reviewed commit: "fix: resolve 20 bugs and performance iss..." | Re-trigger Greptile

Comment thread src/ops/chunk_copy.rs Outdated
Comment on lines 148 to 158
match fs::rename(temp_dest, dest) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
cleanup_file(temp_dest);
return Err(err);
}
Err(err) => {
cleanup_file(temp_dest);
return Err(err);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Both Err arms in this match perform identical cleanup (cleanup_file + return Err), so the AlreadyExists branch is redundant. On POSIX, rename(2) atomically replaces existing files and never returns EEXIST for a regular-file destination, making that arm unreachable on Linux/macOS. The two arms can be collapsed without changing behaviour on any platform where AlreadyExists is actually returned.

Suggested change
match fs::rename(temp_dest, dest) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
cleanup_file(temp_dest);
return Err(err);
}
Err(err) => {
cleanup_file(temp_dest);
return Err(err);
}
}
match fs::rename(temp_dest, dest) {
Ok(()) => {}
Err(err) => {
cleanup_file(temp_dest);
return Err(err);
}
}

Comment thread src/input/dialogs.rs Outdated
Comment on lines +118 to +120
(Err(_), Err(_)) | (Ok(_), _) | (_, Ok(_)) => {
crate::fs::path::clean_path(src) == crate::fs::path::clean_path(dest)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The fallback arm (Err(_), Err(_)) | (Ok(_), _) | (_, Ok(_)) is logically equivalent to _ — the sub-pattern (Ok(_), _) already covers (Ok, Ok) which is handled by the first arm, and (_, Ok(_)) overlaps it further. While Rust handles this safely by ordering, clippy's match_overlapping_arm lint (treated as an error via -D warnings per the project's CI setup) may flag the overlapping alternatives within the | chain. A plain _ is both correct and unambiguous.

Suggested change
(Err(_), Err(_)) | (Ok(_), _) | (_, Ok(_)) => {
crate::fs::path::clean_path(src) == crate::fs::path::clean_path(dest)
}
_ => crate::fs::path::clean_path(src) == crate::fs::path::clean_path(dest),

Comment thread scan-results.md Outdated
Comment on lines +1 to +10
# LibreCommander — Skan Repozytorium

Data: 2026-05-24
Cel: błędy, dług techniczny, optymalizacje

---

## Fala 1

### app/types.rs (2463 lines)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Audit artefact committed to repo root

This 765-line Polish-language scan report is now versioned in the repository. If the intent is to track outstanding work publicly, a GitHub Issue or a file under .claude/ / docs/ would keep the repo root clean and separate concerns from source code. As committed, cargo test and cargo clippy won't flag it, but it adds noise to git log and future contributors may be puzzled by it at the root.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

sorting: wrap full tuple in Reverse() for tiebreaker ordering
viewer: fix empty file line_offsets, remove RefCell render mutation
main: log leave error before resume, drop leave propagation
reader: signed_duration_since prevents panic on future timestamps
mime: eliminate double I/O in detect_mime, .lock->text/config
chunk_copy: remove duplicate set_file_times, cancel flush, TOCTOU
watcher: fix path_cache self-mapping, remove dead fallback branch
watcher_sync: skip non-canonicalizable paths in desired set
search: remove redundant contains, limit visibility, drop allow
path: add clean_path normalization, reject ${123} env var names
command_line: clear command_draft on execute/cancel/esc
menu_actions: join warning and local-menu messages
mouse: rename _viewer_state, replace unreachable!() with no-op
user_menu: strip_prefix char-aware, single-strip leading +
theme: remove redundant hex digit pre-validation
dialogs: saturate u16 scroll, fix status message double-wrap
types: bounds check in hotlist_remove before Vec::remove
pickers: clarify hotlist error message for non-directory case
input/dialogs: fix is_same_file fallback exhaustiveness
mode_dispatch: guard menu_total_count % 0 and underflow
chunk_copy: add try_exists before rename fallback to prevent silent
overwrite on POSIX when hard_link fails (e.g. FAT32); collapse duplicate
Err arms into inspect_err
dialogs: simplify is_same_file fallback pattern to wildcard _
mime: add path.is_dir() fallback when File::open fails for directories
@leszek3737
leszek3737 merged commit 6322862 into main May 24, 2026
5 checks passed
@leszek3737
leszek3737 deleted the fix52 branch May 24, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant