1 use std::{ 2 io, 3 sync::{atomic::AtomicU16, Arc, Mutex, MutexGuard, Once, RwLock, Weak}, 4 }; 5 6 use crossterm::{ 7 style::Color, 8 terminal::{self}, 9 }; 10 use lazy_static::lazy_static; 11 12 use crate::{ 13 config::appconfig::AppSetting, 14 utils::{ 15 buffer::EditBuffer, cursor::CursorCrtl, style::StyleManager, terminal::TermManager, 16 ui::InfoLevel, 17 }, 18 }; 19 20 #[cfg(feature = "dragonos")] 21 use crate::utils::input::Input; 22 23 use super::{ 24 mode::mode::{Command, InputMode, Insert, LastLine, ModeType}, 25 AppInfo, 26 }; 27 28 lazy_static! { 29 static ref COMMAND: Arc<Command> = Arc::new(Command); 30 static ref INSERT: Arc<Insert> = Arc::new(Insert); 31 static ref LASTLINE: Arc<LastLine> = Arc::new(LastLine::new()); 32 pub static ref APP_INFO: Mutex<AppInfo> = Mutex::new(AppInfo { 33 level: InfoLevel::Info, 34 info: String::new() 35 }); 36 } 37 38 pub static TAB_SIZE: AtomicU16 = AtomicU16::new(4); 39 40 #[derive(Debug, Clone, Copy)] 41 pub struct WinSize { 42 pub cols: u16, 43 pub rows: u16, 44 } 45 46 #[derive(Debug)] 47 pub struct UiCore { 48 pub buffer: Arc<EditBuffer>, 49 pub cursor: CursorCrtl, 50 51 #[allow(dead_code)] 52 setting: AppSetting, 53 container: Weak<Ui>, 54 55 edited: bool, 56 edited_once: Once, 57 } 58 59 impl UiCore { 60 pub fn new(buf: Arc<EditBuffer>, cursor: CursorCrtl, setting: AppSetting) -> Self { 61 Self { 62 buffer: buf, 63 cursor, 64 container: Weak::new(), 65 setting, 66 edited: false, 67 edited_once: Once::new(), 68 } 69 } 70 71 pub fn edited(&self) -> bool { 72 self.edited 73 } 74 75 pub fn set_edited(&mut self) { 76 self.edited_once.call_once(|| self.edited = true) 77 } 78 79 pub fn update_bottom_state_bar(&mut self) -> io::Result<()> { 80 let container = self.container.upgrade().unwrap(); 81 let mode = container.mode.read().unwrap().mode_type(); 82 if mode == ModeType::LastLine { 83 return Ok(()); 84 } 85 86 let size = *WINSIZE.read().unwrap(); 87 88 let store_x = self.cursor.x(); 89 let store_y = self.cursor.y(); 90 91 self.cursor.set_prefix_mode(false); 92 93 DEF_STYLE.read().unwrap().set_cmd_style()?; 94 let cmd_y = size.rows - 1; 95 self.cursor.move_to_row(cmd_y)?; 96 self.cursor.clear_current_line()?; 97 self.cursor 98 .write_with_pos(format!("{mode:?}"), 0, cmd_y, false)?; 99 100 let (buf_x, buf_y) = (store_x, store_y + 1 + self.buffer.offset() as u16); 101 let index_info = format!("row:{buf_y} col:{buf_x}"); 102 let len = index_info.len() as u16; 103 self.cursor 104 .write_with_pos(index_info, size.cols - len, cmd_y, false)?; 105 106 self.cursor.set_prefix_mode(true); 107 self.cursor.move_to(store_x, store_y)?; 108 109 let mut info = APP_INFO.lock().unwrap(); 110 info.level.set_style()?; 111 self.cursor 112 .write_with_pos(&info.info, size.cols / 3, cmd_y, false)?; 113 114 info.reset(); 115 self.cursor.move_to(store_x, store_y)?; 116 117 StyleManager::reset_color()?; 118 119 Ok(()) 120 } 121 122 /// 渲染部分文件内容,从y行开始渲染count行 123 /// 返回实际渲染行数 124 pub fn render_content(&mut self, mut y: u16, mut count: usize) -> io::Result<usize> { 125 y += UI_HEAD_OFFSET; 126 let content_winsize = *CONTENT_WINSIZE.read().unwrap(); 127 128 // 超出正文范围 129 if y + count as u16 > content_winsize.rows { 130 count = (content_winsize.rows - y) as usize; 131 } 132 133 let def_style = *DEF_STYLE.read().unwrap(); 134 135 let content = self.buffer.get_content(y as usize, count); 136 137 if content.is_none() { 138 return Ok(0); 139 } 140 let content = content.unwrap(); 141 142 // 保存光标 143 let pos = self.cursor.store_tmp_pos(); 144 145 let tmp = y; 146 147 let num_len = (tmp + content_winsize.rows).to_string().len(); 148 149 self.cursor.set_prefix_mode(false); 150 for line in content.iter() { 151 let str = String::from_utf8_lossy(&line.data).to_string(); 152 def_style.set_content_style()?; 153 154 // 移动 155 self.cursor 156 .move_to(num_len as u16 + 2 + CursorCrtl::PREFIX_COL, y)?; 157 self.cursor.clear_current_line()?; 158 self.cursor.write(str)?; 159 y += 1; 160 StyleManager::reset_color()?; 161 } 162 163 self.cursor.update_line_prefix(&content, tmp, num_len)?; 164 self.cursor.set_prefix_mode(true); 165 166 self.cursor.restore_tmp_pos(pos)?; 167 168 self.cursor.highlight(None)?; 169 170 Ok(content.len()) 171 } 172 173 // 将正文向上滚动count行 174 pub fn scroll_up(&mut self, mut count: u16) -> io::Result<()> { 175 let winsize = *CONTENT_WINSIZE.read().unwrap(); 176 177 let pos = self.cursor.store_tmp_pos(); 178 179 // 计算最多还能滚动多少行 180 let offset = self.buffer.offset(); 181 182 // 最多出两行 183 let linecount = self.buffer.line_count(); 184 if offset + winsize.rows as usize + count as usize >= linecount { 185 count = linecount as u16 - offset as u16 - winsize.rows; 186 } 187 self.buffer.set_offset(offset + count as usize); 188 // 将光标移动到滚动后的位置 189 self.cursor.move_to_row(winsize.rows - count)?; 190 191 // 执行滚动 192 TermManager::scroll_up(count)?; 193 194 // 清除光标以下的内容 195 TermManager::clear_under_cursor()?; 196 197 // 渲染count行数据 198 self.render_content(self.cursor.y(), count as usize)?; 199 200 self.cursor.restore_tmp_pos(pos)?; 201 202 self.cursor.highlight(Some(self.cursor.y() - count))?; 203 Ok(()) 204 } 205 206 pub fn scroll_down(&mut self, mut count: u16) -> io::Result<()> { 207 let pos = self.cursor.store_tmp_pos(); 208 209 // 计算最多还能滚动多少行 210 let offset = self.buffer.offset(); 211 if offset < count as usize { 212 count = offset as u16; 213 } 214 215 self.buffer.set_offset(offset - count as usize); 216 217 // 执行滚动 218 TermManager::scroll_down(count)?; 219 220 // 将光标移动第count行 221 self.cursor.move_to_row(count - 1)?; 222 // 清除光标以上的内容 223 TermManager::clear_up_cursor()?; 224 225 // 渲染count行数据 226 self.render_content(0, count as usize)?; 227 228 self.cursor.restore_tmp_pos(pos)?; 229 230 self.cursor.highlight(Some(self.cursor.y() + count))?; 231 232 Ok(()) 233 } 234 } 235 236 #[derive(Debug)] 237 pub struct Ui { 238 pub core: Arc<Mutex<UiCore>>, 239 pub mode: RwLock<Arc<dyn InputMode>>, 240 } 241 242 lazy_static! { 243 pub static ref WINSIZE: RwLock<WinSize> = { 244 let size = terminal::size().unwrap(); 245 RwLock::new(WinSize { 246 cols: size.0, 247 rows: size.1, 248 }) 249 }; 250 pub static ref CONTENT_WINSIZE: RwLock<WinSize> = { 251 let size = *WINSIZE.read().unwrap(); 252 RwLock::new(WinSize { 253 cols: size.cols, 254 rows: size.rows - UI_CMD_HEIGHT - UI_HEAD_OFFSET, 255 }) 256 }; 257 pub static ref DEF_STYLE: RwLock<UiStyle> = { 258 let style = UiStyle { 259 content_fg: Some(Color::White), 260 content_bg: None, 261 cmd_line_fg: Some(Color::White), 262 cmd_line_bg: Some(Color::DarkCyan), 263 }; 264 265 RwLock::new(style) 266 }; 267 } 268 269 pub static UI_HEAD_OFFSET: u16 = 0; 270 pub const UI_CMD_HEIGHT: u16 = 1; 271 272 impl Ui { 273 pub fn new(buf: Arc<EditBuffer>, setting: AppSetting) -> Arc<Self> { 274 let mut cursor = CursorCrtl::new(buf.clone(), setting.line); 275 cursor.move_to(0, 0).unwrap(); 276 277 let core = Arc::new(Mutex::new(UiCore::new(buf, cursor, setting))); 278 let ret = Arc::new(Self { 279 mode: RwLock::new(Arc::new(Command)), 280 core: core.clone(), 281 }); 282 283 core.lock().unwrap().container = Arc::downgrade(&ret); 284 285 ret 286 } 287 pub fn init_ui() -> io::Result<()> { 288 TermManager::init_term()?; 289 Ok(()) 290 } 291 292 pub fn start_page_ui(&self) -> io::Result<()> { 293 StyleManager::set_foreground_color(Color::Cyan)?; 294 let mut core = self.core.lock().unwrap(); 295 core.cursor 296 .write_with_pos("Held - DragonOS/Linux Term Editor\n", 5, 0, false)?; 297 StyleManager::set_foreground_color(Color::Green)?; 298 core.cursor 299 .write_with_pos("Author: heyicong@dragonos.org\n", 7, 1, false)?; 300 StyleManager::set_foreground_color(Color::DarkMagenta)?; 301 core.cursor 302 .write_with_pos("Type any key to continue ><\n", 8, 2, false)?; 303 StyleManager::reset_color()?; 304 305 core.cursor.move_to(0, 0)?; 306 307 #[cfg(feature = "dragonos")] 308 let _ = Input::wait_keydown(); 309 310 #[cfg(not(feature = "dragonos"))] 311 loop { 312 let ev = crossterm::event::read()?; 313 if let crossterm::event::Event::Key(_) = ev { 314 break; 315 } 316 } 317 318 TermManager::clear_all()?; 319 320 Ok(()) 321 } 322 323 pub fn ui_loop(&self) -> io::Result<bool> { 324 let mut core = self.core.lock().unwrap(); 325 core.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; 326 core.update_bottom_state_bar()?; 327 core.cursor.move_to(0, 0)?; 328 core.cursor.highlight(None)?; 329 loop { 330 #[cfg(feature = "dragonos")] 331 let callback = { 332 let key = Input::wait_keydown()?; 333 self.mode.read().unwrap().key_event_route(&mut core, key)? 334 }; 335 336 #[cfg(not(feature = "dragonos"))] 337 let callback = { 338 let ev = crossterm::event::read()?; 339 self.mode.read().unwrap().event_route(&mut core, ev)? 340 }; 341 342 match callback { 343 super::event::WarpUiCallBackType::ChangMode(mode) => { 344 self.set_mode(mode, &mut core)? 345 } 346 super::event::WarpUiCallBackType::None => {} 347 super::event::WarpUiCallBackType::Exit(store) => { 348 self.ui_exit(); 349 return Ok(store); 350 } 351 } 352 353 if self.mode.read().unwrap().mode_type() != ModeType::LastLine { 354 core.update_bottom_state_bar()?; 355 } 356 } 357 } 358 359 fn set_mode(&self, mode: ModeType, ui: &mut MutexGuard<UiCore>) -> io::Result<()> { 360 if mode != ModeType::LastLine { 361 ui.cursor.set_prefix_mode(true); 362 363 ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; 364 } 365 match mode { 366 ModeType::Command => *self.mode.write().unwrap() = COMMAND.clone(), 367 ModeType::LastLine => { 368 ui.cursor.set_prefix_mode(false); 369 let lastline = LASTLINE.clone(); 370 lastline.reset(); 371 *self.mode.write().unwrap() = lastline; 372 373 ui.cursor.move_to(0, u16::MAX - 1)?; 374 DEF_STYLE.read().unwrap().set_cmd_style()?; 375 // 写一个空行 376 ui.cursor.clear_current_line()?; 377 ui.cursor.move_to_columu(0)?; 378 ui.cursor.write(':')?; 379 } 380 ModeType::Insert => *self.mode.write().unwrap() = INSERT.clone(), 381 } 382 383 Ok(()) 384 } 385 386 fn ui_exit(&self) { 387 // 处理未保存退出时的提醒 388 389 // 解决退出程序后颜色没改变的问题 390 StyleManager::reset_color().unwrap(); 391 } 392 } 393 394 #[derive(Debug, Clone, Copy)] 395 pub struct UiStyle { 396 pub content_fg: Option<Color>, 397 pub content_bg: Option<Color>, 398 pub cmd_line_fg: Option<Color>, 399 pub cmd_line_bg: Option<Color>, 400 } 401 402 impl UiStyle { 403 pub fn set_cmd_style(&self) -> io::Result<()> { 404 StyleManager::reset_color()?; 405 if self.cmd_line_bg.is_some() { 406 StyleManager::set_background_color(self.cmd_line_bg.unwrap())?; 407 } 408 if self.cmd_line_fg.is_some() { 409 StyleManager::set_foreground_color(self.cmd_line_fg.unwrap())?; 410 } 411 412 Ok(()) 413 } 414 415 pub fn set_content_style(&self) -> io::Result<()> { 416 StyleManager::reset_color()?; 417 if self.content_bg.is_some() { 418 StyleManager::set_background_color(self.content_bg.unwrap())?; 419 } 420 if self.content_fg.is_some() { 421 StyleManager::set_foreground_color(self.content_fg.unwrap())?; 422 } 423 424 Ok(()) 425 } 426 } 427