use crate::line_editor::{LineEditAction, LineEditor};
use crate::native_terminal::{self, KeyEvent, NativeTerminal};
pub struct TerminalIO {
cursor_col: u16,
cursor_row: u16,
cols: u16,
rows: u16,
is_error: bool,
editor: LineEditor,
terminal: NativeTerminal,
}
impl TerminalIO {
pub fn new(history_file_path: &str) -> TerminalIO {
let terminal = NativeTerminal::new().expect("Failed to initialize terminal");
TerminalIO {
cursor_col: 0,
cursor_row: 0,
cols: 0,
rows: 0,
is_error: false,
editor: LineEditor::new(history_file_path),
terminal,
}
}
pub fn init(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let (cols, rows) = self.terminal.size();
self.cols = cols;
self.rows = rows;
self.terminal.clear_screen();
if self.rows > 1 {
self.terminal.set_scroll_region(0, self.rows - 2);
}
self.terminal.move_to(0, 0);
Ok(())
}
pub fn cleanup(&mut self) {
self.terminal.cleanup();
}
pub fn handle_key_event(
&mut self,
key_event: KeyEvent,
send_command: impl FnOnce(String),
) -> bool {
match self.editor.handle_key(&key_event) {
LineEditAction::Exit => return false,
LineEditAction::Submit(command) => {
send_command(command);
self.print("", false);
}
LineEditAction::Updated => {
self.print("", false);
}
LineEditAction::None => {}
}
true
}
pub fn poll_event(&mut self, timeout: std::time::Duration) -> bool {
self.terminal.poll_event(timeout)
}
pub fn read_event(&mut self) -> Option<native_terminal::TermEvent> {
self.terminal.read_event()
}
pub fn handle_resize(&mut self, cols: u16, rows: u16) {
self.cols = cols;
self.rows = rows;
if self.cursor_row >= rows.saturating_sub(1) {
self.cursor_row = rows.saturating_sub(2);
}
if self.rows > 1 {
self.terminal.set_scroll_region(0, self.rows - 2);
}
self.draw_prompt();
}
pub fn print(&mut self, data: &str, force_show: bool) {
if !force_show && self.is_error {
return;
}
self.is_error = false;
self.terminal.hide_cursor();
self.terminal.move_to(0, self.rows - 1);
self.terminal.clear_line();
self.terminal.move_to(self.cursor_col, self.cursor_row);
if self.rows > 1 {
self.terminal.set_scroll_region(0, self.rows - 2);
}
self.terminal.move_to(self.cursor_col, self.cursor_row);
if !data.is_empty() {
self.display_received_data(data);
}
self.update_cursor_after_print(data);
self.draw_prompt();
}
fn update_cursor_after_print(&mut self, data: &str) {
let max_row = self.rows.saturating_sub(2);
for ch in data.chars() {
match ch {
'\n' => {
self.cursor_col = 0;
if self.cursor_row < max_row {
self.cursor_row += 1;
}
}
'\r' => {
self.cursor_col = 0;
}
c if !c.is_control() => {
self.cursor_col += 1;
if self.cursor_col >= self.cols {
self.cursor_col = 0;
if self.cursor_row < max_row {
self.cursor_row += 1;
}
}
}
_ => {}
}
}
}
fn draw_prompt(&mut self) {
self.terminal.reset_scroll_region();
self.terminal.move_to(0, self.rows - 1);
self.terminal.clear_line();
self.terminal.set_color_yellow();
let buf = self.editor.buffer_str();
self.terminal.write_str(&format!("> {}", buf));
self.terminal.reset_color();
if self.rows > 1 {
self.terminal.set_scroll_region(0, self.rows - 2);
}
let cursor_col = 2 + self.editor.cursor_pos() as u16;
self.terminal.move_to(cursor_col, self.rows - 1);
self.terminal.show_cursor();
self.terminal.flush();
}
pub fn show_error(&mut self, error_msg: &str) {
self.terminal.reset_scroll_region();
self.terminal.move_to(0, self.rows - 1);
self.terminal.clear_line();
self.terminal.set_color_red();
self.terminal.write_str(&format!("! {}", error_msg));
self.terminal.reset_color();
self.terminal.flush();
if self.rows > 1 {
self.terminal.set_scroll_region(0, self.rows - 2);
}
self.is_error = true;
}
pub fn show_info(&mut self, info_msg: &str) {
self.terminal.reset_scroll_region();
self.terminal.move_to(0, self.rows - 1);
self.terminal.clear_line();
self.terminal.set_color_green();
self.terminal.write_str(&format!("> {}", info_msg));
self.terminal.reset_color();
self.terminal.flush();
if self.rows > 1 {
self.terminal.set_scroll_region(0, self.rows - 2);
}
}
pub fn clear_info(&mut self) {
self.print("", true);
}
pub fn display_received_data(&mut self, data: &str) {
self.terminal.write_str(data);
}
}