diff options
| author | sf-jin-ku <jin.ku@sendbird.com> | 2026-06-03 09:13:01 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-03 19:13:01 +0300 |
| commit | f5e66b251fa8ec24989bf25861bcf4e38a561470 (patch) | |
| tree | 3244ffd1af0054334f8fe65ddbb0ff6f5a809cd3 /src/app/input/settings.rs | |
| parent | 03f68b7275f07c98769bf699d2aa14805b921f8d (diff) | |
feat: switch input source during prefix mode (#434)
On macOS, opt-in under [experimental], temporarily switch the host input
source to the current ASCII-capable layout while prefix mode is active so
prefix commands register even when a CJK IME is composing, then restore the
previous input source when prefix mode exits or is cancelled. No-op when the
current source is already ASCII-capable, on non-macOS platforms, or when any
input source call fails. Pane text input is unaffected.
Also pin next_resize_poll in next_loop_deadline_includes_selection_autoscroll_deadline
so the selection autoscroll deadline stays the earliest; the default resize
poll could otherwise beat it and flake the assertion.
refs #400
Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/app/input/settings.rs')
| -rw-r--r-- | src/app/input/settings.rs | 82 |
1 files changed, 74 insertions, 8 deletions
diff --git a/src/app/input/settings.rs b/src/app/input/settings.rs index 986f59e2..66ec3226 100644 --- a/src/app/input/settings.rs +++ b/src/app/input/settings.rs @@ -3,7 +3,7 @@ use ratatui::layout::Rect; use crate::{ app::{ - state::{AppState, SettingsSection, THEME_NAMES}, + state::{AppState, ExperimentSetting, SettingsSection, THEME_NAMES}, App, Mode, }, config::ToastDelivery, @@ -18,9 +18,24 @@ pub(super) enum SettingsAction { SaveToastDelivery(ToastDelivery), SaveAgentBorderLabels(bool), SavePaneHistory(bool), + SaveSwitchAsciiInputSourceInPrefix(bool), InstallRecommendedIntegrations, } +/// Map an Experiments row index to the toggle action that flips it. +fn experiment_toggle_action(state: &AppState, idx: usize) -> Option<SettingsAction> { + match ExperimentSetting::ALL.get(idx).copied()? { + ExperimentSetting::PaneHistory => Some(SettingsAction::SavePaneHistory( + !ExperimentSetting::PaneHistory.enabled(state), + )), + ExperimentSetting::SwitchAsciiInputSourceInPrefix => { + Some(SettingsAction::SaveSwitchAsciiInputSourceInPrefix( + !ExperimentSetting::SwitchAsciiInputSourceInPrefix.enabled(state), + )) + } + } +} + impl App { pub(crate) fn handle_settings_key(&mut self, key: KeyEvent) { let previous_section = self.state.settings.section; @@ -35,6 +50,9 @@ impl App { SettingsAction::SavePaneHistory(enabled) => { self.save_pane_history_persistence(enabled) } + SettingsAction::SaveSwitchAsciiInputSourceInPrefix(enabled) => { + self.save_switch_ascii_input_source_in_prefix(enabled) + } SettingsAction::InstallRecommendedIntegrations => { self.install_recommended_integrations() } @@ -228,10 +246,12 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti } }, SettingsSection::Experiments => match key.code { + KeyCode::Up | KeyCode::Char('k') => state.settings.list.move_prev(), + KeyCode::Down | KeyCode::Char('j') => { + state.settings.list.move_next(ExperimentSetting::ALL.len()) + } KeyCode::Enter | KeyCode::Char(' ') => { - return Some(SettingsAction::SavePaneHistory( - !state.pane_history_persistence_enabled(), - )); + return experiment_toggle_action(state, state.settings.list.selected); } KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => { state.settings.section = SettingsSection::Integrations; @@ -377,7 +397,11 @@ impl AppState { } SettingsSection::Experiments => { let list_y = area.y + 3; - (row == list_y).then_some(0) + if row >= list_y && row < list_y + ExperimentSetting::ALL.len() as u16 { + Some((row - list_y) as usize) + } else { + None + } } SettingsSection::Integrations => None, } @@ -419,9 +443,7 @@ impl AppState { let enabled = idx == 0; Some(SettingsAction::SaveAgentBorderLabels(enabled)) } - SettingsSection::Experiments => Some(SettingsAction::SavePaneHistory( - !self.pane_history_persistence_enabled(), - )), + SettingsSection::Experiments => experiment_toggle_action(self, idx), SettingsSection::Integrations => None, }; } @@ -524,6 +546,30 @@ mod tests { } #[test] + fn settings_experiments_down_then_toggle_switches_ascii_input_source() { + let mut state = state_with_workspaces(&["test"]); + state.switch_ascii_input_source_in_prefix = false; + open_settings_at(&mut state, SettingsSection::Experiments); + + update_settings_state( + &mut state, + KeyEvent::new(KeyCode::Down, KeyModifiers::empty()), + ); + assert_eq!(state.settings.list.selected, 1); + + let action = update_settings_state( + &mut state, + KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()), + ); + + assert_eq!( + action, + Some(SettingsAction::SaveSwitchAsciiInputSourceInPrefix(true)) + ); + assert_eq!(state.mode, Mode::Settings); + } + + #[test] fn settings_tab_cycle_places_experiments_last() { let mut state = state_with_workspaces(&["test"]); open_settings_at(&mut state, SettingsSection::PaneLabels); @@ -607,6 +653,26 @@ mod tests { } #[test] + fn settings_mouse_click_toggles_switch_ascii_input_source_row() { + let mut app = app_for_mouse_test(); + app.state.switch_ascii_input_source_in_prefix = false; + open_settings_at(&mut app.state, SettingsSection::Experiments); + + let area = app.state.settings_content_rect(); + let action = app.state.handle_settings_mouse(mouse( + MouseEventKind::Down(crossterm::event::MouseButton::Left), + area.x + 2, + area.y + 4, + )); + + assert_eq!( + action, + Some(SettingsAction::SaveSwitchAsciiInputSourceInPrefix(true)) + ); + assert_eq!(app.state.settings.list.selected, 1); + } + + #[test] fn integration_update_badge_only_tracks_outdated_recommendations() { let mut state = state_with_workspaces(&["test"]); state.integration_recommendations = vec