use super::normalize_path;
use super::BufferId;
use super::BufferMetadata;
use super::Editor;
use crate::input::keybindings::Action;
use crate::services::plugins::hooks::HookArgs;
use crate::view::prompt::PromptType;
use std::path::Path;
pub enum PromptResult {
Done,
ExecuteAction(Action),
EarlyReturn,
}
impl Editor {
pub fn handle_prompt_confirm_input(
&mut self,
input: String,
prompt_type: PromptType,
selected_index: Option<usize>,
) -> PromptResult {
match prompt_type {
PromptType::OpenFile => {
let input_path = Path::new(&input);
let resolved_path = if input_path.is_absolute() {
normalize_path(input_path)
} else {
normalize_path(&self.working_dir.join(input_path))
};
if let Err(e) = self.open_file(&resolved_path) {
self.set_status_message(format!("Error opening file: {e}"));
} else {
self.set_status_message(format!("Opened {}", resolved_path.display()));
}
}
PromptType::SwitchProject => {
let input_path = Path::new(&input);
let resolved_path = if input_path.is_absolute() {
normalize_path(input_path)
} else {
normalize_path(&self.working_dir.join(input_path))
};
if resolved_path.is_dir() {
self.change_working_dir(resolved_path);
} else {
self.set_status_message(format!(
"Not a directory: {}",
resolved_path.display()
));
}
}
PromptType::SaveFileAs => {
self.handle_save_file_as(&input);
}
PromptType::Search => {
self.perform_search(&input);
}
PromptType::ReplaceSearch => {
self.perform_search(&input);
self.start_prompt(
format!("Replace '{}' with: ", input),
PromptType::Replace {
search: input.clone(),
},
);
}
PromptType::Replace { search } => {
if self.search_confirm_each {
self.start_interactive_replace(&search, &input);
} else {
self.perform_replace(&search, &input);
}
}
PromptType::QueryReplaceSearch => {
self.perform_search(&input);
self.start_prompt(
format!("Query replace '{}' with: ", input),
PromptType::QueryReplace {
search: input.clone(),
},
);
}
PromptType::QueryReplace { search } => {
if self.search_confirm_each {
self.start_interactive_replace(&search, &input);
} else {
self.perform_replace(&search, &input);
}
}
PromptType::Command => {
let commands = self.command_registry.read().unwrap().get_all();
if let Some(cmd) = commands.iter().find(|c| c.name == input) {
let action = cmd.action.clone();
let cmd_name = cmd.name.clone();
self.set_status_message(format!("Executing: {}", cmd_name));
self.command_registry
.write()
.unwrap()
.record_usage(&cmd_name);
return PromptResult::ExecuteAction(action);
} else {
self.set_status_message(format!("Unknown command: {input}"));
}
}
PromptType::GotoLine => match input.trim().parse::<usize>() {
Ok(line_num) if line_num > 0 => {
self.goto_line_col(line_num, None);
self.set_status_message(format!("Jumped to line {}", line_num));
}
Ok(_) => {
self.set_status_message("Line number must be positive".to_string());
}
Err(_) => {
self.set_status_message(format!("Invalid line number: {}", input));
}
},
PromptType::SetBackgroundFile => {
if let Err(e) = self.load_ansi_background(&input) {
self.set_status_message(format!("Failed to load background: {}", e));
}
}
PromptType::SetBackgroundBlend => match input.trim().parse::<f32>() {
Ok(val) => {
let clamped = val.clamp(0.0, 1.0);
self.background_fade = clamped;
self.set_status_message(format!("Background blend set to {:.2}", clamped));
}
Err(_) => {
self.set_status_message(format!("Invalid blend value: {}", input));
}
},
PromptType::SetComposeWidth => {
self.handle_set_compose_width(&input);
}
PromptType::RecordMacro => {
self.handle_register_input(
&input,
|editor, c| editor.toggle_macro_recording(c),
"Macro",
);
}
PromptType::PlayMacro => {
self.handle_register_input(&input, |editor, c| editor.play_macro(c), "Macro");
}
PromptType::SetBookmark => {
self.handle_register_input(&input, |editor, c| editor.set_bookmark(c), "Bookmark");
}
PromptType::JumpToBookmark => {
self.handle_register_input(
&input,
|editor, c| editor.jump_to_bookmark(c),
"Bookmark",
);
}
PromptType::Plugin { custom_type } => {
self.plugin_manager.run_hook(
"prompt_confirmed",
HookArgs::PromptConfirmed {
prompt_type: custom_type,
input,
selected_index,
},
);
}
PromptType::ConfirmRevert => {
let input_lower = input.trim().to_lowercase();
if input_lower == "r" || input_lower == "revert" {
if let Err(e) = self.revert_file() {
self.set_status_message(format!("Failed to revert: {}", e));
}
} else {
self.set_status_message("Revert cancelled".to_string());
}
}
PromptType::ConfirmSaveConflict => {
let input_lower = input.trim().to_lowercase();
if input_lower == "o" || input_lower == "overwrite" {
if let Err(e) = self.save() {
self.set_status_message(format!("Failed to save: {}", e));
}
} else {
self.set_status_message("Save cancelled".to_string());
}
}
PromptType::ConfirmCloseBuffer { buffer_id } => {
if self.handle_confirm_close_buffer(&input, buffer_id) {
return PromptResult::EarlyReturn;
}
}
PromptType::ConfirmQuitWithModified => {
let input_lower = input.trim().to_lowercase();
if input_lower == "d" || input_lower == "discard" {
self.should_quit = true;
} else {
self.set_status_message("Quit cancelled".to_string());
}
}
PromptType::LspRename {
original_text,
start_pos,
end_pos: _,
overlay_handle,
} => {
self.perform_lsp_rename(input, original_text, start_pos, overlay_handle);
}
PromptType::FileExplorerRename {
original_path,
original_name,
} => {
self.perform_file_explorer_rename(original_path, original_name, input);
}
PromptType::StopLspServer => {
self.handle_stop_lsp_server(&input);
}
PromptType::SelectTheme => {
self.apply_theme(input.trim());
}
PromptType::SelectKeybindingMap => {
self.apply_keybinding_map(input.trim());
}
PromptType::SwitchToTab => {
if let Ok(id) = input.trim().parse::<usize>() {
self.switch_to_tab(BufferId(id));
}
}
PromptType::QueryReplaceConfirm => {
if let Some(c) = input.chars().next() {
let _ = self.handle_interactive_replace_key(c);
}
}
}
PromptResult::Done
}
fn handle_save_file_as(&mut self, input: &str) {
let input_path = Path::new(input);
let full_path = if input_path.is_absolute() {
normalize_path(input_path)
} else {
normalize_path(&self.working_dir.join(input_path))
};
let before_idx = self.active_event_log().current_index();
let before_len = self.active_event_log().len();
tracing::debug!(
"SaveFileAs BEFORE: event_log index={}, len={}",
before_idx,
before_len
);
match self.active_state_mut().buffer.save_to_file(&full_path) {
Ok(()) => {
let after_save_idx = self.active_event_log().current_index();
let after_save_len = self.active_event_log().len();
tracing::debug!(
"SaveFileAs AFTER buffer.save_to_file: event_log index={}, len={}",
after_save_idx,
after_save_len
);
let metadata = BufferMetadata::with_file(full_path.clone(), &self.working_dir);
self.buffer_metadata.insert(self.active_buffer(), metadata);
self.active_event_log_mut().mark_saved();
tracing::debug!(
"SaveFileAs AFTER mark_saved: event_log index={}, len={}",
self.active_event_log().current_index(),
self.active_event_log().len()
);
if let Ok(metadata) = std::fs::metadata(&full_path) {
if let Ok(mtime) = metadata.modified() {
self.file_mod_times.insert(full_path.clone(), mtime);
}
}
self.notify_lsp_save();
self.emit_event(
crate::model::control_event::events::FILE_SAVED.name,
serde_json::json!({"path": full_path.display().to_string()}),
);
self.plugin_manager.run_hook(
"after_file_save",
crate::services::plugins::hooks::HookArgs::AfterFileSave {
buffer_id: self.active_buffer(),
path: full_path.clone(),
},
);
if let Some(buffer_to_close) = self.pending_close_buffer.take() {
if let Err(e) = self.force_close_buffer(buffer_to_close) {
self.set_status_message(format!("Saved, but cannot close buffer: {}", e));
} else {
self.set_status_message("Saved and closed".to_string());
}
} else {
self.set_status_message(format!("Saved as: {}", full_path.display()));
}
}
Err(e) => {
self.pending_close_buffer = None;
self.set_status_message(format!("Error saving file: {}", e));
}
}
}
fn handle_set_compose_width(&mut self, input: &str) {
let buffer_id = self.active_buffer();
let active_split = self.split_manager.active_split();
let trimmed = input.trim();
if trimmed.is_empty() {
if let Some(state) = self.buffers.get_mut(&buffer_id) {
state.compose_width = None;
}
if let Some(vs) = self.split_view_states.get_mut(&active_split) {
vs.compose_width = None;
}
self.set_status_message("Compose width cleared (viewport)".to_string());
} else {
match trimmed.parse::<u16>() {
Ok(val) if val > 0 => {
if let Some(state) = self.buffers.get_mut(&buffer_id) {
state.compose_width = Some(val);
}
if let Some(vs) = self.split_view_states.get_mut(&active_split) {
vs.compose_width = Some(val);
}
self.set_status_message(format!("Compose width set to {}", val));
}
_ => {
self.set_status_message(format!("Invalid compose width: {}", input));
}
}
}
}
fn handle_register_input<F>(&mut self, input: &str, action: F, register_type: &str)
where
F: FnOnce(&mut Self, char),
{
if let Some(c) = input.trim().chars().next() {
if c.is_ascii_digit() {
action(self, c);
} else {
self.set_status_message(format!("{} register must be 0-9", register_type));
}
} else {
self.set_status_message("No register specified".to_string());
}
}
fn handle_confirm_close_buffer(&mut self, input: &str, buffer_id: BufferId) -> bool {
let input_lower = input.trim().to_lowercase();
match input_lower.chars().next() {
Some('s') => {
let has_path = self
.buffers
.get(&buffer_id)
.map(|s| s.buffer.file_path().is_some())
.unwrap_or(false);
if has_path {
let old_active = self.active_buffer();
self.set_active_buffer(buffer_id);
if let Err(e) = self.save() {
self.set_status_message(format!("Failed to save: {}", e));
self.set_active_buffer(old_active);
return true; }
self.set_active_buffer(old_active);
if let Err(e) = self.force_close_buffer(buffer_id) {
self.set_status_message(format!("Cannot close buffer: {}", e));
} else {
self.set_status_message("Saved and closed".to_string());
}
} else {
self.pending_close_buffer = Some(buffer_id);
self.start_prompt_with_initial_text(
"Save as: ".to_string(),
PromptType::SaveFileAs,
String::new(),
);
}
}
Some('d') => {
if let Err(e) = self.force_close_buffer(buffer_id) {
self.set_status_message(format!("Cannot close buffer: {}", e));
} else {
self.set_status_message("Buffer closed (changes discarded)".to_string());
}
}
_ => {
self.set_status_message("Close cancelled".to_string());
}
}
false
}
fn handle_stop_lsp_server(&mut self, input: &str) {
let language = input.trim();
if language.is_empty() {
return;
}
if let Some(lsp) = &mut self.lsp {
if lsp.shutdown_server(language) {
if let Some(lsp_config) = self.config.lsp.get_mut(language) {
lsp_config.auto_start = false;
if let Err(e) = self.save_config() {
tracing::warn!(
"Failed to save config after disabling LSP auto-start: {}",
e
);
} else {
let config_path = self.dir_context.config_path();
self.emit_event(
"config_changed",
serde_json::json!({
"path": config_path.to_string_lossy(),
}),
);
}
}
self.set_status_message(format!(
"LSP server for '{}' stopped (auto-start disabled)",
language
));
} else {
self.set_status_message(format!("No running LSP server found for '{}'", language));
}
}
}
}