feat: implement TTY mode, cclip deletion, and modernize dependencies#23
feat: implement TTY mode, cclip deletion, and modernize dependencies#23
Conversation
This commit introduces significant features and optimizations across fsel: App Launcher & TTY Mode: - Implement `-t, --tty` flag and `terminal_launcher = "tty"` config option. - Use `CommandExt::exec` to replace the fsel process with the target app when in TTY mode, ensuring seamless terminal transitions. - Refine launch logic to record history and frecency BEFORE process replacement and bypass environment-specific prefixes (uwsm, systemd) in TTY mode. - Fix -t shorthand parsing in CLI options. Clipboard Management (cclip): - Add entry deletion support via new [cclip_delete](cci:1://file:///home/chris/projects/code/fsel/src/ui/keybinds.rs:228:4-231:5) keybind (Alt+Delete). - Implement [delete_item](cci:1://file:///home/chris/projects/code/fsel/src/modes/cclip/select.rs:155:0-167:1) helper calling `cclip delete <ID>`. - Fix selection jump bug: preserve selection index and scroll offset after deletion, ensuring the selection stays at the same physical position. Performance & Dependency Updates: - Update Ratatui to v0.30 and enable the `layout-cache` feature for faster layout calculations and UI rendering. - Bump core dependencies: tokio (1.49), directories (6.0), dirs (6.0), nucleo-matcher (0.3.1), and unicode-width (0.2.2). - Clean up duplicate `prefix_depth` field in CLI options.
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds TTY launch mode and clipboard-entry deletion; migrates serialization from bincode→postcard, time handling from chrono→time, and directory lookup from dirs→directories; updates dependencies and documentation; replaces config-rs flow with manual TOML loading and introduces a ConfigError type. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI
participant AppLauncher
participant DB
participant OS
User->>CLI: invoke fsel (args, maybe -t)
CLI->>AppLauncher: launch(command, opts)
alt tty mode (opts.tty && app.is_terminal)
AppLauncher->>DB: record history & frecency
DB-->>AppLauncher: ok
AppLauncher->>AppLauncher: validate executable
AppLauncher->>OS: exec(target) -- replaces current process
OS-->>AppLauncher: error (if exec fails)
else normal mode
AppLauncher->>OS: spawn(target) -- child process created
OS-->>AppLauncher: child pid
AppLauncher->>DB: update history/frecency
DB-->>AppLauncher: ok
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/cli.rs`:
- Around line 114-115: The detailed help output has two lines using leading
spaces instead of the tree-drawing characters, causing inconsistent formatting;
update the help text where the entries for "-V, --version" and "-t, --tty" are
defined (look for the help string or function that constructs those lines, e.g.,
the constant or function that includes the literal " -V, --version
Show version info" and " -t, --tty Launch in current terminal
(TTY mode)") and replace the leading spaces with the appropriate tree characters
(use "├─" for the middle item and "└─" for the last item, and ensure vertical
connector "│" alignment matches surrounding lines) so the ASCII tree matches the
rest of the help output.
In `@src/modes/cclip/run.rs`:
- Around line 1062-1063: The delete_item call currently ignores failures; update
the super::select::delete_item(&rowid) handling to match the success branch by
reporting errors to the user: on Ok(()) keep
ui.set_temp_message(format!("Deleted entry {}", rowid)); and on Err(e) call
ui.set_temp_message(...) with a clear error message (e.g. "Failed to delete
entry {rowid}: {e}") so failures (command not found, permission denied, etc.)
surface to the user.
- Around line 1065-1097: When reloading clipboard history after a deletion, keep
the active tag filter (cli.cclip_tag) instead of using
super::scan::get_clipboard_history() unfiltered: call the same filtering logic
used on initial load (the branch that checks cli.cclip_tag) — either call the
filtered loader if one exists or filter updated_items in place by retaining only
items whose tags contain the selected tag (check cclip_item.tags) before mapping
into Item; ensure you reference cli.cclip_tag,
super::scan::get_clipboard_history(), and the cclip_item.tags field so the
reload presents the same filtered view the user was previously seeing.
🧹 Nitpick comments (1)
src/modes/cclip/run.rs (1)
1070-1088: Consider extracting duplicate item reconstruction logic.This item reconstruction block (converting
CclipItemtoItemwith formatting) is repeated in at least three places: here (deletion), tag application (lines 1301-1319), and tag removal (lines 1390-1408). Extracting this to a helper function would improve maintainability.
|
you always have an issue with everything i do bro damn, how about a "good work" every once in a while damn |
fixing help text styling Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Report failures from cclip delete command to the user UI - Maintain the active tag filter when reloading history after deletion - Ensure UI selection and scroll preservation logic remains robust
Major overhaul to significantly reduce binary size, dependency count, and resolve security audits. Key Changes: - Replaced `bincode` (unmaintained) with `postcard` 1.1 for all database/cache serialization. - Replaced `chrono` with `time` 0.3 for lighter debug logging. - Replaced `config` crate with lightweight direct TOML parsing + env handling. - Replaced `regex` with `strip-ansi-escapes` for zero-overhead ANSI stripping. - Replaced `dirs` with `directories` crate equivalents. - optimization: Slimmed `tokio` features to only used components. - security: Resolved `atomic-polyfill` audit warning by disabling unused `heapless` features in postcard. - fix(cli): Normalize [terminal_launcher](cci:1://file:///home/chris/projects/code/fsel/src/config.rs:189:0-192:1) config to empty string when "tty" mode is detected to prevent misuse. Stats: - Dependencies: Reduced from ~317 to 229 (-88 deps) - Binary Size: Reduced from ~4.5MB to 2.7MB can you belieave that BREAKING CHANGE: - Database format changed from bincode to postcard. Existing history and cache will be reset/rebuilt on next launch. i think
|
Aight so check it, i went thru and purged hella bloat cause this shi was gettin rediculous. We droppd from like 317 dependecies down to 229 and the binary size sittin at 2.7MB now (was pushin 5MB before). Deep dive on what i changed:
Codebase way cleaner now ts |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/cli.rs`:
- Around line 262-263: The CLI path should mirror the config handling by
clearing terminal_launcher instead of setting the "fsel-tty-placeholder"; when
detecting tty mode (check cli.tty and app.is_terminal before the early exec()),
set terminal_launcher to an empty/None value (same semantics used in the config
branch) rather than assigning the placeholder string so both code paths are
consistent and the intent is explicit (reference symbols: cli.tty,
app.is_terminal, terminal_launcher, exec()).
🧹 Nitpick comments (2)
src/core/debug_logger.rs (1)
51-52: Consider using compile-time format descriptions for slight efficiency gain.The current approach parses format descriptions at runtime. While the
.unwrap()is safe here since the format strings are known-valid, usingtime::macros::format_description!would be slightly more efficient by parsing at compile time.♻️ Optional: Use compile-time format descriptions
+use time::macros::format_description; + +// At module level or as constants: +const DATETIME_FORMAT: &[time::format_description::FormatItem<'static>] = + format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"); +const FILENAME_FORMAT: &[time::format_description::FormatItem<'static>] = + format_description!("[year][month][day]-[hour][minute][second]"); // Then use: - time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc()) - .format(&time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]").unwrap()).unwrap() + time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc()) + .format(&DATETIME_FORMAT).unwrap()src/modes/app_launcher/launch.rs (1)
37-48: Consider extracting history/frecency recording to a helper function.The history and frecency recording logic is duplicated between the TTY path (lines 37-48) and the non-TTY path (lines 141-152). While the placement differs by necessity (before exec vs. after spawn), the logic is identical.
♻️ Suggested helper function
fn record_launch(db: &std::sync::Arc<redb::Database>, app_name: &str, history: i64) -> Result<()> { let value = history + 1; let write_txn = db.begin_write()?; { let mut table = write_txn.open_table(crate::core::cache::HISTORY_TABLE)?; table.insert(app_name, value)?; } write_txn.commit()?; if let Err(e) = crate::core::database::record_access(db, app_name) { eprintln!("Warning: Failed to update frecency: {}", e); } Ok(()) }Then use it in both paths:
- // Record history and frecency BEFORE exec since we disappear after - let value = app.history + 1; - let write_txn = db.begin_write()?; - { - let mut table = write_txn.open_table(crate::core::cache::HISTORY_TABLE)?; - table.insert(app.name.as_str(), value)?; - } - write_txn.commit()?; - - if let Err(e) = crate::core::database::record_access(db, &app.name) { - eprintln!("Warning: Failed to update frecency: {}", e); - } + // Record history and frecency BEFORE exec since we disappear after + record_launch(db, &app.name, app.history)?;
…er for tty mode as coderabbit said fr]
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@Cargo.toml`:
- Line 38: Update the ratatui dependency features in Cargo.toml so the crossterm
variant is explicit for version 0.30: replace the current features entry for the
ratatui dependency (ratatui = { version = "0.30", default-features = false,
features = ["crossterm", "layout-cache"] }) to use the version-specific feature
name "crossterm_0_29" instead of "crossterm", keeping "layout-cache" and
default-features = false unchanged.
In `@README.md`:
- Around line 207-213: The README's "TTY mode" paragraph uses "etc" without the
required period; update the text in the TTY mode section (the paragraph
mentioning "TTY mode" and examples like `fsel -t`, `fsel --tty`, and
`terminal_launcher = "tty"`) to change "etc" to "etc." so the abbreviation
follows American English punctuation conventions.
🧹 Nitpick comments (3)
src/core/debug_logger.rs (2)
25-30: Consider using compile-time format description macro.The
timecrate providestime::macros::format_description!for compile-time validation, which eliminates runtime parsing and the associated.unwrap()calls.♻️ Suggested refactor using compile-time macro
+use time::macros::format_description; + +// At module level or as consts: +const FILENAME_FMT: &[time::format_description::FormatItem<'static>] = + format_description!("[year][month][day]-[hour][minute][second]");Then at the call site:
let now = time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc()); -let timestamp = now - .format( - &time::format_description::parse("[year][month][day]-[hour][minute][second]").unwrap(), - ) - .unwrap(); +let timestamp = now.format(&FILENAME_FMT).unwrap();
55-63: Duplicated format string; consider extracting to a constant.This exact format string is repeated at line 253 for
log_session_end(). Extracting it to a shared constant (ideally using the compile-time macro) would improve maintainability.♻️ Suggested extraction
use time::macros::format_description; const TIMESTAMP_FMT: &[time::format_description::FormatItem<'static>] = format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]");Then use
&TIMESTAMP_FMTin both locations.Cargo.toml (1)
28-29: Documenttime0.3local-offsetplatform constraints before deployment.
timecrate'slocal-offsetfeature has well-documented platform-specific behavior on supported targets:
- Linux: requires process to be single-threaded (checked via
/proc/self/stat); returnsIndeterminateOffseterror in multi-threaded contexts- BSD & other Unix: safe implementation gracefully returns error; local offset detection only succeeds if timezone is configured via
/etc/localtimebacked by/usr/share/zoneinfoEnsure error handling is in place for calls to
OffsetDateTime::now_local()and similar APIs, especially if multi-threading is enabled on Linux or custom timezone setups are used on BSD systems. The unsound escape hatch--cfg unsound_local_offsetshould not be used (explicitly unsound, potential segfaults).
directories6.0 is compatible; its changes are limited to dependency updates.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/config.rs (1)
275-357:⚠️ Potential issue | 🟡 MinorDon’t silently ignore a missing explicit config path.
If the user passes--config, falling back to defaults hides mistakes; returning a NotFound error is safer.🔧 Suggested fix
- let config_path = if let Some(path) = cli_config_path { + let cli_provided = cli_config_path.is_some(); + let config_path = if let Some(path) = cli_config_path { Some(path) } else if let Some(proj_dirs) = ProjectDirs::from("", "", "fsel") { let mut p = proj_dirs.config_dir().to_path_buf(); p.push("config.toml"); Some(p) } else { None }; // Load config from file or use defaults let mut cfg: FselConfig = if let Some(ref path) = config_path { if path.exists() { let contents = fs::read_to_string(path)?; toml::from_str(&contents)? + } else if cli_provided { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Config file not found: {}", path.display()), + ) + .into()); } else { FselConfig::default() } } else { FselConfig::default() };src/modes/app_launcher/launch.rs (1)
15-55:⚠️ Potential issue | 🟡 MinorGuard against empty launch commands before TTY exec.
commands[0]will panic if the command string is empty/whitespace; return a clear error instead.🔧 Suggested fix
- let commands = shell_words::split(&app.command)?; + let commands = shell_words::split(&app.command)?; + if commands.is_empty() { + return Err(eyre::eyre!("Empty command for app '{}'", app.name)); + }
added a error for empty/whitepasce
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/config.rs`:
- Around line 308-363: The comment claims full parity with the config-rs
Environment source but the code only overrides fields under cfg.general and
cfg.ui (e.g., terminal_launcher, match_mode, highlight_color); add equivalent
env var overrides for the other config sections (cfg.layout, cfg.dmenu,
cfg.cclip, cfg.app_launcher) by reading their respective FSEL_* vars and
parsing/assigning them using the same pattern
(env::var(...).parse().unwrap_or(...)/direct assignment) so environment-driven
settings work for layout/dmenu/cclip/app_launcher, or if this omission is
intentional, update the comment at the top of the block to clearly state only
general and ui are overridden to avoid misleading users.
In `@src/modes/app_launcher/launch.rs`:
- Around line 30-57: The code currently commits history/frecency before calling
process::Command.exec(), so failed execs still count; fix by pre-validating the
target executable (resolve commands[0] on PATH and check it is
accessible/executable) before opening write_txn and inserting into
HISTORY_TABLE, and only perform write_txn.commit() and call
crate::core::database::record_access(db, &app.name) after validation succeeds;
alternatively implement a best‑effort rollback by catching the exec error (the
Err from exec.exec()) and removing the inserted history entry (using write_txn
or a new write transaction to delete app.name from HISTORY_TABLE and adjust
app.history) and attempting to revert frecency via crate::core::database APIs so
failed execs do not persist as launches.
…recording failed execs - Add FSEL_* environment variable overrides for layout, dmenu, cclip and the legacy app_launcher section. Environment values now parse/assign into the corresponding config fields (including Option<T> fields for mode-specific settings), matching the pattern used for general/ui. - In app_launcher launch flow, validate/resolve the target executable (PATH search and executable-bit check) before opening the write transaction and updating HISTORY_TABLE/frecency. Use the resolved path for exec so failed execs are not recorded as successful launches. - Clarify comments; preserve prior behavior and fallbacks for panel sizing/position. i think i made the right choice here but i could be wrong
Summary
This PR implements several significant improvements to
fsel, including a high-performance TTY mode for in-terminal app launching, clipboard entry deletion in cclip mode, and a major dependency modernization to Ratatui 0.30.Changes
App Launcher & TTY Mode
-t, --ttyflag and support forterminal_launcher = "tty"in config.fselnow replaces itself with the target application usingexec, providing a seamless terminal experience.Clipboard Management (cclip)
Alt+Deletekeybind.Modernization & Performance
layout-cachefeature in Cargo.toml to improve UI responsiveness by caching repeated layout calculations.tokio(1.49),directories(6.0),dirs(6.0),nucleo-matcher(0.3.1), andunicode-width(0.2.2).-tshorthand parsing.Testing
fsel -tty, select a terminal app (e.g.,htop), and verify it takes over the current terminal session.fsel --cclip, select an entry, and pressAlt+Delete. Verify the entry is removed and the selection remains at the current index.cargo checkpasses withlayout-cacheenabled.Breaking Changes
fselprocess is replaced. This is intended behavior for terminal launchers but differs from the detached GUI launch model.Related Issues
N/A (Feature implementations and refinements)