1use crate::styled_content::{StyledSegment, extract_styled_segments};
2use crate::themes::Theme;
3use anyhow::Result;
4use par_term_emu_core_rust::pty_session::PtySession;
5use par_term_emu_core_rust::terminal::Terminal;
6use parking_lot::Mutex;
7use std::sync::Arc;
8
9pub use par_term_emu_core_rust::terminal::{ClipboardEntry, ClipboardSlot};
11
12#[allow(dead_code)]
14fn ansi_to_rgb(color_idx: u8) -> [u8; 3] {
15 match color_idx {
16 0 => [0, 0, 0], 1 => [205, 0, 0], 2 => [0, 205, 0], 3 => [205, 205, 0], 4 => [0, 0, 238], 5 => [205, 0, 205], 6 => [0, 205, 205], 7 => [229, 229, 229], 8 => [127, 127, 127], 9 => [255, 0, 0], 10 => [0, 255, 0], 11 => [255, 255, 0], 12 => [92, 92, 255], 13 => [255, 0, 255], 14 => [0, 255, 255], 15 => [255, 255, 255], 16..=231 => {
35 let idx = color_idx - 16;
36 let r = (idx / 36) * 51;
37 let g = ((idx % 36) / 6) * 51;
38 let b = (idx % 6) * 51;
39 [r, g, b]
40 }
41 232..=255 => {
43 let gray = 8 + (color_idx - 232) * 10;
44 [gray, gray, gray]
45 }
46 }
47}
48
49pub mod clipboard;
50pub mod graphics;
51pub mod hyperlinks;
52pub mod rendering;
53pub mod spawn;
54
55pub struct TerminalManager {
57 pub(crate) pty_session: Arc<Mutex<PtySession>>,
59 pub(crate) dimensions: (usize, usize),
61 pub(crate) theme: Theme,
63}
64
65impl TerminalManager {
66 #[allow(dead_code)]
68 pub fn new(cols: usize, rows: usize) -> Result<Self> {
69 Self::new_with_scrollback(cols, rows, 10000)
70 }
71
72 pub fn new_with_scrollback(cols: usize, rows: usize, scrollback_size: usize) -> Result<Self> {
74 log::info!(
75 "Creating terminal with dimensions: {}x{}, scrollback: {}",
76 cols,
77 rows,
78 scrollback_size
79 );
80
81 let pty_session = PtySession::new(cols, rows, scrollback_size);
82 let pty_session = Arc::new(Mutex::new(pty_session));
83
84 Ok(Self {
85 pty_session,
86 dimensions: (cols, rows),
87 theme: Theme::default(),
88 })
89 }
90
91 pub fn set_theme(&mut self, theme: Theme) {
93 self.theme = theme;
94 }
95
96 pub fn set_cell_dimensions(&self, width: u32, height: u32) {
101 let pty = self.pty_session.lock();
102 let terminal = pty.terminal();
103 let mut term = terminal.lock();
104 term.set_cell_dimensions(width, height);
105 }
106
107 pub fn write(&self, data: &[u8]) -> Result<()> {
109 if !data.is_empty() {
111 log::debug!(
112 "Writing to PTY: {:?} (bytes: {:?})",
113 String::from_utf8_lossy(data),
114 data
115 );
116 }
117 let mut pty = self.pty_session.lock();
118 pty.write(data)
119 .map_err(|e| anyhow::anyhow!("Failed to write to PTY: {}", e))?;
120 Ok(())
121 }
122
123 #[allow(dead_code)]
125 pub fn write_str(&self, data: &str) -> Result<()> {
126 let mut pty = self.pty_session.lock();
127 pty.write_str(data)
128 .map_err(|e| anyhow::anyhow!("Failed to write to PTY: {}", e))?;
129 Ok(())
130 }
131
132 #[allow(dead_code)]
134 pub fn content(&self) -> Result<String> {
135 let pty = self.pty_session.lock();
136 Ok(pty.content())
137 }
138
139 #[allow(dead_code)]
141 pub fn resize(&mut self, cols: usize, rows: usize) -> Result<()> {
142 log::info!("Resizing terminal to: {}x{}", cols, rows);
143
144 let mut pty = self.pty_session.lock();
145 pty.resize(cols as u16, rows as u16)
146 .map_err(|e| anyhow::anyhow!("Failed to resize PTY: {}", e))?;
147
148 self.dimensions = (cols, rows);
149 Ok(())
150 }
151
152 pub fn resize_with_pixels(
156 &mut self,
157 cols: usize,
158 rows: usize,
159 width_px: usize,
160 height_px: usize,
161 ) -> Result<()> {
162 log::info!(
163 "Resizing terminal to: {}x{} ({}x{} pixels)",
164 cols,
165 rows,
166 width_px,
167 height_px
168 );
169
170 let mut pty = self.pty_session.lock();
171 pty.resize_with_pixels(cols as u16, rows as u16, width_px as u16, height_px as u16)
172 .map_err(|e| anyhow::anyhow!("Failed to resize PTY with pixels: {}", e))?;
173
174 self.dimensions = (cols, rows);
175 Ok(())
176 }
177
178 #[allow(dead_code)]
180 pub fn set_pixel_size(&mut self, width_px: usize, height_px: usize) -> Result<()> {
181 let pty = self.pty_session.lock();
182 let term_arc = pty.terminal();
183 let mut term = term_arc.lock();
184 term.set_pixel_size(width_px, height_px);
185 Ok(())
186 }
187
188 #[allow(dead_code)]
190 pub fn dimensions(&self) -> (usize, usize) {
191 self.dimensions
192 }
193
194 #[allow(dead_code)]
196 pub fn terminal(&self) -> Arc<Mutex<Terminal>> {
197 let pty = self.pty_session.lock();
198 pty.terminal()
199 }
200
201 #[allow(dead_code)]
203 pub fn has_updates(&self) -> bool {
204 true
207 }
208
209 pub fn is_running(&self) -> bool {
211 let pty = self.pty_session.lock();
212 pty.is_running()
213 }
214
215 pub fn kill(&mut self) -> Result<()> {
217 let mut pty = self.pty_session.lock();
218 pty.kill()
219 .map_err(|e| anyhow::anyhow!("Failed to kill PTY: {:?}", e))
220 }
221
222 pub fn bell_count(&self) -> u64 {
224 let pty = self.pty_session.lock();
225 pty.bell_count()
226 }
227
228 #[allow(dead_code)]
230 pub fn scrollback(&self) -> Vec<String> {
231 let pty = self.pty_session.lock();
232 pty.scrollback()
233 }
234
235 pub fn scrollback_len(&self) -> usize {
237 let pty = self.pty_session.lock();
238 pty.scrollback_len()
239 }
240
241 pub fn clear_scrollback(&self) {
246 let pty = self.pty_session.lock();
247 let terminal = pty.terminal();
248 let mut term = terminal.lock();
249 term.process(b"\x1b[3J");
251 }
252
253 pub fn take_notifications(&self) -> Vec<par_term_emu_core_rust::terminal::Notification> {
255 let pty = self.pty_session.lock();
256 let terminal = pty.terminal();
257 let mut term = terminal.lock();
258 term.take_notifications()
259 }
260
261 pub fn has_notifications(&self) -> bool {
263 let pty = self.pty_session.lock();
264 let terminal = pty.terminal();
265 let term = terminal.lock();
266 term.has_notifications()
267 }
268
269 #[allow(dead_code)]
276 pub fn screenshot_to_file(
277 &self,
278 path: &std::path::Path,
279 format: &str,
280 scrollback_lines: usize,
281 ) -> Result<()> {
282 use par_term_emu_core_rust::screenshot::{ImageFormat, ScreenshotConfig};
283
284 log::info!(
285 "Taking screenshot to: {} (format: {}, scrollback: {})",
286 path.display(),
287 format,
288 scrollback_lines
289 );
290
291 let pty = self.pty_session.lock();
292 let terminal = pty.terminal();
293 let term = terminal.lock();
294
295 let image_format = match format.to_lowercase().as_str() {
297 "png" => ImageFormat::Png,
298 "jpeg" | "jpg" => ImageFormat::Jpeg,
299 "svg" => ImageFormat::Svg,
300 _ => {
301 log::warn!("Unknown format '{}', defaulting to PNG", format);
302 ImageFormat::Png
303 }
304 };
305
306 let config = ScreenshotConfig {
308 format: image_format,
309 ..Default::default()
310 };
311
312 term.screenshot_to_file(path, config, scrollback_lines)
314 .map_err(|e| anyhow::anyhow!("Failed to save screenshot: {}", e))?;
315
316 log::info!("Screenshot saved successfully");
317 Ok(())
318 }
319
320 pub fn shell_integration_cwd(&self) -> Option<String> {
384 let pty = self.pty_session.lock();
385 let terminal = pty.terminal();
386 let term = terminal.lock();
387 term.shell_integration().cwd().map(String::from)
388 }
389
390 pub fn shell_integration_exit_code(&self) -> Option<i32> {
392 let pty = self.pty_session.lock();
393 let terminal = pty.terminal();
394 let term = terminal.lock();
395 term.shell_integration().exit_code()
396 }
397
398 #[allow(dead_code)]
400 pub fn shell_integration_command(&self) -> Option<String> {
401 let pty = self.pty_session.lock();
402 let terminal = pty.terminal();
403 let term = terminal.lock();
404 term.shell_integration().command().map(String::from)
405 }
406
407 #[allow(dead_code)]
422 pub fn cursor_position(&self) -> (usize, usize) {
423 let pty = self.pty_session.lock();
424 pty.cursor_position()
425 }
426
427 pub fn cursor_style(&self) -> par_term_emu_core_rust::cursor::CursorStyle {
429 let pty = self.pty_session.lock();
430 let terminal = pty.terminal();
431 let term = terminal.lock();
432 term.cursor().style()
433 }
434
435 pub fn set_cursor_style(&mut self, style: par_term_emu_core_rust::cursor::CursorStyle) {
437 let pty = self.pty_session.lock();
438 let terminal = pty.terminal();
439 let mut term = terminal.lock();
440 term.set_cursor_style(style);
441 }
442
443 pub fn is_cursor_visible(&self) -> bool {
448 let pty = self.pty_session.lock();
449 let terminal = pty.terminal();
450 let term = terminal.lock();
451 term.cursor().visible
452 }
453
454 pub fn is_mouse_tracking_enabled(&self) -> bool {
456 let pty = self.pty_session.lock();
457 let terminal = pty.terminal();
458 let term = terminal.lock();
459 !matches!(
460 term.mouse_mode(),
461 par_term_emu_core_rust::mouse::MouseMode::Off
462 )
463 }
464
465 pub fn is_alt_screen_active(&self) -> bool {
471 let pty = self.pty_session.lock();
472 let terminal = pty.terminal();
473 let term = terminal.lock();
474 term.is_alt_screen_active()
475 }
476
477 pub fn get_title(&self) -> String {
482 let pty = self.pty_session.lock();
483 let terminal = pty.terminal();
484 let term = terminal.lock();
485 term.title().to_string()
486 }
487
488 pub fn should_report_mouse_motion(&self, button_pressed: bool) -> bool {
491 let pty = self.pty_session.lock();
492 let terminal = pty.terminal();
493 let term = terminal.lock();
494
495 match term.mouse_mode() {
496 par_term_emu_core_rust::mouse::MouseMode::AnyEvent => true,
497 par_term_emu_core_rust::mouse::MouseMode::ButtonEvent => button_pressed,
498 _ => false,
499 }
500 }
501
502 pub fn encode_mouse_event(
514 &self,
515 button: u8,
516 col: usize,
517 row: usize,
518 pressed: bool,
519 modifiers: u8,
520 ) -> Vec<u8> {
521 let pty = self.pty_session.lock();
522 let terminal = pty.terminal();
523 let mut term = terminal.lock();
524
525 let mouse_event =
526 par_term_emu_core_rust::mouse::MouseEvent::new(button, col, row, pressed, modifiers);
527 term.report_mouse(mouse_event)
528 }
529
530 #[allow(dead_code)]
532 pub fn get_styled_segments(&self) -> Vec<StyledSegment> {
533 let pty = self.pty_session.lock();
534 let terminal = pty.terminal();
535 let term = terminal.lock();
536 let grid = term.active_grid();
537 extract_styled_segments(grid)
538 }
539
540 pub fn update_generation(&self) -> u64 {
545 let pty = self.pty_session.lock();
546 pty.update_generation()
547 }
548}
549
550impl TerminalManager {}
555
556impl Drop for TerminalManager {
557 fn drop(&mut self) {
558 log::info!("Shutting down terminal manager");
559
560 if let Some(mut pty) = self.pty_session.try_lock() {
562 if pty.is_running() {
564 log::info!("Killing PTY process during shutdown");
565 if let Err(e) = pty.kill() {
566 log::warn!("Failed to kill PTY process: {:?}", e);
567 }
568 }
569 } else {
570 log::warn!("Could not acquire PTY lock during terminal manager shutdown");
571 }
572
573 log::info!("Terminal manager shutdown complete");
574 }
575}