use super::terminal_input::{should_enter_terminal_mode, TerminalModeInputHandler};
use super::Editor;
use crate::input::handler::{DeferredAction, InputContext, InputHandler, InputResult};
use crate::input::keybindings::Action;
use crate::view::file_browser_input::FileBrowserInputHandler;
use crate::view::query_replace_input::QueryReplaceConfirmInputHandler;
use crate::view::ui::MenuInputHandler;
use crossterm::event::KeyEvent;
impl Editor {
pub fn dispatch_terminal_input(&mut self, event: &KeyEvent) -> Option<InputResult> {
let in_modal = self.is_prompting()
|| self.active_state().popups.is_visible()
|| self.menu_state.active_menu.is_some()
|| self.settings_state.as_ref().map_or(false, |s| s.visible);
if in_modal {
return None;
}
if self.terminal_mode {
let mut ctx = InputContext::new();
let mut handler =
TerminalModeInputHandler::new(self.keyboard_capture, &self.keybindings);
let result = handler.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
if self.is_terminal_buffer(self.active_buffer()) && should_enter_terminal_mode(event) {
self.enter_terminal_mode();
return Some(InputResult::Consumed);
}
None
}
pub fn dispatch_modal_input(&mut self, event: &KeyEvent) -> Option<InputResult> {
let mut ctx = InputContext::new();
if let Some(ref mut settings) = self.settings_state {
if settings.visible {
let result = settings.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
}
if self.menu_state.active_menu.is_some() {
let all_menus: Vec<crate::config::Menu> = self
.config
.menu
.menus
.iter()
.chain(self.menu_state.plugin_menus.iter())
.cloned()
.collect();
let mut handler = MenuInputHandler::new(&mut self.menu_state, &all_menus);
let result = handler.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
if self.prompt.is_some() {
if event
.modifiers
.contains(crossterm::event::KeyModifiers::ALT)
{
if let crossterm::event::KeyCode::Char(_) = event.code {
let action = self
.keybindings
.resolve(event, crate::input::keybindings::KeyContext::Prompt);
if !matches!(action, Action::None) {
let _ = self.handle_action(action);
return Some(InputResult::Consumed);
}
}
}
if self.is_file_open_active() {
if let (Some(ref mut file_state), Some(ref mut prompt)) =
(&mut self.file_open_state, &mut self.prompt)
{
let mut handler = FileBrowserInputHandler::new(file_state, prompt);
let result = handler.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
}
use crate::view::prompt::PromptType;
let is_query_replace_confirm = self
.prompt
.as_ref()
.map_or(false, |p| p.prompt_type == PromptType::QueryReplaceConfirm);
if is_query_replace_confirm {
let mut handler = QueryReplaceConfirmInputHandler::new();
let result = handler.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
if let Some(ref mut prompt) = self.prompt {
let result = prompt.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
}
if self.active_state().popups.is_visible() {
let result = self
.active_state_mut()
.popups
.dispatch_input(event, &mut ctx);
self.process_deferred_actions(ctx);
return Some(result);
}
None
}
pub fn process_deferred_actions(&mut self, ctx: InputContext) {
if let Some(msg) = ctx.status_message {
self.set_status_message(msg);
}
for action in ctx.deferred_actions {
if let Err(e) = self.execute_deferred_action(action) {
self.set_status_message(format!("Error: {}", e));
}
}
}
fn execute_deferred_action(&mut self, action: DeferredAction) -> std::io::Result<()> {
match action {
DeferredAction::CloseSettings { save } => {
if save {
self.save_settings();
}
self.close_settings(false);
}
DeferredAction::CloseMenu => {
self.menu_state.close_menu();
}
DeferredAction::ExecuteMenuAction { action, args } => {
if let Some(kb_action) = self.menu_action_to_action(&action, args) {
self.handle_action(kb_action)?;
}
}
DeferredAction::ClosePrompt => {
self.cancel_prompt();
}
DeferredAction::ConfirmPrompt => {
self.handle_action(Action::PromptConfirm)?;
}
DeferredAction::UpdatePromptSuggestions => {
self.update_prompt_suggestions();
}
DeferredAction::PromptHistoryPrev => {
self.prompt_history_prev();
}
DeferredAction::PromptHistoryNext => {
self.prompt_history_next();
}
DeferredAction::ClosePopup => {
self.hide_popup();
}
DeferredAction::ConfirmPopup => {
self.handle_action(Action::PopupConfirm)?;
}
DeferredAction::ExecuteAction(kb_action) => {
self.handle_action(kb_action)?;
}
DeferredAction::InsertCharAndUpdate(c) => {
if let Some(ref mut prompt) = self.prompt {
prompt.insert_char(c);
}
self.update_prompt_suggestions();
}
DeferredAction::FileBrowserSelectPrev => {
if let Some(state) = &mut self.file_open_state {
state.select_prev();
}
}
DeferredAction::FileBrowserSelectNext => {
if let Some(state) = &mut self.file_open_state {
state.select_next();
}
}
DeferredAction::FileBrowserPageUp => {
if let Some(state) = &mut self.file_open_state {
state.page_up(10);
}
}
DeferredAction::FileBrowserPageDown => {
if let Some(state) = &mut self.file_open_state {
state.page_down(10);
}
}
DeferredAction::FileBrowserConfirm => {
self.handle_file_open_action(&Action::PromptConfirm);
}
DeferredAction::FileBrowserAcceptSuggestion => {
self.handle_file_open_action(&Action::PromptAcceptSuggestion);
}
DeferredAction::FileBrowserGoParent => {
let parent = self
.file_open_state
.as_ref()
.and_then(|s| s.current_dir.parent())
.map(|p| p.to_path_buf());
if let Some(parent_path) = parent {
self.load_file_open_directory(parent_path);
}
}
DeferredAction::FileBrowserUpdateFilter => {
self.update_file_open_filter();
}
DeferredAction::InteractiveReplaceKey(c) => {
self.handle_interactive_replace_key(c)?;
}
DeferredAction::CancelInteractiveReplace => {
self.cancel_prompt();
self.interactive_replace_state = None;
}
DeferredAction::ToggleKeyboardCapture => {
self.keyboard_capture = !self.keyboard_capture;
if self.keyboard_capture {
self.set_status_message(
"Keyboard capture ON - all keys go to terminal (F9 to toggle)".to_string(),
);
} else {
self.set_status_message(
"Keyboard capture OFF - UI bindings active (F9 to toggle)".to_string(),
);
}
}
DeferredAction::SendTerminalKey(code, modifiers) => {
self.send_terminal_key(code, modifiers);
}
DeferredAction::ExitTerminalMode { explicit } => {
self.terminal_mode = false;
self.key_context = crate::input::keybindings::KeyContext::Normal;
if explicit {
self.terminal_mode_resume.remove(&self.active_buffer());
self.sync_terminal_to_buffer(self.active_buffer());
self.set_status_message(
"Terminal mode disabled - read only (Ctrl+Space to resume)".to_string(),
);
}
}
DeferredAction::EnterScrollbackMode => {
self.terminal_mode = false;
self.key_context = crate::input::keybindings::KeyContext::Normal;
self.sync_terminal_to_buffer(self.active_buffer());
self.set_status_message(
"Scrollback mode - use PageUp/Down to scroll (Ctrl+Space to resume)"
.to_string(),
);
self.handle_action(Action::MovePageUp)?;
}
DeferredAction::EnterTerminalMode => {
self.enter_terminal_mode();
}
}
Ok(())
}
fn menu_action_to_action(
&self,
action_name: &str,
args: std::collections::HashMap<String, serde_json::Value>,
) -> Option<Action> {
if let Some(action) = Action::from_str(action_name, &args) {
return Some(action);
}
Some(Action::PluginAction(action_name.to_string()))
}
fn prompt_history_prev(&mut self) {
use crate::view::prompt::PromptType;
let prompt_info = self
.prompt
.as_ref()
.map(|p| (p.prompt_type.clone(), p.input.clone()));
if let Some((prompt_type, current_input)) = prompt_info {
if matches!(
prompt_type,
PromptType::Search | PromptType::ReplaceSearch | PromptType::QueryReplaceSearch
) {
if let Some(entry) = self.search_history.navigate_prev(¤t_input) {
if let Some(ref mut prompt) = self.prompt {
prompt.set_input(entry);
}
}
}
else if matches!(
prompt_type,
PromptType::Replace { .. } | PromptType::QueryReplace { .. }
) {
if let Some(entry) = self.replace_history.navigate_prev(¤t_input) {
if let Some(ref mut prompt) = self.prompt {
prompt.set_input(entry);
}
}
}
}
}
fn prompt_history_next(&mut self) {
use crate::view::prompt::PromptType;
let prompt_type = self.prompt.as_ref().map(|p| p.prompt_type.clone());
if let Some(prompt_type) = prompt_type {
if matches!(
prompt_type,
PromptType::Search | PromptType::ReplaceSearch | PromptType::QueryReplaceSearch
) {
if let Some(entry) = self.search_history.navigate_next() {
if let Some(ref mut prompt) = self.prompt {
prompt.set_input(entry);
}
}
}
else if matches!(
prompt_type,
PromptType::Replace { .. } | PromptType::QueryReplace { .. }
) {
if let Some(entry) = self.replace_history.navigate_next() {
if let Some(ref mut prompt) = self.prompt {
prompt.set_input(entry);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deferred_action_close_menu() {
let action = DeferredAction::CloseMenu;
assert!(matches!(action, DeferredAction::CloseMenu));
}
#[test]
fn test_deferred_action_execute_menu_action() {
let action = DeferredAction::ExecuteMenuAction {
action: "save".to_string(),
args: std::collections::HashMap::new(),
};
if let DeferredAction::ExecuteMenuAction { action: name, .. } = action {
assert_eq!(name, "save");
} else {
panic!("Expected ExecuteMenuAction");
}
}
}