cnctd-service-ssh 0.1.8

SSH command execution service - library and MCP server
Documentation
//! Terminal emulation using vt100 for screen state tracking.

use crate::sessions::types::ScreenState;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Terminal emulator wrapper around vt100::Parser
pub struct TerminalEmulator {
    parser: Arc<RwLock<vt100::Parser>>,
    cols: u16,
    rows: u16,
}

impl TerminalEmulator {
    /// Create a new terminal emulator with the given dimensions
    pub fn new(cols: u16, rows: u16) -> Self {
        let parser = vt100::Parser::new(rows, cols, 1000); // 1000 lines scrollback
        Self {
            parser: Arc::new(RwLock::new(parser)),
            cols,
            rows,
        }
    }

    /// Process output bytes through the terminal emulator
    pub async fn process(&self, data: &[u8]) {
        let mut parser = self.parser.write().await;
        parser.process(data);
    }

    /// Get the current screen state
    pub async fn screen_state(&self) -> ScreenState {
        let parser = self.parser.read().await;
        let screen = parser.screen();

        // Extract visible lines
        let mut lines = Vec::with_capacity(self.rows as usize);
        for row in 0..self.rows {
            let row_text = screen.contents_between(
                row,
                0,
                row,
                self.cols,
            );
            // Trim trailing whitespace but preserve leading
            lines.push(row_text.trim_end().to_string());
        }

        // Get cursor position
        let cursor = (screen.cursor_position().0, screen.cursor_position().1);

        // Check for alternate screen (used by vim, htop, etc.)
        let alternate_screen = screen.alternate_screen();

        // Get title if set
        let title_str = screen.title();
        let title = if title_str.is_empty() { None } else { Some(title_str.to_string()) };

        ScreenState {
            lines,
            cursor,
            size: (self.cols, self.rows),
            alternate_screen,
            title,
        }
    }

    /// Resize the terminal
    pub async fn resize(&mut self, cols: u16, rows: u16) {
        self.cols = cols;
        self.rows = rows;
        let mut parser = self.parser.write().await;
        parser.set_size(rows, cols);
    }

    /// Get current dimensions
    pub fn size(&self) -> (u16, u16) {
        (self.cols, self.rows)
    }
}