-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add global goto key (g) with hint popup #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Warning Rate limit exceeded@fcoury has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 25 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (9)
WalkthroughAdds timed multi-key command handling for Normal mode with a key-sequence handler, UI hint popup, new goto actions, config timeout, and App-level integration to start/complete sequences and render hints. Public APIs exposed for sequence types and popup. Changes
Sequence DiagramsequenceDiagram
participant User
participant App
participant KeySequenceHandler
participant KeyHintPopup
participant UI
User->>App: press 'g' (first key)
App->>KeySequenceHandler: process_first_key('g')
KeySequenceHandler-->>App: Started(PendingKey::G)
App->>App: record pending, start timeout
alt timeout reached
App->>KeySequenceHandler: should_show_hint?
KeySequenceHandler-->>App: true
App->>KeyHintPopup: new(PendingKey::G)
App->>UI: render KeyHintPopup
UI-->>User: show hints (gg, ge, gc, gt, gr)
end
User->>App: press second key (e.g., 'g' or 'e')
App->>KeySequenceHandler: process_second_key('g'/'e'/...)
KeySequenceHandler-->>App: Completed(KeySequenceAction::Goto*)
App->>App: execute_key_sequence_action(...)
App->>UI: update focus/navigation
KeySequenceHandler-->>App: cleared pending state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/tsql/src/app/app.rs (1)
1449-1467: Esc must cancelself.key_sequencetoo (otherwise sequences can get “stuck”).
CurrentlyEscclearsself.pending_keybut notself.key_sequence, so “g then Esc” can leave a pending sequence behind.@@ if key.code == KeyCode::Esc && key.modifiers == KeyModifiers::NONE { if self.db.running { self.cancel_query(); return false; } + // Cancel any pending multi-key sequence (e.g., started with 'g') + self.key_sequence.cancel(); + self.help_popup = None; self.search.close(); self.command.close(); self.completion.close(); self.cell_editor.close(); self.history_picker = None; self.connection_picker = None; self.pending_key = None; self.last_error = None; self.mode = Mode::Normal; return false; }
🧹 Nitpick comments (7)
crates/tsql/src/config/schema.rs (1)
131-132: LGTM!The configurable timeout with a sensible 500ms default is well-designed. The
u64type is appropriate for millisecond durations.Consider adding bounds validation in the future if users report issues with extreme timeout values (e.g., <50ms causing hint flicker or >5000ms feeling unresponsive).
Also applies to: 154-154
crates/tsql/src/config/keymap.rs (2)
101-107: Avoid long-term drift betweenconfig::Action::Goto*andui::KeySequenceAction::*(same conceptual actions).
Right now the PR introduces two parallel “goto action” enums. Consider converging on a single source of truth (e.g.,KeySequenceAction->Actionconversion, or reuseActiondirectly) to prevent future mismatches in behavior/descriptions.Also applies to: 178-182, 285-290
934-956: Add coverage for the newgoto_*FromStrbranches.
Thetest_action_from_strtest doesn’t exercise the new variants, so regressions in config parsing could slip in.@@ fn test_action_from_str() { @@ // Test case insensitivity assert_eq!("MOVE_UP".parse::<Action>().unwrap(), Action::MoveUp); assert_eq!("Move_Up".parse::<Action>().unwrap(), Action::MoveUp); + + // Goto sequences + assert_eq!("goto_first".parse::<Action>().unwrap(), Action::GotoFirst); + assert_eq!("goto_editor".parse::<Action>().unwrap(), Action::GotoEditor); + assert_eq!( + "goto_connections".parse::<Action>().unwrap(), + Action::GotoConnections + ); + assert_eq!("goto_tables".parse::<Action>().unwrap(), Action::GotoTables); + assert_eq!("goto_results".parse::<Action>().unwrap(), Action::GotoResults); }crates/tsql/src/ui/key_hint_popup.rs (1)
31-38: Keep hint copy aligned with actual actions (and consider reusingAction::description()).
Right now the popup shows abbreviated strings (“tables”, “first row”) while config descriptions are richer (“tables/schema sidebar”, “first row/document start”). Reusing the existing description source (or at least aligning wording) will reduce UI drift.Also applies to: 114-127
crates/tsql/src/app/app.rs (1)
1361-1368: Either markhint_shown(or remove it) to keepKeySequenceHandlerstate coherent.
KeySequenceHandlerhashint_shown, but the app never callsmark_hint_shown(), andshould_show_hint()doesn’t consult it—so the flag is currently dead state.@@ if self.key_sequence.should_show_hint() { if let Some(pending_key) = self.key_sequence.pending() { let hint_popup = KeyHintPopup::new(pending_key); hint_popup.render(frame, size); + self.key_sequence.mark_hint_shown(); } }crates/tsql/src/ui/key_sequence.rs (2)
88-109: Makehint_shownmeaningful (or delete it) by gatingshould_show_hint().
Right nowhint_showndoesn’t affect anything, so it’s confusing state. If you keep it,should_show_hint()should likely return false oncehint_shownis true.@@ pub fn should_show_hint(&self) -> bool { - if let (Some(_), Some(since)) = (self.pending, self.pending_since) { - since.elapsed() >= Duration::from_millis(self.timeout_ms) + if self.hint_shown { + return false; + } + if let (Some(_), Some(since)) = (self.pending, self.pending_since) { + since.elapsed() >= Duration::from_millis(self.timeout_ms) } else { false } }Also applies to: 101-109
173-178: Fixis_waiting()doc (or implement timeout-based auto-cancel) + reduce sleep-test flakiness.
- The
is_waiting()comment says it considers timeout, but it doesn’t.test_should_show_hint_after_timeoutusingsleep(20ms)can be flaky; bump the margin.@@ - /// Check if there's a pending key and timeout hasn't been reached. - /// Useful for deciding whether to wait for more input. + /// Returns true if there is a pending key sequence. pub fn is_waiting(&self) -> bool { self.pending.is_some() } @@ fn test_should_show_hint_after_timeout() { let mut handler = KeySequenceHandler::new(10); // 10ms timeout for quick test @@ - sleep(Duration::from_millis(20)); + sleep(Duration::from_millis(50)); assert!(handler.should_show_hint()); }Also applies to: 286-295
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
crates/tsql/src/app/app.rs(14 hunks)crates/tsql/src/config/keymap.rs(3 hunks)crates/tsql/src/config/schema.rs(2 hunks)crates/tsql/src/ui/completion.rs(2 hunks)crates/tsql/src/ui/grid.rs(2 hunks)crates/tsql/src/ui/help_popup.rs(2 hunks)crates/tsql/src/ui/key_hint_popup.rs(1 hunks)crates/tsql/src/ui/key_sequence.rs(1 hunks)crates/tsql/src/ui/mod.rs(2 hunks)crates/tsql/src/ui/sidebar.rs(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
crates/tsql/src/ui/key_sequence.rs (2)
crates/tsql/src/config/schema.rs (4)
default(45-57)default(83-94)default(114-122)default(151-161)crates/tsql/src/ui/key_hint_popup.rs (2)
new(26-28)new(48-50)
crates/tsql/src/ui/key_hint_popup.rs (1)
crates/tsql/src/ui/key_sequence.rs (2)
new(74-81)default(67-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Test (windows-latest)
🔇 Additional comments (8)
crates/tsql/src/ui/completion.rs (1)
221-221: LGTM!Formatting-only changes that collapse method chains and constructor calls onto single lines. No behavioral impact.
Also applies to: 249-250
crates/tsql/src/ui/help_popup.rs (2)
76-88: LGTM!New help section for goto key sequences is well-documented and aligns with the PR objectives. The bindings (gg, ge, gc, gt, gr) accurately describe the implemented functionality.
216-229: LGTM!GOTO section is appropriately positioned in ALL_SECTIONS after GLOBAL, making it discoverable for users learning the new navigation shortcuts.
crates/tsql/src/ui/mod.rs (1)
11-12: LGTM!New modules and re-exports follow the existing pattern and properly expose the key sequence infrastructure for use by the app layer.
Also applies to: 33-34
crates/tsql/src/ui/sidebar.rs (2)
241-245: LGTM with minor observation.The method is clean and follows the existing pattern from
connections_up/connections_down. Note that if the connections list is empty, this will setselected_connection = Some(0)which is technically invalid, butget_selected_connectionwill safely returnNonein that case. This matches the existing behavior in other methods.
69-70: LGTM!Formatting adjustments only - no behavioral changes.
Also applies to: 248-251
crates/tsql/src/ui/grid.rs (2)
48-49: LGTM!The new
GotoFirstRowvariant correctly signals that this action originates from theggkey sequence and should be handled at the app level, aligning with the new multi-key sequence architecture.
218-220: LGTM!Correctly removed the
'g'key from the direct first-row navigation. The'g'key is now a sequence prefix handled at the app level, whileHomekey remains as a direct shortcut for keyboard navigation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/tsql/src/app/app.rs (1)
1800-1815: Edge case:connections_down(count)withcount == 0.
You guard the “move to schema” path withcount > 0, but you still callconnections_down(0)in theelse. Ifconnections_downassumescount > 0, this could misbehave.
♻️ Duplicate comments (1)
crates/tsql/src/app/app.rs (1)
1582-1630: Clearing legacypending_keyduring sequences
Already addressed in this diff and previously called out.
🧹 Nitpick comments (7)
crates/tsql/src/ui/key_sequence.rs (3)
116-136: Deduplicate state reset logic (cancel()vscomplete()).
cancel()andcomplete()do the same thing; it’s easy for them to drift later.pub fn cancel(&mut self) { - self.pending = None; - self.pending_since = None; - self.hint_shown = false; + self.complete(); }
137-147: Guard against starting a “first key” while already waiting (API footgun).
Right nowprocess_first_key()will reset the timer/state even if a sequence is already pending; App currently guards this, but the handler API itself doesn’t. Consider making it a no-op (or cancel+restart) whenself.pending.is_some().
291-300: Timeout test may be flaky under load.
Thesleep(50ms)vstimeout=10msis probably fine, but CI jitter can still bite. Consider increasing the margin (e.g., timeout 50ms + sleep 200ms) to reduce flakes.crates/tsql/src/app/app.rs (4)
1361-1378: Compute hint visibility once per tick to avoid “marked shown but not rendered” edge cases.
Becauseshould_show_hint()is time-based and called multiple times per loop, it can flip between calls. Recommend computing once beforedraw()and reusing the value in both places.- terminal.draw(|frame| { + let show_key_hint = self.key_sequence.should_show_hint(); + let pending_key = self.key_sequence.pending(); + + terminal.draw(|frame| { let size = frame.area(); ... - if self.key_sequence.should_show_hint() { - if let Some(pending_key) = self.key_sequence.pending() { + if show_key_hint { + if let Some(pending_key) = pending_key { let hint_popup = KeyHintPopup::new(pending_key); hint_popup.render(frame, size); } } ... })?;
1375-1378: Tiemark_hint_shown()to the same computed “rendered” condition.
This pairs with the change above.- if self.key_sequence.should_show_hint() && !self.key_sequence.is_hint_shown() { + if show_key_hint && !self.key_sequence.is_hint_shown() { self.key_sequence.mark_hint_shown(); }
1582-1630: Clarify/lock in intended behavior for “invalid second key” (fallthrough vs consume).
CurrentlyKeySequenceResult::Cancelledcancels and then lets the second key fall through to normal handling. That can be great (users don’t “lose” the key), but it also meansgbecomes a hard prefix that always eats the first key even if the user mistypes. Worth confirming this UX is intentional.(Also: the
else if key.code == KeyCode::Escbranch inside this block appears redundant becauseEscis handled earlier inon_key().)
1732-1734: Dead check in gridToggleSidebarbranch (focus can’t beSidebarhere).
This branch is underFocus::Grid, somatches!(self.focus, Focus::Sidebar(_))is always false.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
crates/tsql/src/app/app.rs(15 hunks)crates/tsql/src/config/keymap.rs(4 hunks)crates/tsql/src/ui/key_hint_popup.rs(1 hunks)crates/tsql/src/ui/key_sequence.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/tsql/src/ui/key_hint_popup.rs
🧰 Additional context used
🧬 Code graph analysis (2)
crates/tsql/src/app/app.rs (3)
crates/tsql/src/config/keymap.rs (2)
new(305-307)new(428-432)crates/tsql/src/ui/key_hint_popup.rs (2)
new(27-29)new(49-51)crates/tsql/src/ui/key_sequence.rs (1)
default(67-69)
crates/tsql/src/ui/key_sequence.rs (2)
crates/tsql/src/config/schema.rs (4)
default(45-57)default(83-94)default(114-122)default(151-161)crates/tsql/src/ui/key_hint_popup.rs (2)
new(27-29)new(49-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Test (windows-latest)
🔇 Additional comments (7)
crates/tsql/src/ui/key_sequence.rs (1)
8-52: Nice, minimal public surface for sequences (PendingKey,KeySequenceAction,KeySequenceResult).
This reads cleanly and should be easy to extend beyondg*.crates/tsql/src/app/app.rs (4)
561-563: App-level integration looks straightforward (configurable timeout + handler stored onApp).Also applies to: 666-688
1461-1463: Cancelling pending sequences onEscis a good, predictable escape hatch.
3736-3776:execute_key_sequence_action()routing is clean and keeps the key-sequence handler reusable.
Nice touch auto-showing the sidebar forgc/gt.
1773-1777: FallbackGridKeyResult::GotoFirstRowhandling is fine as a safety net.crates/tsql/src/config/keymap.rs (2)
101-108: New action variants are well-named and consistent with snake_case config.
956-969: Tests cover parsing for the newgoto_*strings.
Transform 'g' into a global goto prefix key with multi-key sequences: - gg: Go to first row (grid) / document start (editor) - ge: Go to editor - gc: Go to connections sidebar (auto-shows if hidden) - gt: Go to tables/schema sidebar (auto-shows if hidden) - gr: Go to results grid Features: - Configurable timeout (default 500ms) before showing hint popup - Bottom-right hint popup displays available options (like Helix editor) - Modular key_sequence.rs module for reusable multi-key handling - Works in Normal mode across all focus areas Configuration: - key_sequence_timeout_ms in [keymap] section (default: 500)
- Mark hint_shown after rendering popup to track state coherently - Cancel key_sequence on Esc to prevent stale pending state - Clear pending_key when starting/processing g sequences to prevent operator-pending state from leaking across key sequences - Use UnicodeWidthStr for proper character width calculation in popup - Add dimension clamping to prevent popup overflow on small terminals - Fix should_show_hint() to keep showing hint until sequence ends - Fix is_waiting() doc comment to match implementation - Add test coverage for goto_* FromStr parsing - Increase test sleep margin from 20ms to 50ms to reduce flakiness
The Action::GotoFirst, GotoEditor, GotoConnections, GotoTables, and GotoResults variants were defined and parseable but had no execution handlers, causing custom keybindings using these actions to silently do nothing. Added handlers in: - Grid keymap handling (lines 1738-1764): handles goto actions when grid is focused in normal mode - handle_editor_action() (lines 3069-3091): handles goto actions when query editor is focused in normal mode - Insert mode keymap handling (lines 3573-3598): handles goto actions when editor is in insert mode Each handler performs context-appropriate navigation: - GotoFirst: In grid goes to first row, in editor goes to document start - GotoEditor: Sets focus to query editor - GotoConnections: Shows sidebar and focuses connections section - GotoTables: Shows sidebar and focuses schema/tables section - GotoResults: Sets focus to results grid Added test_goto_action_bindings_in_grid_keymap to verify that custom keybindings for goto_* actions are correctly registered and can be used via configurable keymaps.
Key sequence improvements (key_sequence.rs): - Deduplicate cancel() and complete() into shared clear_state() method - Guard process_first_key() against already-waiting state (cancel+restart) - Increase test timeout margin (50ms timeout + 200ms sleep) to reduce flakiness Render loop improvements (app.rs): - Compute hint visibility once per tick to avoid time-based state flipping - Use pre-computed show_key_hint and pending_key_for_hint in draw closure - Remove redundant Esc check (already handled by global Esc handler) - Add UX comment explaining Cancelled fallthrough behavior - Remove dead Focus::Sidebar check in Focus::Grid ToggleSidebar branch
f525ec4 to
b2e0e53
Compare
Summary
ginto a global goto prefix key with multi-key sequencesKey Bindings
gggegcgtgrFeatures
gand wait 500ms to see available optionsggquickly to execute without popupkey_sequence_timeout_msin config (default 500ms)gcandgtautomatically show sidebar if hiddenTest plan
gand wait - hint popup should appear in bottom-rightggquickly - should go to first row without popupge,gc,gt,grnavigate to correct areasgcandgtshould auto-show sidebar if hiddengthenEsc- should cancel sequencegthen invalid key - should cancel sequenceSummary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.