nanorust 0.1.0

A terminal-based text editor written in Rust, inspired by GNU nano
Documentation
use crate::error::{EditorError, Result};
use std::io::{self, Write};
use termion::raw::{IntoRawMode, RawTerminal};
use nix::sys::signal::{self, Signal};

/// Terminal size information
#[derive(Debug, Clone, Copy)]
pub struct TerminalSize {
    pub rows: u16,
    pub cols: u16,
}

/// Terminal handler for raw mode operations
pub struct Terminal {
    stdout: RawTerminal<io::Stdout>,
    size: TerminalSize,
}

impl Terminal {
    /// Create a new Terminal instance and enter raw mode
    pub fn new() -> Result<Self> {
        let stdout = io::stdout()
            .into_raw_mode()
            .map_err(|e| EditorError::TerminalError(format!("Failed to enter raw mode: {}", e)))?;

        let size = Self::query_size()?;

        Ok(Terminal { stdout, size })
    }

    /// Enter raw mode (disable echo and enable raw input)
    /// Note: This is called automatically in new(), but provided for explicit control
    pub fn enter_raw_mode(&mut self) -> Result<()> {
        // Raw mode is already enabled via IntoRawMode in new()
        // This method exists for API completeness
        Ok(())
    }

    /// Exit raw mode and restore original terminal state
    /// Note: This is called automatically via Drop trait
    pub fn exit_raw_mode(&mut self) -> Result<()> {
        // Raw mode will be exited when RawTerminal is dropped
        // Flush any pending output
        self.flush()?;
        Ok(())
    }

    /// Query terminal size
    fn query_size() -> Result<TerminalSize> {
        let (cols, rows) = termion::terminal_size()
            .map_err(|e| EditorError::TerminalError(format!("Failed to get terminal size: {}", e)))?;

        Ok(TerminalSize { rows, cols })
    }

    /// Get current terminal size
    pub fn get_size(&self) -> TerminalSize {
        self.size
    }

    /// Update terminal size (useful after resize events)
    pub fn update_size(&mut self) -> Result<()> {
        self.size = Self::query_size()?;
        Ok(())
    }

    /// Clear the entire screen
    pub fn clear_screen(&mut self) -> Result<()> {
        write!(self.stdout, "{}", termion::clear::All)?;
        self.flush()?;
        Ok(())
    }

    /// Move cursor to specified position (1-indexed)
    pub fn move_cursor(&mut self, row: u16, col: u16) -> Result<()> {
        write!(self.stdout, "{}", termion::cursor::Goto(col, row))?;
        Ok(())
    }

    /// Hide cursor
    pub fn hide_cursor(&mut self) -> Result<()> {
        write!(self.stdout, "{}", termion::cursor::Hide)?;
        Ok(())
    }

    /// Show cursor
    pub fn show_cursor(&mut self) -> Result<()> {
        write!(self.stdout, "{}", termion::cursor::Show)?;
        // Set cursor to blinking block (default style)
        write!(self.stdout, "\x1b[1 q")?;
        Ok(())
    }

    /// Flush buffered output to the terminal
    pub fn flush(&mut self) -> Result<()> {
        self.stdout.flush()?;
        Ok(())
    }

    /// Write bytes to the terminal
    pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
        Ok(self.stdout.write(buf)?)
    }

    /// Write all bytes to the terminal
    pub fn write_all(&mut self, buf: &[u8]) -> Result<()> {
        self.stdout.write_all(buf)?;
        Ok(())
    }

    /// Write a formatted string to the terminal
    pub fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> Result<()> {
        self.stdout.write_fmt(fmt)?;
        Ok(())
    }

    /// Write a string to the terminal
    pub fn write_str(&mut self, s: &str) -> Result<()> {
        self.write_all(s.as_bytes())?;
        Ok(())
    }

    /// Move cursor to the status line (second to last row)
    pub fn move_cursor_to_status_line(&mut self) -> Result<()> {
        let row = self.size.rows.saturating_sub(1);
        self.move_cursor(row, 1)?;
        Ok(())
    }

    /// Clear the current line
    pub fn clear_current_line(&mut self) -> Result<()> {
        write!(self.stdout, "{}", termion::clear::CurrentLine)?;
        Ok(())
    }

    /// Suspend the editor (send SIGTSTP to self)
    /// This will restore terminal state before suspending
    pub fn suspend(&mut self) -> Result<()> {
        // Exit raw mode to restore terminal state
        self.exit_raw_mode()?;
        
        // Show cursor
        self.show_cursor()?;
        
        // Flush output
        self.flush()?;
        
        // Send SIGTSTP to self
        signal::raise(Signal::SIGTSTP)
            .map_err(|e| EditorError::TerminalError(format!("Failed to suspend: {}", e)))?;
        
        Ok(())
    }

    /// Resume after suspension
    /// This will re-enter raw mode and prepare for editing
    pub fn resume(&mut self) -> Result<()> {
        // Re-enter raw mode
        // Note: Since we're using termion's RawTerminal, we need to handle this carefully
        // The terminal is already in raw mode from the RawTerminal wrapper
        // We just need to ensure the state is correct
        
        // Hide cursor
        self.hide_cursor()?;
        
        // Clear and redraw will be handled by the editor
        Ok(())
    }
}

impl io::Write for Terminal {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.stdout.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.stdout.flush()
    }
}

impl Drop for Terminal {
    /// Ensure terminal state is restored even on panic
    fn drop(&mut self) {
        // Clear screen before exiting
        let _ = write!(self.stdout, "{}", termion::clear::All);
        // Move cursor to top-left
        let _ = write!(self.stdout, "{}", termion::cursor::Goto(1, 1));
        // Show cursor
        let _ = write!(self.stdout, "{}", termion::cursor::Show);
        // Flush any remaining output
        let _ = self.stdout.flush();
        // RawTerminal's Drop implementation will restore terminal state
    }
}