From 984a200e159c143eb730a2df362731fe1a62fe01 Mon Sep 17 00:00:00 2001 From: GnoCiYeH Date: Mon, 11 Mar 2024 03:18:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E7=89=88=E6=9C=AC=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=AE=80=E5=8D=95=E7=9A=84=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=EF=BC=8C=E6=94=AF=E6=8C=81=E7=AE=80=E5=8D=95?= =?UTF-8?q?=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 8 + Cargo.toml | 28 +++ Makefile | 62 +++++ config.yaml | 9 + src/app.rs | 65 +++++ src/config/appconfig.rs | 163 +++++++++++++ src/config/cmd.rs | 16 ++ src/config/lastline_cmd.rs | 304 ++++++++++++++++++++++++ src/config/mod.rs | 3 + src/main.rs | 30 +++ src/utils/buffer.rs | 380 +++++++++++++++++++++++++++++ src/utils/cursor.rs | 475 +++++++++++++++++++++++++++++++++++++ src/utils/file.rs | 86 +++++++ src/utils/input.rs | 71 ++++++ src/utils/log_util.rs | 27 +++ src/utils/mod.rs | 11 + src/utils/style.rs | 54 +++++ src/utils/term_io.rs | 15 ++ src/utils/terminal.rs | 87 +++++++ src/utils/ui/event.rs | 171 +++++++++++++ src/utils/ui/mod.rs | 46 ++++ src/utils/ui/mode/mod.rs | 1 + src/utils/ui/mode/mode.rs | 471 ++++++++++++++++++++++++++++++++++++ src/utils/ui/uicore.rs | 423 +++++++++++++++++++++++++++++++++ 24 files changed, 3006 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 config.yaml create mode 100644 src/app.rs create mode 100644 src/config/appconfig.rs create mode 100644 src/config/cmd.rs create mode 100644 src/config/lastline_cmd.rs create mode 100644 src/config/mod.rs create mode 100644 src/main.rs create mode 100644 src/utils/buffer.rs create mode 100644 src/utils/cursor.rs create mode 100644 src/utils/file.rs create mode 100644 src/utils/input.rs create mode 100644 src/utils/log_util.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/style.rs create mode 100644 src/utils/term_io.rs create mode 100644 src/utils/terminal.rs create mode 100644 src/utils/ui/event.rs create mode 100644 src/utils/ui/mod.rs create mode 100644 src/utils/ui/mode/mod.rs create mode 100644 src/utils/ui/mode/mode.rs create mode 100644 src/utils/ui/uicore.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd4772a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target +*.log +*.txt +*.heldbak +*.tmp +Cargo.lock +install +.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4479d9e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "held" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +dragonos = [] + +[dependencies] +# 控制term +crossterm = "0.27" +lazy_static = "1.4" + +# 命令解析 +clap = { version = "4.4.7",features = ["derive"] } + +# 日志 +simplelog = "^0.12.1" +log = "0.4" + +# 解析配置文件 +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.9" + +# 定义标志位 +bitflags = "2.4.2" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9eb7ccd --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +# TOOLCHAIN="+nightly-2023-08-15-x86_64-unknown-linux-gnu" +# RUSTFLAGS+="-C target-feature=+crt-static -C link-arg=-no-pie" + +ifdef DADK_CURRENT_BUILD_DIR +# 如果是在dadk中编译,那么安装到dadk的安装目录中 + INSTALL_DIR = $(DADK_CURRENT_BUILD_DIR) +else +# 如果是在本地编译,那么安装到当前目录下的install目录中 + INSTALL_DIR = ./install +endif + +ifeq ($(ARCH), x86_64) + export RUST_TARGET=x86_64-unknown-linux-musl +else ifeq ($(ARCH), riscv64) + export RUST_TARGET=riscv64gc-unknown-linux-gnu +else +# 默认为x86_86,用于本地编译 + export RUST_TARGET=x86_64-unknown-linux-musl +endif + +run: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) run --target $(RUST_TARGET) + +build-linux: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET) + +build-dragonos: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET) --features dragonos + +clean: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) clean --target $(RUST_TARGET) + +test: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) test --target $(RUST_TARGET) + +doc: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) doc --target $(RUST_TARGET) + +fmt: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) fmt + +fmt-check: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) fmt --check + +run-release: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) run --target $(RUST_TARGET) --release + +build-release: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) build --target $(RUST_TARGET) --release + +clean-release: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) clean --target $(RUST_TARGET) --release + +test-release: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) test --target $(RUST_TARGET) --release + +.PHONY: install-dragonos +install-dragonos: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) install --target $(RUST_TARGET) --features dragonos --path . --no-track --root $(INSTALL_DIR) --force + +install-linux: + RUSTFLAGS=$(RUSTFLAGS) cargo $(TOOLCHAIN) install --target $(RUST_TARGET) --path . --no-track --root $(INSTALL_DIR) --force diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..e098041 --- /dev/null +++ b/config.yaml @@ -0,0 +1,9 @@ +line: + number: + enable: + true + highlight: + enable: + true + color: + 0x00ffff diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..466e998 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,65 @@ +use std::{io, sync::Arc}; + +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; + +use crate::{ + config::appconfig::AppSetting, + utils::{file::FileManager, ui::uicore::Ui}, +}; + +pub struct Application { + file_manager: FileManager, + bak: bool, + ui: Arc, +} + +impl Application { + pub fn new(file_path: Option, setting: AppSetting) -> io::Result { + let bak; + let mut file = if file_path.is_some() { + bak = true; + FileManager::new(file_path.unwrap())? + } else { + bak = false; + FileManager::new("held.tmp".to_string())? + }; + + // 将文件数据读入buf + let buf = file.init(bak)?; + + Ok(Self { + file_manager: file, + bak, + ui: Ui::new(Arc::new(buf), setting), + }) + } + + fn init(&mut self) -> io::Result<()> { + Ui::init_ui()?; + + if !self.bak { + self.ui.start_page_ui()?; + } + + Ok(()) + } + + pub fn run(&mut self) -> io::Result<()> { + enable_raw_mode()?; + self.init()?; + match self.ui.ui_loop() { + Ok(store) => { + if store { + let buffer = &self.ui.core.lock().unwrap().buffer; + self.file_manager.store(buffer)? + } + } + Err(_) => { + // 补救措施:恢复备份文件 + todo!() + } + } + disable_raw_mode()?; + Ok(()) + } +} diff --git a/src/config/appconfig.rs b/src/config/appconfig.rs new file mode 100644 index 0000000..a0869c6 --- /dev/null +++ b/src/config/appconfig.rs @@ -0,0 +1,163 @@ +use crossterm::style::Color; + +#[derive(Debug, serde::Deserialize, Default)] +pub struct DeserializeAppOption { + pub line: Option, +} + +#[derive(Debug)] +pub struct AppSetting { + pub line: LineSetting, +} + +#[derive(Debug, serde::Deserialize, Clone, Copy)] +pub struct DeserializeLineOption { + pub number: Option, + pub highlight: Option, +} + +#[derive(Debug, Clone, Copy)] +pub struct LineSetting { + pub line_num: LineNumberSetting, + pub highlight: HighLightSetting, + + pub prefix_width: usize, +} + +#[derive(Debug, serde::Deserialize, Clone, Copy)] +pub struct DeserializeLineNumber { + pub enable: bool, + pub background: Option, + pub frontground: Option, +} + +impl DeserializeLineNumber { + pub fn to_line_number_setting(setting: Option) -> LineNumberSetting { + let mut ret = LineNumberSetting::default(); + if setting.is_none() { + return ret; + } else { + let setting = setting.unwrap(); + if setting.background.is_some() { + let color = setting.background.unwrap(); + let r = (color & 0xff0000) >> 16; + let g = (color & 0x00ff00) >> 8; + let b = color & 0x0000ff; + + ret.background = Color::Rgb { + r: r as u8, + g: g as u8, + b: b as u8, + } + } + + if setting.frontground.is_some() { + let color = setting.frontground.unwrap(); + let r = (color & 0xff0000) >> 16; + let g = (color & 0x00ff00) >> 8; + let b = color & 0x0000ff; + + ret.frontground = Color::Rgb { + r: r as u8, + g: g as u8, + b: b as u8, + } + } + + return ret; + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct LineNumberSetting { + pub enable: bool, + pub background: Color, + pub frontground: Color, +} + +impl Default for LineNumberSetting { + fn default() -> Self { + Self { + enable: true, + background: Color::DarkGreen, + frontground: Color::White, + } + } +} + +impl Default for LineSetting { + fn default() -> Self { + Self { + line_num: LineNumberSetting::default(), + highlight: Default::default(), + prefix_width: 0, + } + } +} + +// 高亮选项 +#[derive(Debug, serde::Deserialize, Clone, Copy)] +pub struct DeserializeHighLightSetting { + pub enable: bool, + pub color: u32, +} + +impl DeserializeHighLightSetting { + pub fn to_highlight_setting(&self) -> HighLightSetting { + let r = (self.color & 0xff0000) >> 16; + let g = (self.color & 0x00ff00) >> 8; + let b = self.color & 0x0000ff; + + HighLightSetting { + enable: self.enable, + color: Color::Rgb { + r: r as u8, + g: g as u8, + b: b as u8, + }, + } + } +} + +// 高亮选项 +#[derive(Debug, Clone, Copy)] +pub struct HighLightSetting { + pub enable: bool, + pub color: Color, +} + +impl Default for HighLightSetting { + fn default() -> Self { + Self { + enable: true, + color: Color::DarkYellow, + } + } +} + +impl DeserializeAppOption { + pub fn to_app_setting(&self) -> AppSetting { + let line_setting = match self.line { + Some(setting) => setting.to_line_setting(), + None => LineSetting::default(), + }; + + AppSetting { line: line_setting } + } +} + +impl DeserializeLineOption { + pub fn to_line_setting(&self) -> LineSetting { + let mut highlight = HighLightSetting::default(); + if self.highlight.is_some() { + let h = self.highlight.unwrap(); + highlight = h.to_highlight_setting(); + } + LineSetting { + line_num: DeserializeLineNumber::to_line_number_setting(self.number), + highlight, + prefix_width: 0, + } + } +} diff --git a/src/config/cmd.rs b/src/config/cmd.rs new file mode 100644 index 0000000..f7f0e10 --- /dev/null +++ b/src/config/cmd.rs @@ -0,0 +1,16 @@ +use clap::Parser; +use log::LevelFilter; + +#[derive(Parser)] +#[command(name = "held")] +#[command(author = "heyicong@dragonos.org")] +#[command(version = "1.0")] +#[command(about = "a termial editor", long_about = None)] +pub struct CmdConfig { + /// open file + pub file: Option, + + /// log level + #[arg(value_enum, short, long, default_value = "warn")] + pub level: LevelFilter, +} diff --git a/src/config/lastline_cmd.rs b/src/config/lastline_cmd.rs new file mode 100644 index 0000000..9069318 --- /dev/null +++ b/src/config/lastline_cmd.rs @@ -0,0 +1,304 @@ +use std::{collections::HashMap, sync::MutexGuard}; + +use lazy_static::lazy_static; + +use crate::utils::{ + buffer::LineState, + ui::{ + event::WarpUiCallBackType, + uicore::{UiCore, APP_INFO, CONTENT_WINSIZE}, + InfoLevel, + }, +}; + +lazy_static! { + static ref COMMAND: HashMap<&'static str, fn(&mut MutexGuard, &str) -> WarpUiCallBackType> = { + let mut map: HashMap<&str, fn(&mut MutexGuard, &str) -> WarpUiCallBackType> = + HashMap::new(); + map.insert(":q!", LastLineCommand::force_exit); + map.insert(":q", LastLineCommand::exit_without_store); + map.insert(":wq", LastLineCommand::exit); + + // 跳转 + map.insert(":goto", LastLineCommand::goto); + map.insert(":gt", LastLineCommand::goto); + + // 标记或锁定 + map.insert(":flag", LastLineCommand::flag); + map.insert(":lock", LastLineCommand::lock); + map.insert(":unflag", LastLineCommand::unflag); + map.insert(":unlock", LastLineCommand::unlock); + + map.insert(":delete", LastLineCommand::delete_lines); + map.insert(":dl", LastLineCommand::delete_lines); + + map + }; +} + +const EDITED_NO_STORE: &'static str = "Changes have not been saved"; +const NOT_FOUNT_CMD: &'static str = "Command Not Fount"; + +#[derive(Debug)] +pub struct LastLineCommand { + /// Command + pub command: String, + pub args: Vec, +} + +/// 提供给用户的命令行功能 +impl LastLineCommand { + pub fn process(ui: &mut MutexGuard, cmd: String) -> WarpUiCallBackType { + let args = cmd + .splitn(2, |x: char| x.is_ascii_whitespace()) + .collect::>(); + + if let Some(func) = COMMAND.get(args[0]) { + let ret = if args.len() == 1 { + func(ui, "") + } else { + func(ui, &args[1]) + }; + + ret + } else { + let mut info = APP_INFO.lock().unwrap(); + info.level = InfoLevel::Info; + info.info = NOT_FOUNT_CMD.to_string(); + return WarpUiCallBackType::None; + } + } + + const fn is_split_char(x: char) -> bool { + x == ',' || x == ';' || x == ':' || x == '/' || x.is_ascii_whitespace() + } + + fn force_exit(_ui: &mut MutexGuard, _args: &str) -> WarpUiCallBackType { + WarpUiCallBackType::Exit(false) + } + + fn exit_without_store(ui: &mut MutexGuard, _args: &str) -> WarpUiCallBackType { + if ui.edited() { + // 编辑过但不保存? + // 更新警示信息 + let mut info = APP_INFO.lock().unwrap(); + info.level = InfoLevel::Warn; + info.info = EDITED_NO_STORE.to_string(); + return WarpUiCallBackType::None; + } + WarpUiCallBackType::Exit(false) + } + + fn exit(_ui: &mut MutexGuard, _args: &str) -> WarpUiCallBackType { + WarpUiCallBackType::Exit(true) + } + + fn goto(ui: &mut MutexGuard, args: &str) -> WarpUiCallBackType { + if args.is_empty() { + let mut info = APP_INFO.lock().unwrap(); + info.level = InfoLevel::Info; + info.info = "Useage: {goto}|{gt} {row}{' '|','|';'|':'|'/'}{col}".to_string(); + return WarpUiCallBackType::None; + } + let (y, x) = { + let a = args.split(|x| Self::is_split_char(x)).collect::>(); + + if a.len() == 1 { + (u16::from_str_radix(a[0], 10), Ok(1)) + } else { + (u16::from_str_radix(a[0], 10), u16::from_str_radix(a[1], 10)) + } + }; + + if y.is_err() { + let mut info = APP_INFO.lock().unwrap(); + info.level = InfoLevel::Info; + info.info = "Useage: goto {row}({' '|','|';'|':'|'/'}{col})".to_string(); + return WarpUiCallBackType::None; + } + + let buf_line_max = ui.buffer.line_count() as u16; + let content_line_max = CONTENT_WINSIZE.read().unwrap().rows; + let mut y = y.unwrap().min(buf_line_max); + let mut x = x.unwrap_or(1).min(ui.buffer.get_linesize(y)); + + if y == 0 { + y += 1; + } + if x == 0 { + x += 1; + } + + x -= 1; + y -= 1; + + ui.cursor.set_prefix_mode(true); + + ui.cursor.restore_pos().unwrap(); + + // if y < ui.buffer.offset() as u16 + content_line_max { + // ui.buffer.set_offset(0); + // } else { + // ui.buffer.set_offset((y - content_line_max) as usize); + // } + + let lasty = ui.cursor.y(); + let y = ui.buffer.goto_line(y as usize); + ui.cursor.move_to(x, y).unwrap(); + + let pos = ui.cursor.store_tmp_pos(); + ui.render_content(0, content_line_max as usize).unwrap(); + ui.cursor.restore_tmp_pos(pos).unwrap(); + + ui.cursor.highlight(Some(lasty)).unwrap(); + + ui.cursor.store_pos(); + + return WarpUiCallBackType::None; + } + + // 标记行 + pub fn flag(ui: &mut MutexGuard, args: &str) -> WarpUiCallBackType { + let args = args.split(|x| Self::is_split_char(x)).collect::>(); + + if args.len() == 0 { + ui.buffer + .add_line_flags(ui.cursor.cmd_y() as usize - 1, LineState::FLAGED); + } + + for s in args { + let line = usize::from_str_radix(s, 10); + if line.is_err() { + APP_INFO.lock().unwrap().info = format!("\"{s}\" is not a number"); + return WarpUiCallBackType::None; + } + + let line = line.unwrap(); + ui.buffer.add_line_flags(line - 1, LineState::FLAGED); + } + + WarpUiCallBackType::None + } + + // 锁定行 + pub fn lock(ui: &mut MutexGuard, args: &str) -> WarpUiCallBackType { + let args = args.split(|x| Self::is_split_char(x)).collect::>(); + + if args.len() == 0 { + ui.buffer + .add_line_flags(ui.cursor.cmd_y() as usize - 1, LineState::LOCKED); + } + + for s in args { + let line = usize::from_str_radix(s, 10); + if line.is_err() { + APP_INFO.lock().unwrap().info = format!("\"{s}\" is not a number"); + return WarpUiCallBackType::None; + } + + let line = line.unwrap(); + ui.buffer.add_line_flags(line - 1, LineState::LOCKED); + } + + WarpUiCallBackType::None + } + + // 标记行 + pub fn unflag(ui: &mut MutexGuard, args: &str) -> WarpUiCallBackType { + let args = args.split(|x| Self::is_split_char(x)).collect::>(); + + if args.len() == 0 { + ui.buffer + .remove_line_flags(ui.cursor.cmd_y() as usize - 1, LineState::FLAGED); + } + + for s in args { + let line = usize::from_str_radix(s, 10); + if line.is_err() { + APP_INFO.lock().unwrap().info = format!("\"{s}\" is not a number"); + return WarpUiCallBackType::None; + } + + let line = line.unwrap(); + ui.buffer.remove_line_flags(line - 1, LineState::FLAGED); + } + + WarpUiCallBackType::None + } + + // 锁定行 + pub fn unlock(ui: &mut MutexGuard, args: &str) -> WarpUiCallBackType { + let args = args.split(|x| Self::is_split_char(x)).collect::>(); + + if args.len() == 0 { + ui.buffer + .remove_line_flags(ui.cursor.cmd_y() as usize - 1, LineState::LOCKED); + } + + for s in args { + let line = usize::from_str_radix(s, 10); + if line.is_err() { + APP_INFO.lock().unwrap().info = format!("\"{s}\" is not a number"); + return WarpUiCallBackType::None; + } + + let line = line.unwrap(); + ui.buffer.remove_line_flags(line - 1, LineState::LOCKED); + } + + WarpUiCallBackType::None + } + + pub fn delete_lines(ui: &mut MutexGuard, args: &str) -> WarpUiCallBackType { + let args = args.split(|x| x == '-').collect::>(); + + if args.len() == 0 { + // 删除当前行 + let offset = ui.buffer.offset() + ui.cursor.y() as usize; + let count = ui.buffer.delete_lines(offset, offset + 1); + if count != 0 { + APP_INFO.lock().unwrap().info = format!("Successfully deleted {count} row"); + } + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize) + .unwrap(); + return WarpUiCallBackType::None; + } + + if args.len() == 1 { + let line = usize::from_str_radix(args[0], 10); + if line.is_err() { + APP_INFO.lock().unwrap().info = format!("\"{}\" is not a number", args[0]); + return WarpUiCallBackType::None; + } + + let line = line.unwrap(); + + let offset = ui.buffer.offset() + line; + let count = ui.buffer.delete_lines(offset, offset + 1); + if count != 0 { + APP_INFO.lock().unwrap().info = format!("Successfully deleted {count} row"); + } + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize) + .unwrap(); + return WarpUiCallBackType::None; + } + + let start = usize::from_str_radix(args[0], 10); + let end = usize::from_str_radix(args[1], 10); + + if start.is_err() || end.is_err() { + APP_INFO.lock().unwrap().info = "Useage: (dl)|(delete) {start}({'-'}{end})".to_string(); + return WarpUiCallBackType::None; + } + + let count = ui.buffer.delete_lines(start.unwrap() - 1, end.unwrap() - 1); + if count != 0 { + APP_INFO.lock().unwrap().info = format!("Successfully deleted {count} row"); + } + + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize) + .unwrap(); + + return WarpUiCallBackType::None; + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..90d9281 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,3 @@ +pub mod cmd; +pub mod lastline_cmd; +pub mod appconfig; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..042c080 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,30 @@ +use std::{fs::File, io}; + +use app::Application; +use clap::Parser; +use config::{appconfig::DeserializeAppOption, cmd::CmdConfig}; +use utils::log_util::Log; + +mod app; +mod config; +mod utils; + +#[macro_use] +extern crate log; +extern crate simplelog; + +fn main() -> io::Result<()> { + let config = CmdConfig::parse(); + Log::init(config.level)?; + + let setting; + + let file = File::open("config.yam"); + if file.is_err() { + setting = DeserializeAppOption::default(); + } else { + setting = serde_yaml::from_reader::(file?).unwrap_or_default(); + } + + Application::new(config.file, setting.to_app_setting())?.run() +} diff --git a/src/utils/buffer.rs b/src/utils/buffer.rs new file mode 100644 index 0000000..45ddf9a --- /dev/null +++ b/src/utils/buffer.rs @@ -0,0 +1,380 @@ +use std::{ + collections::HashMap, + io, + ops::Deref, + sync::{ + atomic::{AtomicUsize, Ordering}, + RwLock, + }, +}; + +use bitflags::bitflags; +use crossterm::style::Color; + +use super::{ + style::StyleManager, + ui::uicore::{APP_INFO, CONTENT_WINSIZE}, +}; + +#[derive(Debug, Default, Clone)] +pub struct LineBuffer { + id: usize, + + pub data: Vec, + + // 是否被标记 + pub flags: LineState, +} + +impl Deref for LineBuffer { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl LineBuffer { + pub fn new(data: Vec) -> Self { + static LINE_ID_ALLOCTOR: AtomicUsize = AtomicUsize::new(0); + Self { + id: LINE_ID_ALLOCTOR.fetch_add(1, Ordering::SeqCst), + data, + flags: LineState::empty(), + } + } + + #[inline] + pub fn remove(&mut self, idx: usize) { + self.data.remove(idx); + } + + #[inline] + pub fn size(&self) -> usize { + self.data.len() + } + + #[inline] + pub fn extend(&mut self, other: LineBuffer) { + self.data.extend(other.data) + } + + #[inline] + pub fn insert(&mut self, idx: usize, data: u8) { + self.data.insert(idx, data) + } +} + +#[derive(Debug, Default)] +pub struct EditBuffer { + buf: RwLock>, + + // 记录当前页第一行对应buf中的index + offset: AtomicUsize, + + // 记录被标记的行,行ID -> 行index + flag_lines: RwLock>, + + // 记录锁定行 + locked_lines: RwLock>, +} + +impl EditBuffer { + pub fn new(buf: Vec) -> Self { + let mut lines = buf + .split_inclusive(|x| *x == '\n' as u8) + .map(|slice| slice.to_vec()) + .collect::>>(); + + let last = lines.last(); + if last.is_none() { + lines.push(vec!['\n' as u8]) + } else { + let last = last.unwrap(); + if !last.ends_with(&['\n' as u8]) { + lines.last_mut().unwrap().push('\n' as u8) + } + } + + let mut buf = Vec::new(); + for v in lines { + buf.push(LineBuffer::new(v)); + } + + Self { + buf: RwLock::new(buf), + offset: AtomicUsize::new(0), + flag_lines: RwLock::new(HashMap::new()), + locked_lines: RwLock::new(HashMap::new()), + } + } + + #[inline] + pub fn set_offset(&self, mut offset: usize) { + offset = offset.min(self.buf.read().unwrap().len() - 1); + self.offset.store(offset, Ordering::SeqCst); + } + + #[inline] + pub fn offset(&self) -> usize { + self.offset.load(Ordering::SeqCst) + } + + pub fn line_count(&self) -> usize { + self.buf.read().unwrap().len() + } + + /// 获取一部分上下文 + pub fn get_content( + &self, + mut start_y: usize, + mut line_count: usize, + ) -> Option> { + start_y += self.offset.load(Ordering::SeqCst); + line_count = line_count.min(self.line_count() - start_y); + let buf = self.buf.read().unwrap(); + if start_y > buf.len() { + return None; + } + let end = buf.len().min(start_y + line_count); + + let mut ret = Vec::with_capacity(end - start_y); + ret.resize(end - start_y, LineBuffer::default()); + ret[..].clone_from_slice(&buf[start_y..end]); + Some(ret) + } + + pub fn get_linesize(&self, line: u16) -> u16 { + let buf = self.buf.read().unwrap(); + let line = buf.get(self.offset.load(Ordering::SeqCst) + line as usize); + if line.is_none() { + return 0; + } + + let line = line.unwrap(); + + line.data.len() as u16 + } + + /// 外部接口,本结构体内部方法不应该使用,因为涉及offset计算 + pub fn remove_char(&self, x: u16, y: u16) { + let mut buf = self.buf.write().unwrap(); + let line = buf.get_mut(self.offset.load(Ordering::SeqCst) + y as usize); + if line.is_none() { + return; + } + + line.unwrap().remove(x as usize); + } + + /// 获取一份对应行的拷贝 + pub fn get_line(&self, line: u16) -> LineBuffer { + let buf = self.buf.read().unwrap(); + let line = buf.get(self.offset.load(Ordering::SeqCst) + line as usize); + if line.is_none() { + LineBuffer::default() + } else { + line.unwrap().clone() + } + } + + /// 将某行数据与上一行合并 + /// 返回合并是否成功,以及被合并行之前的长度 + pub fn merge_line(&self, line: u16) -> (bool, usize) { + let line = self.offset.load(Ordering::SeqCst) + line as usize; + if line == 0 { + // 没有上一行 + return (false, 0); + } + + let mut buf = self.buf.write().unwrap(); + let cur_line = buf.get(line as usize).unwrap().clone(); + + let previous_line = buf.get_mut(line - 1).unwrap(); + + if previous_line.flags.contains(LineState::LOCKED) + || cur_line.flags.contains(LineState::LOCKED) + { + APP_INFO.lock().unwrap().info = "Row is locked".to_string(); + return (false, 0); + } + + let p_len = previous_line.size(); + // 移除最后的\n + previous_line.remove(p_len - 1); + previous_line.extend(cur_line); + + buf.remove(line as usize); + + return (true, p_len - 1); + } + + /// 屏幕坐标 + #[inline] + pub fn insert_char(&self, ch: u8, x: u16, y: u16) { + let mut buf = self.buf.write().unwrap(); + let line = buf.get_mut(self.offset() + y as usize).unwrap(); + line.insert(x as usize, ch); + } + + #[inline] + pub fn all_buffer(&self) -> Vec { + self.buf.read().unwrap().clone() + } + + /// 输入enter时buf的更新操作 + pub fn input_enter(&self, x: u16, y: u16) { + let y = self.offset.load(Ordering::SeqCst) + y as usize; + + let mut buf = self.buf.write().unwrap(); + let linesize = buf.get(y).unwrap().size(); + if x as usize == linesize { + buf.insert(y, LineBuffer::new(vec!['\n' as u8])); + } + + let oldline = buf.get_mut(y).unwrap(); + let mut newline = Vec::new(); + newline.extend_from_slice(&oldline.data[x as usize..]); + + oldline.data.resize(x as usize, 0); + oldline.data.push('\n' as u8); + + buf.insert(y + 1, LineBuffer::new(newline)); + } + + pub fn add_line_flags(&self, line_index: usize, flags: LineState) { + let mut buf = self.buf.write().unwrap(); + + let line = buf.get_mut(line_index); + + if line.is_none() { + return; + } + + let line = line.unwrap(); + + line.flags.insert(flags); + + let mut flag_map = self.flag_lines.write().unwrap(); + if flags.contains(LineState::FLAGED) { + flag_map.insert(line.id, line_index); + } + + let mut locked_map = self.locked_lines.write().unwrap(); + if flags.contains(LineState::LOCKED) { + locked_map.insert(line.id, line_index); + } + } + + pub fn remove_line_flags(&self, line_index: usize, flags: LineState) { + let mut buf = self.buf.write().unwrap(); + + let line = buf.get_mut(line_index); + + if line.is_none() { + return; + } + + let line = line.unwrap(); + + line.flags.remove(flags); + + let mut flag_map = self.flag_lines.write().unwrap(); + if flags.contains(LineState::FLAGED) { + flag_map.remove(&line.id); + } + + let mut locked_map = self.locked_lines.write().unwrap(); + if flags.contains(LineState::LOCKED) { + locked_map.remove(&line.id); + } + } + + #[inline] + pub fn line_flags(&self, line: u16) -> LineState { + self.get_line(line).flags + } + + // 定位到指定行数,返回在正文窗口中的y坐标 + pub fn goto_line(&self, mut line_idx: usize) -> u16 { + let max_line = self.line_count(); + + if line_idx > max_line - 1 { + line_idx = max_line - 1 + } + + let size = *CONTENT_WINSIZE.read().unwrap(); + + // 先将其坐标定位在正文中央 + let win_rows = size.rows as usize; + let mut y = win_rows / 2; + + if line_idx < y { + self.set_offset(0); + return line_idx as u16; + } + + let mut offset = line_idx - y; + + if offset + win_rows > max_line { + // 最底下无数据,则调整 + let adapt_offset = max_line - win_rows; + + y += offset - adapt_offset; + offset = adapt_offset; + } + + self.set_offset(offset); + + y as u16 + } + + /// 删除行,不会删除锁定行,返回删除成功的行数 + pub fn delete_lines(&self, start: usize, mut end: usize) -> usize { + let max = self.line_count(); + if start >= max { + return 0; + } + + end = end.min(max); + let mut index = start; + let mut count = 0; + let mut buffer = self.buf.write().unwrap(); + + for _ in start..=end { + let line = buffer.get(index).unwrap(); + if line.flags.contains(LineState::LOCKED) { + index += 1; + } else { + buffer.remove(index); + count += 1; + } + } + + count + } +} + +bitflags! { + #[derive(Debug, Default, Clone,Copy)] + pub struct LineState: u32 { + /// 该行被标记 + const FLAGED = 1 << 1; + /// 锁定该行不能被更改 + const LOCKED = 1 << 2; + } +} + +impl LineState { + pub fn set_style(&self) -> io::Result<()> { + if self.contains(Self::FLAGED) { + StyleManager::set_background_color(Color::Cyan)?; + } + + if self.contains(Self::LOCKED) { + StyleManager::set_background_color(Color::DarkRed)?; + } + + Ok(()) + } +} diff --git a/src/utils/cursor.rs b/src/utils/cursor.rs new file mode 100644 index 0000000..aaa4d20 --- /dev/null +++ b/src/utils/cursor.rs @@ -0,0 +1,475 @@ +use std::{ + fmt::Display, + io::{self, stdout, Write}, + sync::Arc, +}; + +use crossterm::{ + cursor::{ + Hide, MoveDown, MoveLeft, MoveRight, MoveTo, MoveToColumn, MoveToNextLine, + MoveToPreviousLine, MoveToRow, MoveUp, RestorePosition, SavePosition, Show, + }, + ExecutableCommand, +}; + +use crate::config::appconfig::LineSetting; + +use super::{ + buffer::{EditBuffer, LineBuffer}, + style::StyleManager, + term_io::TermIO, + terminal::TermManager, + ui::uicore::{CONTENT_WINSIZE, DEF_STYLE, WINSIZE}, +}; + +struct CursorManager; + +#[allow(dead_code)] +impl CursorManager { + #[inline] + pub fn move_to(x: u16, y: u16) -> io::Result<()> { + stdout().execute(MoveTo(x, y)).unwrap().flush() + } + + #[inline] + pub fn move_to_nextline(lines: u16) -> io::Result<()> { + stdout().execute(MoveToNextLine(lines)).unwrap().flush() + } + + #[inline] + pub fn move_to_previous_line(lines: u16) -> io::Result<()> { + stdout().execute(MoveToPreviousLine(lines)).unwrap().flush() + } + + #[inline] + pub fn move_to_columu(col: u16) -> io::Result<()> { + stdout().execute(MoveToColumn(col)).unwrap().flush() + } + + #[inline] + pub fn move_to_row(row: u16) -> io::Result<()> { + stdout().execute(MoveToRow(row)).unwrap().flush() + } + + #[inline] + pub fn move_up(count: u16) -> io::Result<()> { + stdout().execute(MoveUp(count)).unwrap().flush() + } + + #[inline] + pub fn move_down(count: u16) -> io::Result<()> { + stdout().execute(MoveDown(count)).unwrap().flush() + } + + #[inline] + pub fn move_left(count: u16) -> io::Result<()> { + stdout().execute(MoveLeft(count)).unwrap().flush() + } + + #[inline] + pub fn move_right(count: u16) -> io::Result<()> { + stdout().execute(MoveRight(count)).unwrap().flush() + } + + #[inline] + pub fn save_position() -> io::Result<()> { + stdout().execute(SavePosition).unwrap().flush() + } + + #[inline] + pub fn restore_position() -> io::Result<()> { + stdout().execute(RestorePosition).unwrap().flush() + } + + #[inline] + pub fn hide() -> io::Result<()> { + stdout().execute(Hide).unwrap().flush() + } + + #[inline] + pub fn show() -> io::Result<()> { + stdout().execute(Show).unwrap().flush() + } +} + +#[derive(Debug)] +pub struct CursorCrtl { + x: u16, + y: u16, + + // 用于处理状态位置 + stored_x: u16, + stored_y: u16, + + line_prefix_width: u16, + store_flag: bool, + + // 正文模式会输出前缀,这个标志表示是否需要以正文前缀模式调整坐标 + prefix_mode: bool, + + line_setting: LineSetting, + + buf: Arc, +} + +#[allow(dead_code)] +impl CursorCrtl { + pub const PREFIX_COL: u16 = 1; + pub fn new(buf: Arc, line_setting: LineSetting) -> Self { + Self { + x: 0, + y: 0, + stored_x: 0, + stored_y: 0, + store_flag: false, + line_prefix_width: Self::PREFIX_COL, + prefix_mode: true, + line_setting, + buf, + } + } + + pub fn x(&self) -> u16 { + if self.prefix_mode { + if self.x < self.line_prefix_width { + return 0; + } + self.x - self.line_prefix_width + } else { + self.x + } + } + + pub fn y(&self) -> u16 { + self.y + } + + pub fn cmd_y(&self) -> u16 { + if self.store_flag { + self.stored_y + } else { + self.y + } + } + + #[inline] + pub fn set_prefix_mode(&mut self, on: bool) { + self.prefix_mode = on; + if on && self.x < self.line_prefix_width { + self.x = self.line_prefix_width; + self.move_to_columu(0).unwrap(); + } + } + + pub fn move_to(&mut self, mut x: u16, y: u16) -> io::Result<()> { + if self.prefix_mode { + x += self.line_prefix_width; + } + let size = *WINSIZE.read().unwrap(); + CursorManager::move_to(x, y)?; + self.x = (size.cols - 1).min(x); + self.y = (size.rows - 1).min(y); + Ok(()) + } + + pub fn move_to_nextline(&mut self, lines: u16) -> io::Result<()> { + let size = *WINSIZE.read().unwrap(); + if self.y + lines >= size.rows { + // 向上滚动 + todo!() + } + + CursorManager::move_to_nextline(lines)?; + if self.prefix_mode { + self.x = self.line_prefix_width; + self.move_to_columu(0)?; + } else { + self.x = 0; + } + + self.y += lines; + Ok(()) + } + + pub fn move_to_previous_line(&mut self, lines: u16) -> io::Result<()> { + let size = *WINSIZE.read().unwrap(); + + if self.y() - lines > size.rows { + // 溢出,则向下滚动 + todo!() + } + + CursorManager::move_to_previous_line(lines)?; + + self.y -= lines; + if self.prefix_mode { + self.x = self.line_prefix_width; + self.move_to_columu(0)?; + } else { + self.x = 0; + } + Ok(()) + } + + pub fn move_to_columu(&mut self, mut col: u16) -> io::Result<()> { + if self.prefix_mode { + col += self.line_prefix_width; + } + let size = *WINSIZE.read().unwrap(); + CursorManager::move_to_columu(col)?; + self.x = (size.cols - 1).min(col); + Ok(()) + } + + pub fn move_to_row(&mut self, row: u16) -> io::Result<()> { + let size = *WINSIZE.read().unwrap(); + CursorManager::move_to_row(row)?; + self.y = (size.rows - 1).min(row); + Ok(()) + } + + pub fn move_up(&mut self, count: u16) -> io::Result<()> { + CursorManager::move_up(count)?; + self.y -= count; + + Ok(()) + } + + pub fn move_down(&mut self, count: u16) -> io::Result<()> { + CursorManager::move_down(count)?; + + self.y += count; + Ok(()) + } + + pub fn move_left(&mut self, mut count: u16) -> io::Result<()> { + if count > self.x { + return self.move_to_columu(0); + } + if self.prefix_mode { + if self.x == self.line_prefix_width - 1 { + return Ok(()); + } + if self.x - count < self.line_prefix_width { + return self.move_to_columu(0); + } + } + if self.x == 0 { + return Ok(()); + } + if count > self.x { + count = self.x - self.line_prefix_width + } + CursorManager::move_left(count)?; + + if count > self.x { + self.x = self.line_prefix_width - 1; + } else { + self.x -= count; + } + + Ok(()) + } + + pub fn move_right(&mut self, count: u16) -> io::Result<()> { + let mut linesize = self.buf.get_linesize(self.y()) - 1; + let mut size = *WINSIZE.read().unwrap(); + if self.prefix_mode { + size.cols -= self.line_prefix_width; + linesize += self.line_prefix_width; + } + if self.x == size.cols - 1 { + return Ok(()); + } + + if self.x + count > linesize { + CursorManager::move_to_columu(linesize)?; + self.x = linesize; + } else { + CursorManager::move_right(count)?; + self.x += count; + } + + Ok(()) + } + + pub fn write(&mut self, str: D) -> io::Result<()> { + let str = str.to_string(); + + let ss = str.split_terminator(|x| x == '\n').collect::>(); + for s in ss { + self.write_line(s)?; + } + Ok(()) + } + + fn write_line(&mut self, str: &str) -> io::Result<()> { + let len = str.len() as u16; + + let mut size = *WINSIZE.read().unwrap(); + + if self.prefix_mode { + size.cols -= self.line_prefix_width; + } + + if self.x + len > size.cols { + let ss = str.split_at((size.cols - self.x) as usize); + TermIO::write_str(ss.0)?; + self.move_to_nextline(1)?; + self.write_line(ss.1)?; + } else { + TermIO::write_str(str)?; + if str.ends_with(|x| x == '\n') { + self.move_to_nextline(1)?; + } else { + self.x += str.len() as u16; + } + } + + Ok(()) + } + + pub fn write_with_pos( + &mut self, + str: D, + x: u16, + y: u16, + stroe: bool, + ) -> io::Result<()> { + let mut pos = (0, 0); + if stroe { + pos = self.store_tmp_pos(); + } + self.move_to(x, y)?; + self.write(str)?; + if stroe { + self.restore_tmp_pos(pos)?; + } + Ok(()) + } + + pub fn store_pos(&mut self) { + if self.store_flag { + panic!("Stored val doesn't restore") + } + self.stored_x = self.x; + self.stored_y = self.y; + self.store_flag = true; + } + + pub fn restore_pos(&mut self) -> io::Result<()> { + if !self.store_flag { + panic!("No val stored") + } + self.x = self.stored_x; + self.y = self.stored_y; + self.store_flag = false; + CursorManager::move_to(self.stored_x, self.stored_y) + } + + #[inline] + pub fn store_tmp_pos(&mut self) -> (u16, u16) { + (self.x(), self.y()) + } + + pub fn restore_tmp_pos(&mut self, pos: (u16, u16)) -> io::Result<()> { + self.move_to(pos.0, pos.1) + } + + /// 更新前缀列 + pub fn update_line_prefix( + &mut self, + content: &Vec, + start: u16, + number_len: usize, + ) -> io::Result<()> { + let startline = self.buf.offset() + 1; + let size = *CONTENT_WINSIZE.read().unwrap(); + let max_line = startline + size.rows as usize; + + // 先关闭prefix模式 + self.set_prefix_mode(false); + + // 绝对坐标 + let (x, y) = (self.x(), self.y()); + + // 更新第一列flags + for (num, line) in content.iter().enumerate() { + // 设置颜色 + StyleManager::set_background_color(self.line_setting.line_num.background)?; + StyleManager::set_foreground_color(self.line_setting.line_num.frontground)?; + self.move_to(0, start + num as u16)?; + let flags = line.flags; + flags.set_style()?; + self.write("~")?; + StyleManager::reset_color()?; + } + + // 更新页面行号 + if self.line_setting.line_num.enable { + let len = number_len + 2; + self.line_prefix_width = len as u16 + Self::PREFIX_COL; + + // 设置颜色 + StyleManager::set_background_color(self.line_setting.line_num.background)?; + StyleManager::set_foreground_color(self.line_setting.line_num.frontground)?; + for line in startline..max_line { + self.move_to(Self::PREFIX_COL, (line - startline) as u16)?; + let mut prefix = line.to_string(); + + prefix.insert(0, ' '); + unsafe { + let data = prefix.as_mut_vec(); + data.resize(len, ' ' as u8); + }; + + self.write(prefix)?; + } + StyleManager::reset_color()?; + } + // 恢复绝对坐标 + self.move_to(x, y)?; + + self.set_prefix_mode(true); + + Ok(()) + } + + pub fn clear_current_line(&mut self) -> io::Result<()> { + if self.prefix_mode { + let tmp = self.x(); + self.move_to_columu(0)?; + TermManager::clear_until_new_line()?; + self.move_to_columu(tmp) + } else { + TermManager::clear_current_line() + } + } + + pub fn highlight(&mut self, last_line: Option) -> io::Result<()> { + if !self.line_setting.highlight.enable { + return Ok(()); + } + DEF_STYLE.read().unwrap().set_content_style()?; + + if last_line.is_some() { + let last_line = last_line.unwrap(); + // 清除上一行高光 + let pos = self.store_tmp_pos(); + self.move_to(0, last_line)?; + self.clear_current_line()?; + self.write(String::from_utf8_lossy(&self.buf.get_line(last_line)))?; + self.restore_tmp_pos(pos)?; + } + + let pos = self.store_tmp_pos(); + // 设置高光 + StyleManager::set_background_color(self.line_setting.highlight.color)?; + self.clear_current_line()?; + self.move_to_columu(0)?; + self.write(String::from_utf8_lossy(&self.buf.get_line(self.y())))?; + self.restore_tmp_pos(pos)?; + + Ok(()) + } +} diff --git a/src/utils/file.rs b/src/utils/file.rs new file mode 100644 index 0000000..c95f528 --- /dev/null +++ b/src/utils/file.rs @@ -0,0 +1,86 @@ +use std::{ + fs::{self, File}, + io::{self, Read, Seek, Write}, +}; + +use super::buffer::EditBuffer; + +pub const BAK_SUFFIX: &'static str = ".heldbak"; + +pub struct FileManager { + name: String, + file: File, + bak: Option, +} + +impl FileManager { + pub fn new(file_path: String) -> io::Result { + let file = File::options() + .write(true) + .read(true) + .create(true) + .open(file_path.clone())?; + + Ok(Self { + file, + name: file_path, + bak: None, + }) + } + + pub fn init(&mut self, bak: bool) -> io::Result { + let mut buf = Vec::new(); + // 是否备份 + if bak { + self.do_bak(&mut buf)?; + } else { + self.file.read_to_end(&mut buf)?; + } + + Ok(EditBuffer::new(buf)) + } + + // 备份 + fn do_bak(&mut self, buf: &mut Vec) -> io::Result<()> { + let mut bak = File::options() + .write(true) + .read(true) + .create(true) + .open(format!("{}{}", self.name, BAK_SUFFIX))?; + + bak.set_len(0)?; + + self.file.read_to_end(buf)?; + bak.write_all(&buf)?; + + self.file.seek(io::SeekFrom::Start(0))?; + + if self.bak.is_some() { + error!("The backup already exists. The operation may cause data loss."); + } + + self.bak = Some(bak); + + Ok(()) + } + + pub fn store(&mut self, buf: &EditBuffer) -> io::Result<()> { + let data = buf.all_buffer(); + + self.file.set_len(0)?; + + for (idx, line) in data.iter().enumerate() { + if idx == data.len() - 1 { + self.file.write(&line[..line.len()])?; + } else { + self.file.write(&line)?; + } + } + + if self.bak.is_some() { + fs::remove_file(format!("{}{}", self.name, BAK_SUFFIX))?; + } + + Ok(()) + } +} diff --git a/src/utils/input.rs b/src/utils/input.rs new file mode 100644 index 0000000..9106c74 --- /dev/null +++ b/src/utils/input.rs @@ -0,0 +1,71 @@ +use std::io::{self, Read}; + +pub struct Input; + +impl Input { + pub fn wait_keydown() -> io::Result { + let buf: &mut [u8] = &mut [0; 8]; + let count = io::stdin().read(buf)?; + Ok(KeyCodeParser::parse(&buf[0..count])) + } +} + +struct KeyCodeParser; + +impl KeyCodeParser { + pub fn parse(bytes: &[u8]) -> KeyEventType { + if bytes[0] == 224 { + // 控制字符 + return Self::parse_ctrl(&bytes[1..]); + } + match bytes { + // Enter key + b"\n" => KeyEventType::Enter, + // Tab key + b"\t" => KeyEventType::Tab, + // Esc + [0] => KeyEventType::Esc, + + [8] => KeyEventType::Backspace, + + // ASCII 字符 + [byte] if *byte >= 32 && *byte <= 126 => KeyEventType::Common(bytes[0]), + // Unknown bytes + bytes => { + error!("unknown bytes {bytes:?}"); + KeyEventType::Unknown(bytes.to_vec()) + } + } + } + + fn parse_ctrl(bytes: &[u8]) -> KeyEventType { + match bytes { + [72] => KeyEventType::Up, + [80] => KeyEventType::Down, + [75] => KeyEventType::Left, + [77] => KeyEventType::Right, + bytes => { + error!("unknown ctrl bytes {bytes:?}"); + KeyEventType::Unknown(bytes.to_vec()) + } + } + } +} + +#[derive(Debug, Clone)] +pub enum KeyEventType { + Common(u8), + + Up, + Down, + Right, + Left, + + Enter, + Tab, + Backspace, + + Esc, + + Unknown(Vec), +} diff --git a/src/utils/log_util.rs b/src/utils/log_util.rs new file mode 100644 index 0000000..fc6b3f6 --- /dev/null +++ b/src/utils/log_util.rs @@ -0,0 +1,27 @@ +use std::{fs::File, io}; + +use log::LevelFilter; +use simplelog::{CombinedLogger, WriteLogger}; + +pub struct Log; + +impl Log { + pub fn init(level: LevelFilter) -> io::Result<()> { + CombinedLogger::init(vec![ + // TermLogger::new( + // level.to_simplelog_filter(), + // simplelog::Config::default(), + // simplelog::TerminalMode::default(), + // simplelog::ColorChoice::Auto, + // ), + WriteLogger::new( + level, + simplelog::Config::default(), + File::create("held.log")?, + ), + ]) + .unwrap(); + + Ok(()) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..2427952 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,11 @@ +pub mod buffer; +pub mod cursor; +pub mod file; +/// 暂时写在这适配DragonOS +#[cfg(feature = "dragonos")] +pub mod input; +pub mod log_util; +pub mod style; +pub mod term_io; +pub mod terminal; +pub mod ui; diff --git a/src/utils/style.rs b/src/utils/style.rs new file mode 100644 index 0000000..ac768bd --- /dev/null +++ b/src/utils/style.rs @@ -0,0 +1,54 @@ +use std::io::{self, stdout, Write}; + +use crossterm::{style::*, ExecutableCommand}; + +pub struct StyleManager; + +#[allow(dead_code)] +impl StyleManager { + #[inline] + pub fn set_foreground_color(color: Color) -> io::Result<()> { + stdout().execute(SetForegroundColor(color)).unwrap().flush() + } + + #[inline] + pub fn set_background_color(color: Color) -> io::Result<()> { + stdout().execute(SetBackgroundColor(color)).unwrap().flush() + } + + #[inline] + pub fn set_underline_color(color: Color) -> io::Result<()> { + stdout().execute(SetUnderlineColor(color)).unwrap().flush() + } + + #[inline] + pub fn set_color(fg: Option, bg: Option) -> io::Result<()> { + stdout() + .execute(SetColors(Colors { + foreground: fg, + background: bg, + })) + .unwrap() + .flush() + } + + #[inline] + pub fn set_attr(attr: Attribute) -> io::Result<()> { + stdout().execute(SetAttribute(attr)).unwrap().flush() + } + + #[inline] + pub fn set_attrs(attr: Attributes) -> io::Result<()> { + stdout().execute(SetAttributes(attr)).unwrap().flush() + } + + #[inline] + pub fn set_style(style: ContentStyle) -> io::Result<()> { + stdout().execute(SetStyle(style)).unwrap().flush() + } + + #[inline] + pub fn reset_color() -> io::Result<()> { + stdout().execute(ResetColor).unwrap().flush() + } +} diff --git a/src/utils/term_io.rs b/src/utils/term_io.rs new file mode 100644 index 0000000..8890959 --- /dev/null +++ b/src/utils/term_io.rs @@ -0,0 +1,15 @@ +use std::{ + fmt::Display, + io::{self, stdout, Write}, +}; + +use crossterm::{style::Print, ExecutableCommand}; + +pub struct TermIO; + +impl TermIO { + pub fn write_str(str: D) -> io::Result<()> { + stdout().execute(Print(str)).unwrap().flush()?; + Ok(()) + } +} diff --git a/src/utils/terminal.rs b/src/utils/terminal.rs new file mode 100644 index 0000000..ec3c194 --- /dev/null +++ b/src/utils/terminal.rs @@ -0,0 +1,87 @@ +use std::io::{self, stdout, Write}; + +use crossterm::{terminal::*, ExecutableCommand}; + +use super::ui::uicore::DEF_STYLE; + +pub struct TermManager; + +#[allow(dead_code)] +impl TermManager { + pub fn init_term() -> io::Result<()> { + DEF_STYLE.read().unwrap().set_content_style()?; + Self::clear_all() + } + + #[inline] + pub fn disable_line_warp() -> io::Result<()> { + stdout().execute(DisableLineWrap).unwrap().flush() + } + + #[inline] + pub fn enable_line_warp() -> io::Result<()> { + stdout().execute(EnableLineWrap).unwrap().flush() + } + + #[inline] + pub fn leave_alternate_screen() -> io::Result<()> { + stdout().execute(LeaveAlternateScreen).unwrap().flush() + } + + #[inline] + pub fn enter_alternate_screen() -> io::Result<()> { + stdout().execute(EnterAlternateScreen).unwrap().flush() + } + + #[inline] + pub fn scroll_up(lines: u16) -> io::Result<()> { + stdout().execute(ScrollUp(lines)).unwrap().flush() + } + + #[inline] + pub fn scroll_down(lines: u16) -> io::Result<()> { + stdout().execute(ScrollDown(lines)).unwrap().flush() + } + + #[inline] + pub fn clear_all() -> io::Result<()> { + stdout().execute(Clear(ClearType::All)).unwrap().flush() + } + + #[inline] + pub fn clear_purge() -> io::Result<()> { + stdout().execute(Clear(ClearType::Purge)).unwrap().flush() + } + + #[inline] + pub fn clear_under_cursor() -> io::Result<()> { + stdout() + .execute(Clear(ClearType::FromCursorDown)) + .unwrap() + .flush() + } + + #[inline] + pub fn clear_up_cursor() -> io::Result<()> { + stdout() + .execute(Clear(ClearType::FromCursorUp)) + .unwrap() + .flush() + } + + #[inline] + pub fn clear_current_line() -> io::Result<()> { + stdout() + .execute(Clear(ClearType::CurrentLine)) + .unwrap() + .flush() + } + + #[inline] + pub fn clear_until_new_line() -> io::Result<()> { + stdout() + .execute(Clear(ClearType::UntilNewLine)) + .unwrap() + .flush() + } +} diff --git a/src/utils/ui/event.rs b/src/utils/ui/event.rs new file mode 100644 index 0000000..b223065 --- /dev/null +++ b/src/utils/ui/event.rs @@ -0,0 +1,171 @@ +use std::{io, sync::MutexGuard}; + +use crate::utils::{buffer::LineState, cursor::CursorCrtl, style::StyleManager}; + +use super::{ + mode::mode::ModeType, + uicore::{UiCore, APP_INFO, CONTENT_WINSIZE, DEF_STYLE, UI_CMD_HEIGHT}, +}; + +pub const TAB_STR: &'static str = " "; + +pub trait KeyEventCallback { + fn enter(&self, ui: &mut MutexGuard) -> io::Result; + fn tab(&self, ui: &mut MutexGuard) -> io::Result; + fn backspace(&self, ui: &mut MutexGuard) -> io::Result { + if ui.cursor.x() == 0 { + let y = ui.cursor.y(); + let (merged, linelen) = ui.buffer.merge_line(y); + if merged { + // 需要向上翻页 + if ui.cursor.y() == 0 { + ui.scroll_down(1)?; + ui.cursor.move_to_nextline(1)?; + } + // 重新渲染 + ui.cursor.move_up(1)?; + + let y = ui.cursor.y(); + let ret = + ui.render_content(y, (CONTENT_WINSIZE.read().unwrap().rows - y + 1) as usize)?; + + // 清除之前显示行 + // 计算需要clear的行号 + let clear_y = if ui.cursor.y() == 0 { y + 1 } else { y }; + let row = clear_y + ret as u16; + + ui.cursor.move_to_row(row)?; + + DEF_STYLE.read().unwrap().set_content_style()?; + + ui.cursor.set_prefix_mode(false); + StyleManager::reset_color()?; + ui.cursor.move_to_columu(0)?; + ui.cursor + .write(&TAB_STR[..CursorCrtl::PREFIX_COL as usize])?; + ui.cursor.set_prefix_mode(true); + + ui.cursor.clear_current_line()?; + + ui.cursor.move_to_row(y)?; + ui.cursor.move_to_columu(linelen as u16)?; + ui.cursor.highlight(Some(clear_y))?; + ui.set_edited(); + return Ok(WarpUiCallBackType::None); + } else { + return Ok(WarpUiCallBackType::None); + } + } + + let y = ui.cursor.y(); + let x = ui.cursor.x(); + + let line = ui.buffer.get_line(y); + if line.flags.contains(LineState::LOCKED) { + APP_INFO.lock().unwrap().info = "Row is locked".to_string(); + return Ok(WarpUiCallBackType::None); + } + self.left(ui)?; + + ui.buffer.remove_char(x - 1, y); + + let line = ui.buffer.get_line(y); + + ui.cursor.write(format!( + "{} ", + String::from_utf8_lossy(&line.data[x as usize..]) + ))?; + + ui.cursor.highlight(None)?; + + ui.cursor.move_to_columu(x - 1)?; + + Ok(WarpUiCallBackType::None) + } + fn up(&self, ui: &mut MutexGuard) -> io::Result { + if ui.cursor.y() == 0 { + if ui.buffer.offset() == 0 { + // 上面没有数据 + return Ok(WarpUiCallBackType::None); + } + // 向上滚动 + ui.scroll_down(1)?; + + let linesize = ui.buffer.get_linesize(ui.cursor.y()); + + // 考虑\n + if linesize - 1 < ui.cursor.x() { + ui.cursor.move_to_columu(linesize - 1)?; + } + return Ok(WarpUiCallBackType::None); + } + let linesize = ui.buffer.get_linesize(ui.cursor.y() - 1); + + if linesize == 0 { + return Ok(WarpUiCallBackType::None); + } + + ui.cursor.move_up(1)?; + + // 考虑\n + if linesize - 1 < ui.cursor.x() { + ui.cursor.move_to_columu(linesize - 1)?; + } + + let last_y = ui.cursor.y() + 1; + ui.cursor.highlight(Some(last_y))?; + + Ok(WarpUiCallBackType::None) + } + fn down(&self, ui: &mut MutexGuard) -> io::Result { + let size = *CONTENT_WINSIZE.read().unwrap(); + let mut linesize = ui.buffer.get_linesize(ui.cursor.y() + 1); + + if linesize == 0 { + return Ok(WarpUiCallBackType::None); + } + + if ui.cursor.y() == size.rows - UI_CMD_HEIGHT { + // 向shang滚动 + ui.scroll_up(1)?; + if linesize < ui.cursor.x() { + ui.cursor.move_to_columu(linesize - 1)?; + } + return Ok(WarpUiCallBackType::None); + } + + // \n + linesize -= 1; + + ui.cursor.move_down(1)?; + + if linesize < ui.cursor.x() { + ui.cursor.move_to_columu(linesize)?; + } + let last_y = ui.cursor.y() - 1; + ui.cursor.highlight(Some(last_y))?; + + Ok(WarpUiCallBackType::None) + } + fn left(&self, ui: &mut MutexGuard) -> io::Result { + ui.cursor.move_left(1)?; + Ok(WarpUiCallBackType::None) + } + fn right(&self, ui: &mut MutexGuard) -> io::Result { + ui.cursor.move_right(1)?; + Ok(WarpUiCallBackType::None) + } + fn esc(&self, ui: &mut MutexGuard) -> io::Result; + fn input_data( + &self, + ui: &mut MutexGuard, + data: &[u8], + ) -> io::Result; +} + +#[derive(Debug, PartialEq)] +pub enum WarpUiCallBackType { + ChangMode(ModeType), + Exit(bool), + None, +} diff --git a/src/utils/ui/mod.rs b/src/utils/ui/mod.rs new file mode 100644 index 0000000..1b7646f --- /dev/null +++ b/src/utils/ui/mod.rs @@ -0,0 +1,46 @@ +use std::io; + +use crossterm::style::Color; + +use super::style::StyleManager; + +pub mod event; +pub mod mode; +pub mod uicore; + +#[derive(Debug)] +pub struct AppInfo { + pub level: InfoLevel, + pub info: String, +} + +impl AppInfo { + pub fn reset(&mut self) { + self.level = InfoLevel::Info; + self.info = String::new(); + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum InfoLevel { + Info, + Warn, + Error, +} + +impl InfoLevel { + pub fn set_style(&self) -> io::Result<()> { + match self { + InfoLevel::Info => {} + InfoLevel::Warn => { + StyleManager::set_background_color(Color::DarkYellow)?; + } + InfoLevel::Error => { + StyleManager::set_background_color(Color::DarkRed)?; + } + } + + Ok(()) + } +} diff --git a/src/utils/ui/mode/mod.rs b/src/utils/ui/mode/mod.rs new file mode 100644 index 0000000..7b7e0c7 --- /dev/null +++ b/src/utils/ui/mode/mod.rs @@ -0,0 +1 @@ +pub mod mode; diff --git a/src/utils/ui/mode/mode.rs b/src/utils/ui/mode/mode.rs new file mode 100644 index 0000000..c9cdd2f --- /dev/null +++ b/src/utils/ui/mode/mode.rs @@ -0,0 +1,471 @@ +use std::sync::atomic::Ordering; +use std::sync::{Mutex, MutexGuard}; +use std::{fmt::Debug, io}; + +use crate::config::lastline_cmd::LastLineCommand; +use crate::utils::buffer::LineState; +#[cfg(feature = "dragonos")] +use crate::utils::input::KeyEventType; + +use crate::utils::terminal::TermManager; + +use crate::utils::ui::uicore::{UiCore, APP_INFO, TAB_SIZE}; +use crate::utils::ui::{ + event::KeyEventCallback, + uicore::{CONTENT_WINSIZE, DEF_STYLE}, +}; + +use crate::utils::ui::event::WarpUiCallBackType; + +pub trait InputMode: KeyEventCallback + Debug { + fn mode_type(&self) -> ModeType; + + #[cfg(not(feature = "dragonos"))] + fn event_route( + &self, + ui: &mut MutexGuard, + event: crossterm::event::Event, + ) -> io::Result { + match event { + crossterm::event::Event::FocusGained => todo!(), + crossterm::event::Event::FocusLost => todo!(), + crossterm::event::Event::Key(key) => self.key_event_route(ui, key), + crossterm::event::Event::Mouse(_) => todo!(), + crossterm::event::Event::Paste(_) => todo!(), + crossterm::event::Event::Resize(_, _) => todo!(), + } + } + + #[cfg(not(feature = "dragonos"))] + fn key_event_route( + &self, + ui: &mut MutexGuard, + keyev: crossterm::event::KeyEvent, + ) -> io::Result { + let callback = match keyev.code { + crossterm::event::KeyCode::Backspace => self.backspace(ui)?, + crossterm::event::KeyCode::Enter => self.enter(ui)?, + crossterm::event::KeyCode::Left => self.left(ui)?, + crossterm::event::KeyCode::Right => self.right(ui)?, + crossterm::event::KeyCode::Up => self.up(ui)?, + crossterm::event::KeyCode::Down => self.down(ui)?, + crossterm::event::KeyCode::Home => todo!(), + crossterm::event::KeyCode::End => todo!(), + crossterm::event::KeyCode::PageUp => todo!(), + crossterm::event::KeyCode::PageDown => todo!(), + crossterm::event::KeyCode::Tab => self.tab(ui)?, + crossterm::event::KeyCode::BackTab => todo!(), + crossterm::event::KeyCode::Delete => todo!(), + crossterm::event::KeyCode::Insert => todo!(), + crossterm::event::KeyCode::F(_) => todo!(), + crossterm::event::KeyCode::Char(c) => self.input_data(ui, &[c as u8])?, + crossterm::event::KeyCode::Null => todo!(), + crossterm::event::KeyCode::Esc => self.esc(ui)?, + crossterm::event::KeyCode::CapsLock => todo!(), + crossterm::event::KeyCode::ScrollLock => todo!(), + crossterm::event::KeyCode::NumLock => todo!(), + crossterm::event::KeyCode::PrintScreen => todo!(), + crossterm::event::KeyCode::Pause => todo!(), + crossterm::event::KeyCode::Menu => todo!(), + crossterm::event::KeyCode::KeypadBegin => todo!(), + crossterm::event::KeyCode::Media(_) => todo!(), + crossterm::event::KeyCode::Modifier(_) => todo!(), + }; + + Ok(callback) + } + + #[cfg(feature = "dragonos")] + fn key_event_route( + &self, + ui: &mut MutexGuard, + key: KeyEventType, + ) -> io::Result { + match key { + KeyEventType::Common(c) => self.input_data(ui, &[c]), + KeyEventType::Up => self.up(ui), + KeyEventType::Down => self.down(ui), + KeyEventType::Right => self.right(ui), + KeyEventType::Left => self.left(ui), + KeyEventType::Enter => self.enter(ui), + KeyEventType::Tab => self.tab(ui), + KeyEventType::Backspace => self.backspace(ui), + KeyEventType::Esc => self.esc(ui), + KeyEventType::Unknown(_) => { + ui.update_bottom_state_bar()?; + Ok(WarpUiCallBackType::None) + } + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum ModeType { + Command, + LastLine, + Insert, +} + +impl InputMode for Command { + fn mode_type(&self) -> ModeType { + ModeType::Command + } +} +impl InputMode for LastLine { + fn mode_type(&self) -> ModeType { + ModeType::LastLine + } +} +impl InputMode for Insert { + fn mode_type(&self) -> ModeType { + ModeType::Insert + } +} + +#[derive(Debug)] +pub struct Command; + +impl Command { + pub fn jump_to_next_flag( + &self, + ui: &mut MutexGuard, + flags: LineState, + ) -> io::Result<()> { + let offset = ui.buffer.offset(); + let y = ui.cursor.y() as usize; + + let start_line_number = offset + y + 1; + if start_line_number >= ui.buffer.line_count() { + return Ok(()); + } + + let content = &ui.buffer.all_buffer()[start_line_number..]; + + // 下一个flaged位置 + let idx = content.iter().position(|x| x.flags.contains(flags)); + + if idx.is_some() { + // y + idx + let line_number = start_line_number + idx.unwrap(); + let new_y = ui.buffer.goto_line(line_number); + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + ui.cursor.move_to_row(new_y)?; + ui.cursor.highlight(Some(y as u16))?; + } + + Ok(()) + } + + pub fn jump_to_previous_flag( + &self, + ui: &mut MutexGuard, + flags: LineState, + ) -> io::Result<()> { + let offset = ui.buffer.offset(); + let y = ui.cursor.y() as usize; + if offset == 0 && y == 0 { + return Ok(()); + } + let end_linenumber = offset + y - 1; + + let content = &ui.buffer.all_buffer()[0..end_linenumber]; + + // 下一个flaged位置 + let idx = content.iter().rposition(|x| x.flags.contains(flags)); + + if idx.is_some() { + // y + idx + let new_y = ui.buffer.goto_line(idx.unwrap()); + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + ui.cursor.move_to_row(new_y)?; + ui.cursor.highlight(Some(y as u16))?; + } + + Ok(()) + } +} + +impl KeyEventCallback for Command { + fn backspace(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + fn enter(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + + fn tab(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + + fn esc(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + + fn input_data( + &self, + ui: &mut MutexGuard, + data: &[u8], + ) -> io::Result { + match data { + b":" => { + // 保存位置 + ui.cursor.store_pos(); + return Ok(WarpUiCallBackType::ChangMode(ModeType::LastLine)); + } + + b"i" | b"I" => { + // 切换Insert模式 + return Ok(WarpUiCallBackType::ChangMode(ModeType::Insert)); + } + + b"l" | b"L" => { + // 设置当前行lock + let flag = ui.buffer.line_flags(ui.cursor.y()); + let offset = ui.buffer.offset(); + if flag.contains(LineState::LOCKED) { + ui.buffer + .remove_line_flags(offset + ui.cursor.y() as usize, LineState::LOCKED); + } else { + ui.buffer + .add_line_flags(offset + ui.cursor.y() as usize, LineState::LOCKED); + } + let y = ui.cursor.y(); + ui.render_content(y, 1)?; + return Ok(WarpUiCallBackType::None); + } + + b"f" | b"F" => { + // 设置当前行flag + let flag = ui.buffer.line_flags(ui.cursor.y()); + let offset = ui.buffer.offset(); + if flag.contains(LineState::FLAGED) { + ui.buffer + .remove_line_flags(offset + ui.cursor.y() as usize, LineState::FLAGED); + } else { + ui.buffer + .add_line_flags(offset + ui.cursor.y() as usize, LineState::FLAGED); + } + + let y = ui.cursor.y(); + ui.render_content(y, 1)?; + return Ok(WarpUiCallBackType::None); + } + + b"q" | b"Q" => { + // 跳转到上一个flag行 + self.jump_to_previous_flag(ui, LineState::FLAGED)?; + return Ok(WarpUiCallBackType::None); + } + + b"w" | b"W" => { + // 跳转到下一个flag行 + self.jump_to_next_flag(ui, LineState::FLAGED)?; + return Ok(WarpUiCallBackType::None); + } + + b"a" | b"A" => { + self.jump_to_previous_flag(ui, LineState::LOCKED)?; + return Ok(WarpUiCallBackType::None); + } + + b"s" | b"S" => { + self.jump_to_next_flag(ui, LineState::LOCKED)?; + return Ok(WarpUiCallBackType::None); + } + + _ => { + return Ok(WarpUiCallBackType::None); + } + } + } +} + +#[derive(Debug)] +pub struct Insert; +impl KeyEventCallback for Insert { + fn enter(&self, ui: &mut MutexGuard) -> io::Result { + let line_idx = ui.cursor.y(); + let col = ui.cursor.x(); + + let line = ui.buffer.get_line(line_idx); + if line.flags.contains(LineState::LOCKED) { + APP_INFO.lock().unwrap().info = "Row is locked".to_string(); + return Ok(WarpUiCallBackType::None); + } + ui.buffer.input_enter(col, line_idx); + + DEF_STYLE.read().unwrap().set_content_style()?; + // 清空改行光标后的内容 + TermManager::clear_until_new_line()?; + + // 执行渲染后续文本 + ui.cursor.move_to_nextline(1)?; + ui.cursor.clear_current_line()?; + + let ret = ui.render_content( + line_idx + 1, + (CONTENT_WINSIZE.read().unwrap().rows - line_idx) as usize, + )?; + + if ret == 0 { + ui.scroll_up(1)?; + ui.render_content( + line_idx + 1, + (CONTENT_WINSIZE.read().unwrap().rows - line_idx) as usize, + )?; + + ui.cursor.move_up(1)?; + } + + let last = ui.cursor.y() - 1; + ui.cursor.highlight(Some(last))?; + ui.set_edited(); + Ok(WarpUiCallBackType::None) + } + + fn tab(&self, ui: &mut MutexGuard) -> io::Result { + ui.set_edited(); + let x = ui.cursor.x(); + + let tab_size = TAB_SIZE.load(Ordering::SeqCst); + let space_size = tab_size - (x % tab_size); + + for _ in 0..space_size { + ui.buffer + .insert_char(' ' as u8, ui.cursor.x(), ui.cursor.y()); + } + + let y = ui.cursor.y(); + ui.render_content(y, 1)?; + + ui.cursor.move_right(space_size)?; + + Ok(WarpUiCallBackType::None) + } + + fn esc(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::ChangMode(ModeType::Command)) + } + + fn input_data( + &self, + ui: &mut MutexGuard, + data: &[u8], + ) -> io::Result { + let x = ui.cursor.x(); + let y = ui.cursor.y(); + + let line = ui.buffer.get_line(y); + if line.flags.contains(LineState::LOCKED) { + APP_INFO.lock().unwrap().info = "Row is locked".to_string(); + return Ok(WarpUiCallBackType::None); + } + + for (idx, ch) in data.iter().enumerate() { + ui.buffer.insert_char(*ch, x + idx as u16, y); + } + + let line_data = ui.buffer.get_line(y); + + // 考虑长度包含\n,所以要减1 + ui.cursor.write(String::from_utf8_lossy( + &line_data.data[x as usize..(line_data.size() - 1)], + ))?; + + ui.cursor.move_to_columu(x + data.len() as u16)?; + ui.set_edited(); + ui.cursor.highlight(None)?; + Ok(WarpUiCallBackType::None) + } +} + +#[derive(Debug)] +pub struct LastLine { + buf: Mutex>, +} + +impl LastLine { + pub fn new() -> Self { + Self { + buf: Mutex::new(vec![':' as u8]), + } + } + + pub fn reset(&self) { + self.buf.lock().unwrap().resize(1, ':' as u8); + } +} + +impl KeyEventCallback for LastLine { + fn enter(&self, ui: &mut MutexGuard) -> io::Result { + let mut buf = self.buf.lock().unwrap(); + let cmd = String::from_utf8_lossy(&buf).to_string(); + + let ret = LastLineCommand::process(ui, cmd); + + ui.cursor.move_to(1, u16::MAX - 1)?; + // ui.cursor.move_to_columu(1)?; + TermManager::clear_until_new_line()?; + ui.cursor.move_to(1, u16::MAX - 1)?; + + buf.resize(1, 0); + if ret == WarpUiCallBackType::None { + ui.cursor.restore_pos()?; + return Ok(WarpUiCallBackType::ChangMode(ModeType::Command)); + } + + Ok(ret) + } + + fn tab(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + + fn backspace(&self, ui: &mut MutexGuard) -> io::Result { + if ui.cursor.x() == 1 { + return Ok(WarpUiCallBackType::None); + } + + self.left(ui)?; + self.buf.lock().unwrap().remove(ui.cursor.x() as usize); + + ui.cursor.write(' ')?; + self.left(ui)?; + + Ok(WarpUiCallBackType::None) + } + + fn up(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + + fn down(&self, _ui: &mut MutexGuard) -> io::Result { + Ok(WarpUiCallBackType::None) + } + + fn esc(&self, ui: &mut MutexGuard) -> io::Result { + ui.cursor.restore_pos()?; + Ok(WarpUiCallBackType::ChangMode(ModeType::Command)) + } + + fn input_data( + &self, + ui: &mut MutexGuard, + data: &[u8], + ) -> io::Result { + let mut buf = self.buf.lock().unwrap(); + + if ui.cursor.x() == buf.len() as u16 { + buf.extend(data); + } else { + let index = ui.cursor.x() as usize; + for (i, &item) in data.iter().enumerate() { + buf.insert(index + i, item); + } + } + + ui.cursor.write(String::from_utf8_lossy(&data))?; + + Ok(WarpUiCallBackType::None) + } +} diff --git a/src/utils/ui/uicore.rs b/src/utils/ui/uicore.rs new file mode 100644 index 0000000..e805438 --- /dev/null +++ b/src/utils/ui/uicore.rs @@ -0,0 +1,423 @@ +use std::{ + io, + sync::{atomic::AtomicU16, Arc, Mutex, MutexGuard, Once, RwLock, Weak}, +}; + +use crossterm::{ + style::Color, + terminal::{self}, +}; +use lazy_static::lazy_static; + +use crate::{ + config::appconfig::AppSetting, + utils::{ + buffer::EditBuffer, cursor::CursorCrtl, style::StyleManager, terminal::TermManager, + ui::InfoLevel, + }, +}; + +#[cfg(feature = "dragonos")] +use crate::utils::input::Input; + +use super::{ + mode::mode::{Command, InputMode, Insert, LastLine, ModeType}, + AppInfo, +}; + +lazy_static! { + static ref COMMAND: Arc = Arc::new(Command); + static ref INSERT: Arc = Arc::new(Insert); + static ref LASTLINE: Arc = Arc::new(LastLine::new()); + pub static ref APP_INFO: Mutex = Mutex::new(AppInfo { + level: InfoLevel::Info, + info: String::new() + }); +} + +pub static TAB_SIZE: AtomicU16 = AtomicU16::new(4); + +#[derive(Debug, Clone, Copy)] +pub struct WinSize { + pub cols: u16, + pub rows: u16, +} + +#[derive(Debug)] +pub struct UiCore { + pub buffer: Arc, + pub cursor: CursorCrtl, + + #[allow(dead_code)] + setting: AppSetting, + container: Weak, + + edited: bool, + edited_once: Once, +} + +impl UiCore { + pub fn new(buf: Arc, cursor: CursorCrtl, setting: AppSetting) -> Self { + Self { + buffer: buf, + cursor, + container: Weak::new(), + setting, + edited: false, + edited_once: Once::new(), + } + } + + pub fn edited(&self) -> bool { + self.edited + } + + pub fn set_edited(&mut self) { + self.edited_once.call_once(|| self.edited = true) + } + + pub fn update_bottom_state_bar(&mut self) -> io::Result<()> { + let container = self.container.upgrade().unwrap(); + let mode = container.mode.read().unwrap().mode_type(); + if mode == ModeType::LastLine { + return Ok(()); + } + + let size = *WINSIZE.read().unwrap(); + + let store_x = self.cursor.x(); + let store_y = self.cursor.y(); + + self.cursor.set_prefix_mode(false); + + DEF_STYLE.read().unwrap().set_cmd_style()?; + let cmd_y = size.rows - 1; + self.cursor.move_to_row(cmd_y)?; + self.cursor.clear_current_line()?; + self.cursor + .write_with_pos(format!("{mode:?}"), 0, cmd_y, false)?; + + let (buf_x, buf_y) = (store_x, store_y + 1 + self.buffer.offset() as u16); + let index_info = format!("row:{buf_y} col:{buf_x}"); + let len = index_info.len() as u16; + self.cursor + .write_with_pos(index_info, size.cols - len, cmd_y, false)?; + + self.cursor.set_prefix_mode(true); + self.cursor.move_to(store_x, store_y)?; + + let mut info = APP_INFO.lock().unwrap(); + info.level.set_style()?; + self.cursor + .write_with_pos(&info.info, size.cols / 3, cmd_y, false)?; + + info.reset(); + self.cursor.move_to(store_x, store_y)?; + + StyleManager::reset_color()?; + + Ok(()) + } + + /// 渲染部分文件内容,从y行开始渲染count行 + /// 返回实际渲染行数 + pub fn render_content(&mut self, mut y: u16, mut count: usize) -> io::Result { + y += UI_HEAD_OFFSET; + let content_winsize = *CONTENT_WINSIZE.read().unwrap(); + + // 超出正文范围 + if y + count as u16 > content_winsize.rows { + count = (content_winsize.rows - y) as usize; + } + + let def_style = *DEF_STYLE.read().unwrap(); + + let content = self.buffer.get_content(y as usize, count); + + if content.is_none() { + return Ok(0); + } + let content = content.unwrap(); + + // 保存光标 + let pos = self.cursor.store_tmp_pos(); + + let tmp = y; + + let num_len = (tmp + content_winsize.rows).to_string().len(); + + self.cursor.set_prefix_mode(false); + for line in content.iter() { + let str = String::from_utf8_lossy(&line.data).to_string(); + def_style.set_content_style()?; + + // 移动 + self.cursor + .move_to(num_len as u16 + 2 + CursorCrtl::PREFIX_COL, y)?; + self.cursor.clear_current_line()?; + self.cursor.write(str)?; + y += 1; + StyleManager::reset_color()?; + } + + self.cursor.update_line_prefix(&content, tmp, num_len)?; + self.cursor.set_prefix_mode(true); + + self.cursor.restore_tmp_pos(pos)?; + + self.cursor.highlight(None)?; + + Ok(content.len()) + } + + // 将正文向上滚动count行 + pub fn scroll_up(&mut self, mut count: u16) -> io::Result<()> { + let winsize = *CONTENT_WINSIZE.read().unwrap(); + + let pos = self.cursor.store_tmp_pos(); + + // 计算最多还能滚动多少行 + let offset = self.buffer.offset(); + + // 最多出两行 + let linecount = self.buffer.line_count(); + if offset + winsize.rows as usize + count as usize >= linecount { + count = linecount as u16 - offset as u16 - winsize.rows; + } + self.buffer.set_offset(offset + count as usize); + // 将光标移动到滚动后的位置 + self.cursor.move_to_row(winsize.rows - count)?; + + // 执行滚动 + TermManager::scroll_up(count)?; + + // 清除光标以下的内容 + TermManager::clear_under_cursor()?; + + // 渲染count行数据 + self.render_content(self.cursor.y(), count as usize)?; + + self.cursor.restore_tmp_pos(pos)?; + + self.cursor.highlight(Some(self.cursor.y() - count))?; + Ok(()) + } + + pub fn scroll_down(&mut self, mut count: u16) -> io::Result<()> { + let pos = self.cursor.store_tmp_pos(); + + // 计算最多还能滚动多少行 + let offset = self.buffer.offset(); + if offset < count as usize { + count = offset as u16; + } + + self.buffer.set_offset(offset - count as usize); + // 将光标移动第count行 + + // 执行滚动 + TermManager::scroll_down(count)?; + + self.cursor.move_to_row(count - 1)?; + // 清除光标以上的内容 + TermManager::clear_up_cursor()?; + + // 渲染count行数据 + self.render_content(0, count as usize)?; + + self.cursor.restore_tmp_pos(pos)?; + + self.cursor.highlight(Some(self.cursor.y() + count))?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct Ui { + pub core: Arc>, + pub mode: RwLock>, +} + +lazy_static! { + pub static ref WINSIZE: RwLock = { + let size = terminal::size().unwrap(); + RwLock::new(WinSize { + cols: size.0, + rows: size.1, + }) + }; + pub static ref CONTENT_WINSIZE: RwLock = { + let size = *WINSIZE.read().unwrap(); + RwLock::new(WinSize { + cols: size.cols, + rows: size.rows - UI_CMD_HEIGHT - UI_HEAD_OFFSET, + }) + }; + pub static ref DEF_STYLE: RwLock = { + let style = UiStyle { + content_fg: Some(Color::White), + content_bg: None, + cmd_line_fg: Some(Color::White), + cmd_line_bg: Some(Color::DarkCyan), + }; + + RwLock::new(style) + }; +} + +pub static UI_HEAD_OFFSET: u16 = 0; +pub const UI_CMD_HEIGHT: u16 = 1; + +impl Ui { + pub fn new(buf: Arc, setting: AppSetting) -> Arc { + let mut cursor = CursorCrtl::new(buf.clone(), setting.line); + cursor.move_to(0, 0).unwrap(); + + let core = Arc::new(Mutex::new(UiCore::new(buf, cursor, setting))); + let ret = Arc::new(Self { + mode: RwLock::new(Arc::new(Command)), + core: core.clone(), + }); + + core.lock().unwrap().container = Arc::downgrade(&ret); + + ret + } + pub fn init_ui() -> io::Result<()> { + TermManager::init_term()?; + Ok(()) + } + + pub fn start_page_ui(&self) -> io::Result<()> { + StyleManager::set_foreground_color(Color::Cyan)?; + let mut core = self.core.lock().unwrap(); + core.cursor + .write_with_pos("Held - DragonOS/Linux Term Editor\n", 5, 0, false)?; + StyleManager::set_foreground_color(Color::Green)?; + core.cursor + .write_with_pos("Author: heyicong@dragonos.org\n", 7, 1, false)?; + StyleManager::set_foreground_color(Color::DarkMagenta)?; + core.cursor + .write_with_pos("Type any key to continue ><\n", 8, 2, false)?; + StyleManager::reset_color()?; + + core.cursor.move_to(0, 0)?; + + #[cfg(feature = "dragonos")] + let _ = Input::wait_keydown(); + + #[cfg(not(feature = "dragonos"))] + loop { + let ev = crossterm::event::read()?; + if let crossterm::event::Event::Key(_) = ev { + break; + } + } + + TermManager::clear_all()?; + + Ok(()) + } + + pub fn ui_loop(&self) -> io::Result { + let mut core = self.core.lock().unwrap(); + core.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + core.update_bottom_state_bar()?; + core.cursor.move_to(0, 0)?; + core.cursor.highlight(None)?; + loop { + #[cfg(feature = "dragonos")] + let callback = { + let key = Input::wait_keydown()?; + self.mode.read().unwrap().key_event_route(&mut core, key)? + }; + + #[cfg(not(feature = "dragonos"))] + let callback = { + let ev = crossterm::event::read()?; + self.mode.read().unwrap().event_route(&mut core, ev)? + }; + + match callback { + super::event::WarpUiCallBackType::ChangMode(mode) => { + self.set_mode(mode, &mut core)? + } + super::event::WarpUiCallBackType::None => {} + super::event::WarpUiCallBackType::Exit(store) => { + self.ui_exit(); + return Ok(store); + } + } + + if self.mode.read().unwrap().mode_type() != ModeType::LastLine { + core.update_bottom_state_bar()?; + } + } + } + + fn set_mode(&self, mode: ModeType, ui: &mut MutexGuard) -> io::Result<()> { + if mode != ModeType::LastLine { + ui.cursor.set_prefix_mode(true); + + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + } + match mode { + ModeType::Command => *self.mode.write().unwrap() = COMMAND.clone(), + ModeType::LastLine => { + ui.cursor.set_prefix_mode(false); + let lastline = LASTLINE.clone(); + lastline.reset(); + *self.mode.write().unwrap() = lastline; + + ui.cursor.move_to(0, u16::MAX - 1)?; + DEF_STYLE.read().unwrap().set_cmd_style()?; + // 写一个空行 + ui.cursor.clear_current_line()?; + ui.cursor.move_to_columu(0)?; + ui.cursor.write(':')?; + } + ModeType::Insert => *self.mode.write().unwrap() = INSERT.clone(), + } + + Ok(()) + } + + fn ui_exit(&self) { + // 处理未保存退出时的提醒 + } +} + +#[derive(Debug, Clone, Copy)] +pub struct UiStyle { + pub content_fg: Option, + pub content_bg: Option, + pub cmd_line_fg: Option, + pub cmd_line_bg: Option, +} + +impl UiStyle { + pub fn set_cmd_style(&self) -> io::Result<()> { + StyleManager::reset_color()?; + if self.cmd_line_bg.is_some() { + StyleManager::set_background_color(self.cmd_line_bg.unwrap())?; + } + if self.cmd_line_fg.is_some() { + StyleManager::set_foreground_color(self.cmd_line_fg.unwrap())?; + } + + Ok(()) + } + + pub fn set_content_style(&self) -> io::Result<()> { + StyleManager::reset_color()?; + if self.content_bg.is_some() { + StyleManager::set_background_color(self.content_bg.unwrap())?; + } + if self.content_fg.is_some() { + StyleManager::set_foreground_color(self.content_fg.unwrap())?; + } + + Ok(()) + } +}