A fast, lightweight, and portable file renaming tool with a native GUI. Built with Rust.
This project is feature-frozen for long-term personal use. Needs are met; no new modes planned.
| Item | Policy |
|---|---|
| Primary platform | Windows 10+ (64-bit), verified |
| macOS / Linux | Code compiles; not QA-verified. One behavioural difference is documented under Rename safety |
| GUI toolkit | iced 0.14 — do not casual-upgrade majors |
| Toolchain | Pinned to 1.95.0 via rust-toolchain.toml; MSRV floor is 1.89 |
| Lockfile | Cargo.lock is committed; prefer cargo build --release over free dep thrash |
| Gate before touching code | cargo test && cargo clippy -- -D warnings && cargo build --release |
| Rebuilding years from now | See ARCHIVE.md |
Every number quoted in this file is asserted by a test. If a limit, version or MSRV
here disagrees with the code, cargo test fails.
- Drag and drop — drop folders (or individual files) straight onto the window
- Find & Replace Mode — plain text or regex; case-sensitive toggle
- Iteration Numbering Mode — template with
{n}, start number, padding (extension preserved) - Natural sort —
file2beforefile10 - Live preview — debounced (~300 ms)
- Conflict / invalid detection — duplicate targets, collisions with files that are staying put, and illegal basenames mark previews and block Execute
- Dark / Light theme — persisted; colours come from the active theme's palette
- Settings persistence — SQLite (see below; find/replace strings are not stored)
- Keyboard shortcuts
| Shortcut | Action |
|---|---|
Ctrl+O |
Open folder |
Ctrl+Enter |
Execute rename (only when previews are safe) |
Ctrl+↑ / Ctrl+↓ |
Move the selected file up / down |
Delete |
Remove selected file from the list |
Deleteonly acts on the list when no text field has focus, so editing a pattern never silently changes the batch. TheCtrlchords are unambiguous and work regardless of focus.
| Component | Minimum |
|---|---|
| OS | Windows 10+ (64-bit); macOS / Linux 64-bit (unverified) |
| RAM | ~64 MB |
| Disk | ~10 MB for the portable EXE |
| Display | Window opens at 900×650 and refuses to shrink below 700×500 |
| Input | Mouse and keyboard; drag and drop from the system file manager |
Platform support
- Windows: tested and distributed via releases. This is the verified platform.
- macOS / Linux: implemented and compiled, but not QA-verified — the project is developed on Windows. Build from source; see Build from source for the system packages each needs.
There is no platform-specific code beyond filename validation (below), and no platform-specific dependencies. Rendering is CPU-side via
tiny-skia, so no GPU driver or particular graphics API is required anywhere.
- Add files — drag a folder onto the window, or click Add Folder. Either way the listing is non-recursive and natural-sorted. Dropping several items at once works; so does dropping individual files rather than a whole folder.
- Configure — Find & Replace or Iteration
- Preview — red
[CONFLICT]/[INVALID]must be cleared before Execute enables - Execute — two-phase rename (see safety notes)
After a successful run the list is rewritten in place to show the new names, so the result is visible and a second pass needs no re-adding. Your ordering and any entries you removed by hand are preserved, and nothing is re-read from disk.
Renames use a two-phase scheme to avoid mid-batch target collisions:
- Each file is moved to
.rename_temp_<pid>_<newname>in the same directory. - Each temp is moved to the final name.
This is also what makes a swap work: renaming a→b and b→a in one batch is
resolved correctly rather than rejected.
On failure:
- Phase 1 error → already-moved temps are rolled back to originals (best effort).
- Phase 2 error → remaining temps are restored to originals; files already finalized stay final (no silent invent of backups).
Hard crash mid-run may leave .rename_temp_<pid>_* files. Recover by renaming: strip the .rename_temp_<pid>_ prefix to obtain the intended final basename, or restore a known original name. This is best-effort, not a full transactional filesystem journal.
Platform difference. std::fs::rename silently replaces an existing
destination on Unix, but returns an error on Windows. The batch is checked for
existing targets before it starts, and each destination is checked again
immediately before it is written, so the window in which another process could
slip a file into place is small — but on macOS and Linux it is not zero. Do not
run a batch against a directory another program is actively writing to.
Names are validated against the rules of the system you are running on, checked in the preview and again before any filesystem operation.
| Rejected | Windows | macOS / Linux |
|---|---|---|
Empty, ., .. |
yes | yes |
| NUL and control characters | yes | yes |
/ |
yes | yes |
\ < > : " | ? * |
yes | allowed |
Trailing . or space |
yes | allowed |
Reserved device names (CON, NUL, COM1…) |
yes | allowed |
Applying the Windows set everywhere would reject names that are perfectly legal on
Unix — Artist: Title.mp3 being the usual casualty. The trade-off is that a name
created on macOS or Linux is not guaranteed to be copyable to Windows later.
No separate runtime install — single portable binary (SQLite is bundled via rusqlite).
| Crate | Role |
|---|---|
iced 0.14 |
GUI (tiny-skia + tokio) |
rfd 0.17 |
Native folder dialog |
regex 1.13 |
Find/replace patterns |
rusqlite 0.40 |
Settings DB (bundled SQLite) |
dirs 6 |
Settings path |
anyhow 1 |
Error context in domain ops |
tokio 1 |
Async runtime, and the blocking pool that keeps FS work off the UI thread |
There are no platform-specific dependencies. The windows and libc crates were
removed once the administrator/root check was found to be answering a question the
application never had to ask — see Security & limits.
- Download the latest release from Releases
- Extract and run
file-rename-plus.exe
Prerequisites: Rust 1.89+ (the floor is set by font-types, pulled in via iced; verified on 1.95.0, which rust-toolchain.toml pins).
rusqlite builds SQLite from source, so every platform also needs a C compiler.
Beyond that the GUI stack needs the host windowing libraries:
| Platform | Also needed |
|---|---|
| Windows | MSVC build tools (the C compiler); nothing else |
| macOS | Xcode Command Line Tools (xcode-select --install) |
| Debian / Ubuntu | build-essential pkg-config libwayland-dev libxkbcommon-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev |
| Fedora | gcc pkgconf-pkg-config wayland-devel libxkbcommon-devel libX11-devel libXcursor-devel libXrandr-devel libXi-devel |
These are the target system's own libraries, which is also why cross-compiling from Windows to Linux does not work without a full sysroot — build natively on each platform.
Windows:
build_release_windows.batOutput: build_release_windows\file-rename-plus.exe (plus copied README/LICENSE).
macOS / Linux:
./build_release_unix.shOutput: build_release_unix/file-rename-plus (plus copied docs). Like the Windows
script, it runs the gate first and refuses to package a failing build. Or by hand:
cargo test
cargo clippy --all-targets -- -D warnings
cargo build --release
# binary: target/release/file-rename-plusThe test suite is platform-aware: the Windows-only filename assertions are replaced on Unix by ones asserting those same names are accepted. Both branches are compiled and run before release.
- Add a folder (drag it in, or Add Folder)
- Find pattern / replacement
- Optional: Regex, Case Sensitive
- Preview → Execute
Example: IMG_ → Photo_ turns IMG_001.jpg into Photo_001.jpg.
Template must contain {n}. Original extension is appended.
Example: template vacation_{n}, start 1, padding 3 → vacation_001.jpg.
| Platform | Path |
|---|---|
| Windows | %LOCALAPPDATA%\file-rename-plus\settings.db |
| macOS | ~/Library/Application Support/file-rename-plus/settings.db |
| Linux | ~/.local/share/file-rename-plus/settings.db |
Persisted keys: dark_mode, regex_mode, case_sensitive, template, start_number, padding.
Not persisted: find pattern, replace string, file list, mode (always starts in Find & Replace).
Writes are debounced along with the preview, and a failure to save is reported in the status bar rather than discarded.
file-rename-plus/
├── src/
│ ├── main.rs # iced 0.14 entry + metadata consistency tests
│ ├── app/
│ │ ├── mod.rs # state, messages, subscriptions
│ │ ├── update.rs # every state transition
│ │ └── view.rs # widget tree
│ ├── file_ops.rs # scan, natural sort, two-phase rename
│ ├── rename.rs # find/replace, iteration, filename rules, conflicts
│ ├── security.rs # directory write access
│ ├── settings.rs # SQLite load/save
│ ├── limits.rs # domain limits (asserted against this README)
│ ├── theme.rs # sizing and spacing tokens
│ └── types.rs # FileEntry, RenamePreview, AppMode, Status
├── Cargo.toml
├── Cargo.lock
├── rust-toolchain.toml # pinned compiler
├── ARCHIVE.md # how to rebuild this years from now
├── CHANGELOG.md
├── LICENSE # MIT
├── README.md
├── PROJECT_VERSION
├── build_release_windows.bat
└── build_release_unix.sh
- Access checks test the directory, not the file. Renaming rewrites a directory entry, so directory write access is what matters. The previous check opened each file for writing, which wrongly reported "Access denied" for read-only files and for files merely open in another application — both of which rename fine. Each directory is probed once per batch, not once per file.
- No UAC elevation is ever requested.
- Pattern and replacement length cap 1024 characters; template 256; padding 10 digits; max files 2000.
- The file cap is set by what the list renderer sustains, not by any filesystem limit. The list is not virtualised; raising the cap would require that first.
- Regex patterns are compiled with a 1 MiB compiled-program size limit, which
bounds how large a pattern may expand to. Rust's
regexcrate matches in linear time and has no catastrophic backtracking, so there is no ReDoS exposure to mitigate beyond this. - Illegal filenames blocked in preview and at execute.
- Conflicts / invalid names disable Execute (and Ctrl+Enter is a no-op with status error).
- Settings values are written with parameterised SQL and truncated on both read and write.
MIT — see LICENSE.
Leonard Walujan's Public Projects