feat: experimental history browser for interactive history management#38
Merged
feat: experimental history browser for interactive history management#38
Conversation
Add a terminal-based history browser UI similar to the help browser: - Browse R or shell history with `:history browse [r|shell]` - Filter by text (fuzzy), hostname, cwd prefix, or exit status - Select items with Space, batch delete with d - Copy commands with y (stay) or Enter (copy and exit) - Text scroll animation for long commands Technical changes: - Use rusqlite directly with SQLITE_OPEN_READ_ONLY to avoid WAL conflicts - Delete operations still use SqliteBackedHistory for write access - Add tab completion for `:history browse` subcommand Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR adds an interactive terminal-based history browser UI for viewing and managing R and shell command history, following a similar pattern to the existing help browser.
Changes:
- Added a new history browser module with fuzzy filtering, text scrolling, and batch deletion capabilities
- Integrated
:history browse [r|shell]meta-command with tab completion - Used rusqlite directly with read-only connections to prevent WAL database conflicts
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| crates/arf-console/src/pager/history_browser.rs | New 1068-line module implementing the interactive history browser with filtering, navigation, deletion, and clipboard operations |
| crates/arf-console/src/repl/meta_command.rs | Added process_history_browse function and integrated :history browse subcommand with help text |
| crates/arf-console/src/pager/mod.rs | Exported new run_history_browser and HistoryDbMode types, updated module documentation |
| crates/arf-console/src/completion/completer.rs | Added tab completion for :history browse subcommand with r/shell target suggestions |
| crates/arf-console/tests/integration_tests.rs | Added two integration tests verifying cross-session persistence and WAL corruption fix |
| crates/arf-console/Cargo.toml | Added rusqlite workspace dependency |
| Cargo.toml | Added rusqlite 0.37.0 with bundled feature to workspace dependencies |
| Cargo.lock | Updated with rusqlite dependency entry |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add a second footer line showing supported filter syntax: host:<name>, cwd:<path>, exit:<N>, and fuzzy text search - Adjust visible rows calculation to account for the new line Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Change selection toggle from Space to Tab for consistency with help browser and to allow spaces in filter input - Update filter syntax help to clarify "space = AND" for combining multiple filter conditions (e.g., "host:server git push") - Unify terminology: use "exit" instead of "quit" to match other pagers Co-Authored-By: Claude Opus 4.5 <[email protected]>
…e for deletion Use Connection::open() directly instead of SqliteBackedHistory::with_file() for the delete operation. The previous approach created a competing WAL connection alongside the main REPL's active history connection, risking cache inconsistency and database corruption. Also changes one-by-one DELETE to a single batch DELETE with params_from_iter for atomicity and performance. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Address roborev findings for commit 432ebf9: - Add busy_timeout(5s) to prevent SQLITE_BUSY on concurrent access - Fix misleading "transaction" comment (single statement uses autocommit) - Add schema drift warning to test helper - Move deleted_count closer to its usage Co-Authored-By: Claude Opus 4.5 <[email protected]>
Compute the feedback message directly from ids_to_delete.len() before the vector goes out of scope, eliminating the redundant intermediate binding. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Remove redundant thread::sleep calls before expect() (which already polls), and replace post-browser-exit sleeps with clear_and_expect for prompt detection. This eliminates flaky test risk under CI load. Co-Authored-By: Claude Opus 4.5 <[email protected]>
The load_history() function runs in a single-threaded UI context with no concurrent access to the connection. NO_MUTEX disables SQLite's internal mutex, which is unnecessary here and could mask threading bugs if the code were ever called from multiple threads. Co-Authored-By: Claude Opus 4.5 <[email protected]>
session_id was selected at index 3 but discarded (mapped to None), making subsequent column indices fragile. Remove it from the SELECT and shift indices 4-7 down to 3-6. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ersion Replace io::Error::other(e.to_string()) with io::Error::other(e) to retain the original rusqlite::Error as the error source chain. This preserves type information for debugging and error inspection. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add vim-style `/` key to enter filter mode, where all character input goes to the filter text. In normal mode, single-char keybindings (q, d, y, j, k, g, etc.) work as navigation/commands without conflicting with filter input. Esc clears filter and returns to normal mode, Enter confirms filter and returns to normal mode. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…o filter mode Extract move_cursor_up/down/page_up/page_down/to_top/to_bottom helpers to eliminate duplicated navigation logic between filter mode, normal mode, and mouse scroll handlers. Also add PageUp/Down, Home/End, Tab, Ctrl+A, Ctrl+U support to filter mode so users can navigate and select without leaving filter input. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…+B/F conflict in filter mode Add early return in move_page_up/move_page_down when filtered list is empty to prevent cursor from pointing to a non-existent index. Also remove Ctrl+B/Ctrl+F bindings from filter mode's PageUp/PageDown since they conflict with Emacs-style cursor movement (back/forward) which users expect in a text input context. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Replace `|e| io::Error::other(e)` closures with `io::Error::other` function references, and apply rustfmt formatting fixes. Co-Authored-By: Claude Opus 4.5 <[email protected]>
1. Replace manual selected_count field with derived selected_count() method to eliminate desync risk 2. Use safe i64→u64 conversion for duration_ms (try_from instead of as) 3. Show "Copied: ..." feedback after Enter-copy in :history browse 4. Change mouse scroll to move viewport only (not cursor), matching standard pager behavior; cursor stays visible via clamping 5. Extract common complete_targets() helper in completer to deduplicate browse/clear target completion logic Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ard scroll underflow 1. Use chars().take() instead of byte slice for command display truncation to prevent panic on multi-byte UTF-8 characters 2. Cache selected_count() result in local variable during render to avoid redundant O(n) iterations 3. Guard mouse scroll cursor clamping against visible_rows == 0 to prevent usize underflow on extremely small terminals Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Space toggles selection and moves cursor down for convenient multi-select. Tab remains as toggle-only (no cursor movement). Filter mode is unaffected. Co-Authored-By: Claude Opus 4.5 <[email protected]>
In filter mode, plain Home/End now moves the cursor within the filter input field. Alt+Home/End retains the list top/bottom navigation. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…ter mode KeyModifiers is a bitflag type, so exact equality (KeyModifiers::ALT) would miss cases like Alt+Shift. Use contains() guard to match any modifier combination that includes Alt. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…der frame Introduce cached_selected_count field maintained by toggle_selection, select_all_visible, unselect_all, and delete_selected instead of scanning all entries on every render call. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…rse() Remove the redundant clone+reassign pattern where callers modified raw_query in-place then passed a clone to update(). The new reparse() method reads raw_query directly, eliminating the unnecessary allocation. Co-Authored-By: Claude Opus 4.5 <[email protected]>
… Backspace/Delete Backspace and Delete handlers used unwrap_or(0) / unwrap_or(len) when resolving char_indices, which could panic on a multi-byte boundary if cursor_pos were ever inconsistent. Use if-let to skip the operation when the char index is not found. Co-Authored-By: Claude Opus 4.5 <[email protected]>
…crement Prevent potential usize underflow panic if the cache were ever desynchronized, using saturating_sub(1) instead of plain subtraction. Co-Authored-By: Claude Opus 4.5 <[email protected]>
Co-Authored-By: Claude Opus 4.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a terminal-based history browser UI similar to the help browser with
:history browse [r|shell].Features
Browse mode
↑/k/Ctrl+Pup,↓/j/Ctrl+Ndown,PageUp/Ctrl+B,PageDown/Ctrl+F,Home/gtop,End/GbottomSpacetoggle + move down,Tabtoggle,Ctrl+Aselect all visible,Ctrl+Uunselect allEntercopy to clipboard and exit,ycopy to clipboard and stay,dbatch delete selected (with confirmation)/to enter filter modeq,Esc,Ctrl+C,Ctrl+DFilter mode
host:<name>hostname,cwd:<path>working directory prefix,exit:<N>exit status, free text for fuzzy command match (space-separated = AND)Backspace,Delete,Left/Rightcursor movement,Home/Endmove to start/end of input↑/Ctrl+P,↓/Ctrl+N,PageUp,PageDown,Alt+Hometop,Alt+EndbottomTabtoggle,Ctrl+Aselect all visible,Ctrl+Uunselect allEnterconfirm filter and return to browse mode,Escclear filter and return to browse modeCtrl+C,Ctrl+DDisplay
↵markersTechnical details
SQLITE_OPEN_READ_ONLYandbusy_timeoutto avoid WAL conflicts during browsingselected_countto avoid O(n) scan per render framechar_indices()reparse()to avoid redundant cloningcontains()for bitflag modifier matching (Alt, Ctrl):history browsesubcommandFiles changed
history_browser.rs— Main browser implementation (~1340 lines)meta_command.rs—:history browsecommand registration and dispatchcompleter.rs— Tab completion for the subcommandintegration_tests.rs— PTY-based integration tests