1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
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
}
}