feat: Add support IPC socket control over menu.

This commit is contained in:
2026-03-14 17:02:16 -04:00
parent cd1927fa9f
commit aad4c16d6e
15 changed files with 1501 additions and 185 deletions

View File

@@ -39,6 +39,7 @@ pub fn restore_terminal() {
pub async fn run(
action_tx: mpsc::Sender<Action>,
mut event_rx: broadcast::Receiver<MenuEvent>,
filter_history: Option<Vec<String>>,
) -> std::io::Result<()> {
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(std::io::stderr(), crossterm::terminal::EnterAlternateScreen,)?;
@@ -53,7 +54,7 @@ pub async fn run(
let _ = crossterm::event::read()?;
}
let result = run_inner(&action_tx, &mut event_rx, &mut terminal).await;
let result = run_inner(&action_tx, &mut event_rx, &mut terminal, filter_history).await;
// Always clean up terminal, even on error
restore_terminal();
@@ -67,6 +68,7 @@ async fn run_inner(
action_tx: &mpsc::Sender<Action>,
event_rx: &mut broadcast::Receiver<MenuEvent>,
terminal: &mut Terminal<CrosstermBackend<std::io::Stderr>>,
filter_history: Option<Vec<String>>,
) -> std::io::Result<()> {
// Send initial resize
let size = terminal.size()?;
@@ -95,6 +97,11 @@ async fn run_inner(
let mut multi_enabled = false;
let mut visual_anchor: Option<usize> = None;
// Session filter history cycling state
let history = filter_history.unwrap_or_default();
let mut history_cursor: Option<usize> = None;
let mut pre_history_text: Option<String> = None;
loop {
if let Some(ref vs) = view_state {
let ft = filter_text.clone();
@@ -112,6 +119,62 @@ async fn run_inner(
let event = event_result?;
match event {
Event::Key(key) => {
// History cycling: Ctrl+P/Ctrl+N in insert mode
// with a non-empty history list.
if mode == Mode::Insert && !history.is_empty() {
let history_handled = match (key.code, key.modifiers) {
(KeyCode::Char('p'), KeyModifiers::CONTROL) | (KeyCode::Up, _) => {
if pre_history_text.is_none() {
pre_history_text = Some(filter_text.clone());
}
let new_cursor = match history_cursor {
Some(c) if c > 0 => c - 1,
Some(_) => 0,
None => history.len() - 1,
};
history_cursor = Some(new_cursor);
filter_text.clone_from(&history[new_cursor]);
let _ = action_tx
.send(Action::UpdateFilter(filter_text.clone()))
.await;
true
}
(KeyCode::Char('n'), KeyModifiers::CONTROL) => {
if let Some(c) = history_cursor {
if c + 1 < history.len() {
history_cursor = Some(c + 1);
filter_text.clone_from(&history[c + 1]);
} else {
// Past the end: restore original text
history_cursor = None;
if let Some(ref saved) = pre_history_text {
filter_text.clone_from(saved);
}
pre_history_text = None;
}
let _ = action_tx
.send(Action::UpdateFilter(filter_text.clone()))
.await;
true
} else {
// Not cycling, fall through to normal Ctrl+N
false
}
}
_ => {
// Any other key resets history cycling
if history_cursor.is_some() {
history_cursor = None;
pre_history_text = None;
}
false
}
};
if history_handled {
continue;
}
}
// Handle multi-action keys (Tab, Space in normal/visual)
// before the single-action map_key_event path.
let handled = handle_multi_action_key(