nanorust 0.1.0

A terminal-based text editor written in Rust, inspired by GNU nano
Documentation
use crate::display::Display;
use crate::error::{EditorError, Result};
use crate::input;
use crate::terminal::Terminal;
use std::path::PathBuf;

/// Commands that can be executed in the editor
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Command {
    /// Save the current buffer
    Save,
    /// Save the buffer to a specific file
    SaveAs(PathBuf),
    /// Quit the editor
    Quit,
    /// Search for a string
    Search(String),
    /// Search for the next occurrence
    SearchNext,
    /// Replace text
    Replace(String, String),
    /// Cut text
    Cut,
    /// Paste text
    Paste,
    /// Copy text
    Copy,
    /// Undo last operation
    Undo,
    /// Redo last undone operation
    Redo,
    /// Show help screen
    Help,
    /// Go to a specific line
    GoToLine(usize),
    /// Insert file at cursor
    InsertFile(PathBuf),
}

/// Command processor for handling editor commands
pub struct CommandProcessor;

impl CommandProcessor {
    /// Prompt the user for a filename
    /// 
    /// Used for save operations when no filename is set
    pub fn prompt_filename(
        terminal: &mut Terminal,
        display: &mut Display,
        current_filename: Option<&PathBuf>,
    ) -> Result<Option<PathBuf>> {
        // Show prompt in status bar area
        let prompt = if let Some(filename) = current_filename {
            format!("File Name to Write: {} ", filename.display())
        } else {
            "File Name to Write: ".to_string()
        };

        // Move cursor to status bar area and show prompt
        terminal.move_cursor_to_status_line()?;
        terminal.clear_current_line()?;
        terminal.write_str(&prompt)?;
        terminal.flush()?;

        // Read the filename
        match Self::read_input_at_cursor(terminal) {
            Ok(input) => {
                if input.is_empty() {
                    // If empty and we have a current filename, use it
                    if let Some(filename) = current_filename {
                        Ok(Some(filename.clone()))
                    } else {
                        display.show_message("Cancelled".to_string());
                        Ok(None)
                    }
                } else {
                    Ok(Some(PathBuf::from(input)))
                }
            }
            Err(_) => {
                display.show_message("Cancelled".to_string());
                Ok(None)
            }
        }
    }

    /// Prompt the user for a search string
    /// 
    /// Used for search operations
    pub fn prompt_search(
        terminal: &mut Terminal,
        display: &mut Display,
    ) -> Result<Option<String>> {
        // Show prompt in status bar area
        let prompt = "Search: ";

        terminal.move_cursor_to_status_line()?;
        terminal.clear_current_line()?;
        terminal.write_str(prompt)?;
        terminal.flush()?;

        // Read the search string
        match Self::read_input_at_cursor(terminal) {
            Ok(input) => {
                if input.is_empty() {
                    display.show_message("Cancelled".to_string());
                    Ok(None)
                } else {
                    Ok(Some(input))
                }
            }
            Err(_) => {
                display.show_message("Cancelled".to_string());
                Ok(None)
            }
        }
    }

    /// Prompt the user for search and replacement strings
    /// 
    /// Used for replace operations
    pub fn prompt_replace(
        terminal: &mut Terminal,
        display: &mut Display,
    ) -> Result<Option<(String, String)>> {
        // First prompt for search string
        terminal.move_cursor_to_status_line()?;
        terminal.clear_current_line()?;
        terminal.write_str("Search: ")?;
        terminal.flush()?;

        let search = match Self::read_input_at_cursor(terminal) {
            Ok(input) if !input.is_empty() => input,
            _ => {
                display.show_message("Cancelled".to_string());
                return Ok(None);
            }
        };

        // Then prompt for replacement string
        terminal.move_cursor_to_status_line()?;
        terminal.clear_current_line()?;
        terminal.write_str("Replace with: ")?;
        terminal.flush()?;

        let replace = match Self::read_input_at_cursor(terminal) {
            Ok(input) => input,
            Err(_) => {
                display.show_message("Cancelled".to_string());
                return Ok(None);
            }
        };

        Ok(Some((search, replace)))
    }

    /// Prompt the user for a line number
    /// 
    /// Used for go-to-line operations
    pub fn prompt_goto_line(
        terminal: &mut Terminal,
        display: &mut Display,
    ) -> Result<Option<usize>> {
        // Show prompt in status bar area
        let prompt = "Enter line number: ";

        terminal.move_cursor_to_status_line()?;
        terminal.clear_current_line()?;
        terminal.write_str(prompt)?;
        terminal.flush()?;

        // Read the line number
        match Self::read_input_at_cursor(terminal) {
            Ok(input) => {
                if input.is_empty() {
                    display.show_message("Cancelled".to_string());
                    Ok(None)
                } else {
                    match input.parse::<usize>() {
                        Ok(line_num) => Ok(Some(line_num)),
                        Err(_) => {
                            display.show_message("Invalid line number".to_string());
                            Ok(None)
                        }
                    }
                }
            }
            Err(_) => {
                display.show_message("Cancelled".to_string());
                Ok(None)
            }
        }
    }

    /// Read input from the user at the current cursor position
    /// 
    /// This is a helper function for reading user input during prompts
    fn read_input_at_cursor(terminal: &mut Terminal) -> Result<String> {
        let mut input = String::new();

        loop {
            let key = input::read_key()?;

            match key {
                input::Key::Enter => break,
                input::Key::Char(ch) => {
                    input.push(ch);
                    terminal.write_str(&ch.to_string())?;
                    terminal.flush()?;
                }
                input::Key::Backspace => {
                    if input.pop().is_some() {
                        // Move cursor back, write space, move back again
                        terminal.write_str("\x08 \x08")?;
                        terminal.flush()?;
                    }
                }
                input::Key::Ctrl('c') | input::Key::Ctrl('g') => {
                    // Cancel input
                    return Err(EditorError::ParseError("Input cancelled".to_string()));
                }
                _ => {} // Ignore other keys
            }
        }

        Ok(input)
    }
}