1use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use tokio::sync::RwLock;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct TerminalState {
12 pub id: String,
13 pub rows: u16,
14 pub cols: u16,
15 pub cursor: CursorPosition,
16 pub mode: TerminalMode,
17 pub title: String,
18 pub working_dir: Option<String>,
19 pub process_info: Option<ProcessInfo>,
20 pub active: bool,
21 pub last_activity: chrono::DateTime<chrono::Utc>,
22}
23
24#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
25pub struct CursorPosition {
26 pub x: u16,
27 pub y: u16,
28 pub visible: bool,
29 pub blinking: bool,
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
33#[serde(rename_all = "snake_case")]
34pub enum TerminalMode {
35 Normal,
36 Insert,
37 Visual,
38 Command,
39 Search,
40 Raw,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ProcessInfo {
45 pub pid: u32,
46 pub name: String,
47 pub command: String,
48 pub cpu_usage: f32,
49 pub memory_usage: u64,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct TerminalSelection {
54 pub start: CursorPosition,
55 pub end: CursorPosition,
56 pub mode: SelectionMode,
57}
58
59#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum SelectionMode {
62 Character,
63 Word,
64 Line,
65 Block,
66}
67
68impl TerminalState {
69 pub fn new(id: String, rows: u16, cols: u16) -> Self {
70 Self {
71 id,
72 rows,
73 cols,
74 cursor: CursorPosition::default(),
75 mode: TerminalMode::Normal,
76 title: "Terminal".to_string(),
77 working_dir: None,
78 process_info: None,
79 active: true,
80 last_activity: chrono::Utc::now(),
81 }
82 }
83
84 pub fn set_cursor(&mut self, x: u16, y: u16) {
86 self.cursor.x = x.min(self.cols - 1);
87 self.cursor.y = y.min(self.rows - 1);
88 self.update_activity();
89 }
90
91 pub fn move_cursor(&mut self, dx: i16, dy: i16) {
93 let new_x = (self.cursor.x as i16 + dx).max(0).min(self.cols as i16 - 1) as u16;
94 let new_y = (self.cursor.y as i16 + dy).max(0).min(self.rows as i16 - 1) as u16;
95 self.set_cursor(new_x, new_y);
96 }
97
98 pub fn resize(&mut self, rows: u16, cols: u16) {
100 self.rows = rows;
101 self.cols = cols;
102 if self.cursor.x >= cols {
104 self.cursor.x = cols - 1;
105 }
106 if self.cursor.y >= rows {
107 self.cursor.y = rows - 1;
108 }
109 self.update_activity();
110 }
111
112 pub fn set_mode(&mut self, mode: TerminalMode) {
114 self.mode = mode;
115 self.update_activity();
116 }
117
118 pub fn update_activity(&mut self) {
120 self.last_activity = chrono::Utc::now();
121 }
122
123 pub fn set_title(&mut self, title: String) {
125 self.title = title;
126 }
127
128 pub fn update_process_info(&mut self, info: ProcessInfo) {
130 self.process_info = Some(info);
131 self.update_activity();
132 }
133}
134
135pub struct TerminalStateManager {
137 states: Arc<RwLock<std::collections::HashMap<String, TerminalState>>>,
138}
139
140impl Default for TerminalStateManager {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl TerminalStateManager {
147 pub fn new() -> Self {
148 Self {
149 states: Arc::new(RwLock::new(std::collections::HashMap::new())),
150 }
151 }
152
153 pub async fn add_terminal(&self, state: TerminalState) {
155 self.states.write().await.insert(state.id.clone(), state);
156 }
157
158 pub async fn get_terminal(&self, id: &str) -> Option<TerminalState> {
160 self.states.read().await.get(id).cloned()
161 }
162
163 pub async fn update_terminal<F>(&self, id: &str, updater: F) -> Result<(), String>
165 where
166 F: FnOnce(&mut TerminalState),
167 {
168 let mut states = self.states.write().await;
169 if let Some(state) = states.get_mut(id) {
170 updater(state);
171 Ok(())
172 } else {
173 Err(format!("Terminal {id} not found"))
174 }
175 }
176
177 pub async fn remove_terminal(&self, id: &str) -> Option<TerminalState> {
179 self.states.write().await.remove(id)
180 }
181
182 pub async fn get_all_terminals(&self) -> Vec<TerminalState> {
184 self.states.read().await.values().cloned().collect()
185 }
186
187 pub async fn active_count(&self) -> usize {
189 self.states
190 .read()
191 .await
192 .values()
193 .filter(|s| s.active)
194 .count()
195 }
196}
197
198impl Default for TerminalMode {
199 fn default() -> Self {
200 Self::Normal
201 }
202}