diff --git a/Cargo.toml b/Cargo.toml index 4479d9e..8f4bd13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,5 @@ serde_yaml = "0.9" # 定义标志位 bitflags = "2.4.2" +# 正则表达式 +regex = "1.10.6" diff --git a/src/utils/buffer.rs b/src/utils/buffer.rs index b4b220c..de7353b 100644 --- a/src/utils/buffer.rs +++ b/src/utils/buffer.rs @@ -251,6 +251,90 @@ impl EditBuffer { } } + #[inline] + pub fn get_range(&self, start_pos: (u16, u16), end_pos: (u16, u16)) -> Vec { + let buf = self.buf.read().unwrap(); + let mut ret = Vec::new(); + let start = self.offset.load(Ordering::SeqCst) + start_pos.1 as usize; + let end = self.offset.load(Ordering::SeqCst) + end_pos.1 as usize; + if start == end { + let line = buf.get(start).unwrap(); + let start_pos = start_pos.0 as usize; + let end_pos = end_pos.0 as usize; + ret.push(LineBuffer::new(line[start_pos..end_pos].to_vec())); + return ret; + } + // 从起始行到结束行 + for (idx, line) in buf.iter().enumerate().skip(start).take(end - start + 1) { + let mut newline = line.clone(); + if idx == start { + newline.data.drain(0..start_pos.0 as usize); + } else if idx == end { + newline.data.drain(end_pos.0 as usize..); + } + ret.push(newline); + } + ret + } + + #[inline] + pub fn get_range_str(&self, start_pos: (u16, u16), end_pos: (u16, u16)) -> String { + let buf = self.buf.read().unwrap(); + let mut ret = String::new(); + let start = self.offset.load(Ordering::SeqCst) + start_pos.1 as usize; + let end = self.offset.load(Ordering::SeqCst) + end_pos.1 as usize; + if start == end { + let line = buf.get(start).unwrap(); + let start_pos = start_pos.0 as usize; + let end_pos = end_pos.0 as usize; + return String::from_utf8_lossy(&line[start_pos..end_pos]).to_string(); + } + // 从起始行到结束行 + for (idx, line) in buf.iter().enumerate().skip(start).take(end - start + 1) { + if idx == start { + ret.push_str(&String::from_utf8_lossy(&line[start_pos.0 as usize..]).to_string()); + } else if idx == end { + ret.push_str(&String::from_utf8_lossy(&line[..end_pos.0 as usize]).to_string()); + } else { + ret.push_str(&String::from_utf8_lossy(&line).to_string()); + } + } + ret + } + + #[inline] + pub fn get_offset_by_pos(&self, x: u16, y: u16) -> usize { + let mut offset = 0; + let mut abs_y = self.offset(); + let buf = self.buf.read().unwrap(); + for line in buf[abs_y..].iter() { + if abs_y == y as usize + self.offset() { + offset += x as usize; + break; + } + offset += line.size(); + abs_y += 1; + } + offset + } + + pub fn get_pos_by_offset(&self, offset: usize) -> (u16, u16) { + let mut x = 0; + let mut y = 0; + let abs_y = self.offset(); + let buf = self.buf.read().unwrap(); + let mut offset = offset; + for line in buf[abs_y..].iter() { + if offset < line.size() { + x = offset as u16; + break; + } + offset -= line.size(); + y += 1; + } + (x, y) + } + #[inline] pub fn all_buffer(&self) -> Vec { self.buf.read().unwrap().clone() @@ -352,9 +436,9 @@ impl EditBuffer { if offset + win_rows > max_line { // 最底下无数据,则调整 - let adapt_offset = max_line - win_rows; + let adapt_offset = max_line.saturating_sub(win_rows); - y += offset - adapt_offset; + y = y.saturating_add(offset).saturating_sub(adapt_offset); offset = adapt_offset; } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2427952..097e1ce 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,6 +5,7 @@ pub mod file; #[cfg(feature = "dragonos")] pub mod input; pub mod log_util; +pub mod reg; pub mod style; pub mod term_io; pub mod terminal; diff --git a/src/utils/reg.rs b/src/utils/reg.rs new file mode 100644 index 0000000..b987994 --- /dev/null +++ b/src/utils/reg.rs @@ -0,0 +1,33 @@ +use super::buffer::LineBuffer; + +#[derive(Debug)] +pub struct Register { + pub text: Vec, +} + +impl Register { + pub fn is_muti_line(&self) -> bool { + return self.text.len() > 1; + } + + pub fn is_single_line(&self) -> bool { + return self.text.len() == 1 && self.text[0].ends_with(b"\n"); + } + + pub fn new() -> Register { + Register { text: Vec::new() } + } + + pub fn from(lines: Vec) -> Register { + Register { text: lines } + } + + pub fn copy(&mut self, lines: Vec) { + self.text.clear(); + self.text = lines; + } + + pub fn push(&mut self, line: &str) { + self.text.push(LineBuffer::new(line.as_bytes().to_vec())); + } +} diff --git a/src/utils/ui/mode/common.rs b/src/utils/ui/mode/common.rs index d0fa72c..9a3de23 100644 --- a/src/utils/ui/mode/common.rs +++ b/src/utils/ui/mode/common.rs @@ -1,4 +1,4 @@ -use std::{io, sync::MutexGuard}; +use std::{io, sync::MutexGuard, usize}; use crate::utils::{ terminal::TermManager, @@ -118,6 +118,35 @@ pub trait CommonOp: KeyEventCallback { return (next_end_pos.min(linesize as u16 - 1), abs_y); } + fn paste(&self, ui: &mut MutexGuard) -> io::Result<()> { + let x = ui.cursor.x(); + let y = ui.cursor.y(); + if ui.register.text.is_empty() { + return Ok(()); + } + if ui.register.is_single_line() { + // 单行 + ui.buffer.insert_line(y.into(), &ui.register.text[0]); + } else if ui.register.is_muti_line() { + // 多行 + for (idx, line) in ui.register.text.iter().enumerate() { + for (idy, c) in line.data.iter().enumerate() { + ui.buffer.insert_char(*c, x + idy as u16, y + idx as u16); + } + ui.buffer + .input_enter(line.data.len() as u16, y + idx as u16); + } + } else { + // 单词 + let line = &ui.register.text[0]; + for (idx, c) in line.data.iter().enumerate() { + ui.buffer.insert_char(*c, x + idx as u16, y); + } + } + let rest_line = ui.buffer.line_count() - y as usize - ui.buffer.offset(); + ui.render_content(y, rest_line)?; + Ok(()) + } /// 定位到下一个单词的首字母,返回绝对坐标 fn locate_next_word(&self, ui: &mut MutexGuard, abs_pos: (u16, u16)) -> (u16, u16) { let linesize = ui.buffer.get_linesize_abs(abs_pos.1) as usize; diff --git a/src/utils/ui/mode/matching.rs b/src/utils/ui/mode/matching.rs new file mode 100644 index 0000000..3d2ce64 --- /dev/null +++ b/src/utils/ui/mode/matching.rs @@ -0,0 +1,168 @@ +use lazy_static::lazy_static; +use regex::Regex; +use std::{collections::HashMap, sync::MutexGuard}; + +use crate::utils::ui::uicore::{UiCore, CONTENT_WINSIZE}; +/// 匹配括号位置 +pub struct PairedPos { + pub start: (u16, u16), + pub end: (u16, u16), +} +lazy_static! { + pub static ref PAIRING: HashMap = { + let mut m = HashMap::new(); + m.insert('(', Regex::new(r"(?s)\(.*?").unwrap()); + m.insert('[', Regex::new(r"(?s)\[.*?").unwrap()); + m.insert('{', Regex::new(r"(?s)\{.*?").unwrap()); + m.insert('<', Regex::new(r"(?s)<.*?").unwrap()); + m.insert('\'', Regex::new(r"(?s)\'(.*?)\'").unwrap()); + m.insert('"', Regex::new(r#"(?s)"(.*?)""#).unwrap()); + m.insert('`', Regex::new(r"(?s)`(.*?)`").unwrap()); + m.insert(')', Regex::new(r"\)").unwrap()); + m.insert(']', Regex::new(r"\]").unwrap()); + m.insert('}', Regex::new(r"\}").unwrap()); + m.insert('>', Regex::new(r"\>").unwrap()); + m + }; +} + +fn is_left(pat: char) -> bool { + let left = ['(', '[', '{', '<']; + left.contains(&pat) +} + +fn is_right(pat: char) -> bool { + let right = [')', ']', '}', '>']; + right.contains(&pat) +} + +fn is_quote(pat: char) -> bool { + let quote = ['\'', '"', '`']; + quote.contains(&pat) +} + +fn get_pair(pat: char) -> char { + match pat { + '(' => ')', + '[' => ']', + '{' => '}', + '<' => '>', + '\'' => '\'', + '"' => '"', + '`' => '`', + ')' => '(', + ']' => '[', + '}' => '{', + '>' => '<', + _ => unreachable!(), + } +} +/// 获取括号文本 +pub fn find_pair(ui: &mut MutexGuard, pat: u8) -> Option { + let win_rows = CONTENT_WINSIZE.read().unwrap().rows; + // 搜索范围为整个屏幕 + // 把Vec转换为String,因为Regex::find_at()需要String,而且二维变一维方便迭代 + let content = ui.buffer.get_range_str((0, 0), (0, win_rows - 1)); + let x = ui.cursor.x(); + let y = ui.cursor.y(); + let offset = ui.buffer.get_offset_by_pos(x, y); + get_nested_pair(&content, pat as char, offset, ui) +} + +/// 获取匹配的括号 +/// @param text: 文本 +/// @param pat: 括号 +/// @param pos: 光标位置转换后的偏移量 +/// @return: 匹配的括号文本 +fn get_nested_pair( + text: &str, + pat: char, + pos: usize, + ui: &mut MutexGuard, +) -> Option { + let regex = PAIRING.get(&pat)?; + let mtch = regex.find_at(text, pos); + + if let Some(m) = mtch { + let (start, end) = (m.start(), m.end()); + let new_cursor_start = ui.buffer.get_pos_by_offset(start); + + match pat { + _ if is_quote(pat) => { + ui.cursor + .move_to(new_cursor_start.0, new_cursor_start.1) + .ok()?; + return Some(PairedPos { + start: new_cursor_start, + end: ui.buffer.get_pos_by_offset(end), + }); + } + _ if is_left(pat) => { + ui.cursor + .move_to(new_cursor_start.0, new_cursor_start.1) + .ok()?; + return find_matching_right(text, pat, start, ui); + } + _ if is_right(pat) => { + return find_matching_left(text, pat, end, ui); + } + _ => None, + } + } else { + None + } +} + +fn find_matching_right( + text: &str, + left_pat: char, + start: usize, + ui: &mut MutexGuard, +) -> Option { + let right_pat = get_pair(left_pat); + let mut stack = Vec::new(); + + for (idx, c) in text[start..].chars().enumerate() { + if c == left_pat { + stack.push(c); + } else if c == right_pat { + stack.pop(); + if stack.is_empty() { + let end = idx + start; + return Some(PairedPos { + start: ui.buffer.get_pos_by_offset(start), + end: ui.buffer.get_pos_by_offset(end + 1), + }); + } + } + } + None +} + +fn find_matching_left( + text: &str, + right_pat: char, + end: usize, + ui: &mut MutexGuard, +) -> Option { + let left_pat = get_pair(right_pat); + let mut stack = Vec::new(); + let chars: Vec = text[..=end].chars().collect(); + + for (idx, c) in chars.iter().enumerate().rev() { + if *c == right_pat { + stack.push(*c); + } else if *c == left_pat { + stack.pop(); + if stack.is_empty() { + let new_cursor = ui.buffer.get_pos_by_offset(idx); + ui.cursor.move_to(new_cursor.0, new_cursor.1).ok()?; + return Some(PairedPos { + start: ui.buffer.get_pos_by_offset(idx), + end: ui.buffer.get_pos_by_offset(end), + }); + } + } + } + None +} diff --git a/src/utils/ui/mode/mod.rs b/src/utils/ui/mode/mod.rs index 42da31a..4c1bb35 100644 --- a/src/utils/ui/mode/mod.rs +++ b/src/utils/ui/mode/mod.rs @@ -1,4 +1,5 @@ pub mod common; +pub mod matching; pub mod mode; pub mod normal; pub mod state; diff --git a/src/utils/ui/mode/normal.rs b/src/utils/ui/mode/normal.rs index 9bd99ca..5fe60e5 100644 --- a/src/utils/ui/mode/normal.rs +++ b/src/utils/ui/mode/normal.rs @@ -8,6 +8,8 @@ use std::io; use std::sync::{Mutex, MutexGuard}; use super::common::CommonOp; +use super::matching::find_pair; +use super::matching::PAIRING; use super::mode::ModeType; use super::state::StateMachine; use crate::utils::ui::mode::state::StateCallback; @@ -109,7 +111,6 @@ impl KeyEventCallback for Normal { return Ok(WarpUiCallBackType::ChangMode(ModeType::LastLine)); } } - b"$" => normal_state.on_dollar_clicked(), b"e" => normal_state.on_e_clicked(ui), @@ -120,6 +121,10 @@ impl KeyEventCallback for Normal { b"x" => normal_state.on_x_clicked(), + b"y" => normal_state.on_y_clicked(), + + b"p" => normal_state.on_p_clicked(), + b"o" => normal_state.on_o_clicked(), b"O" => normal_state.on_O_clicked(), @@ -295,6 +300,8 @@ impl NormalState { pub fn on_i_clicked(&mut self) { if self.cmdchar.is_none() { self.cmdchar = Some('i'); + } else { + self.buf_op_arg = Some(BufOpArg::Inside); } } pub fn exec_i_cmd(&mut self, _ui: &mut MutexGuard) -> io::Result { @@ -318,6 +325,8 @@ impl NormalState { pub fn on_a_clicked(&mut self) { if self.cmdchar.is_none() { self.cmdchar = Some('a'); + } else { + self.buf_op_arg = Some(BufOpArg::Around) } } @@ -582,12 +591,12 @@ impl NormalState { if self.cmdbuf.len() < 2 { return Ok(StateCallback::None); } - let to_find = self.cmdbuf.last().unwrap().clone() as char; + let to_find = self.cmdbuf.last().unwrap(); let old_x = ui.cursor.x(); let old_y = ui.cursor.y(); let line = String::from_utf8_lossy(&ui.buffer.get_line(old_y)[old_x as usize..]).to_string(); - let pos = line.find(to_find); + let pos = line.find(*to_find as char); if pos.is_none() { return Ok(StateCallback::None); } @@ -608,12 +617,12 @@ impl NormalState { if self.cmdbuf.len() < 2 { return Ok(StateCallback::None); } - let to_find = self.cmdbuf.last().unwrap().clone() as char; + let to_find = self.cmdbuf.last().unwrap(); let old_x = ui.cursor.x(); let old_y = ui.cursor.y(); let line = String::from_utf8_lossy(&ui.buffer.get_line(old_y)[..old_x as usize]).to_string(); - let pos = line.rfind(to_find); + let pos = line.rfind(*to_find as char); if pos.is_none() { return Ok(StateCallback::None); } @@ -662,6 +671,77 @@ impl NormalState { self.move_to_nlines_of_screen(ui, win_size / 2)?; return Ok(StateCallback::Reset); } + + fn on_y_clicked(&mut self) { + match self.cmdchar { + Some('y') => { + self.buf_op_arg = Some(BufOpArg::Line); + } + None => { + self.cmdchar = Some('y'); + } + _ => {} + } + } + + fn exec_y_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + match self.buf_op_arg { + Some(BufOpArg::Line) => { + let y = ui.cursor.y(); + let line = ui.buffer.get_line(y); + ui.register.copy(vec![line]); + return Ok(StateCallback::Reset); + } + Some(BufOpArg::Word) => { + let curr_pos = (ui.cursor.x(), ui.cursor.y() + ui.buffer.offset() as u16); + let next_pos = self.locate_next_word(ui, curr_pos); + let text_copy = ui.buffer.get_range(curr_pos, next_pos); + ui.register.copy(text_copy); + return Ok(StateCallback::Reset); + } + Some(BufOpArg::Around) => { + let pat = self.cmdbuf.last().unwrap(); + if PAIRING.contains_key(&(*pat as char)) { + find_pair(ui, *pat).map(|paired_pos| { + let content = ui.buffer.get_range(paired_pos.start, paired_pos.end); + ui.register.copy(content); + }); + return Ok(StateCallback::Reset); + } + } + Some(BufOpArg::Inside) => { + let pat = self.cmdbuf.last().unwrap(); + if PAIRING.contains_key(&(*pat as char)) { + find_pair(ui, *pat).map(|paired_pos| { + let start = (paired_pos.start.0 + 1, paired_pos.start.1); + let end = (paired_pos.end.0 - 1, paired_pos.end.1); + let content = ui.buffer.get_range(start, end); + ui.register.copy(content); + }); + return Ok(StateCallback::Reset); + } + } + _ => {} + } + return Ok(StateCallback::None); + } + + fn on_p_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('p'); + } + } + + fn exec_p_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let count = match self.count { + Some(count) => count, + None => 1, + }; + for _ in 0..count { + self.paste(ui)?; + } + return Ok(StateCallback::Reset); + } } impl StateMachine for NormalState { @@ -695,6 +775,8 @@ impl StateMachine for NormalState { 'f' => self.exec_f_cmd(ui), 'F' => self.exec_F_cmd(ui), 'x' => self.exec_x_cmd(ui), + 'y' => self.exec_y_cmd(ui), + 'p' => self.exec_p_cmd(ui), 'o' => self.exec_o_cmd(ui), 'O' => self.exec_O_cmd(ui), 'a' => self.exec_a_cmd(ui), diff --git a/src/utils/ui/uicore.rs b/src/utils/ui/uicore.rs index ccf86ad..1ce4f35 100644 --- a/src/utils/ui/uicore.rs +++ b/src/utils/ui/uicore.rs @@ -12,8 +12,8 @@ use lazy_static::lazy_static; use crate::{ config::appconfig::AppSetting, utils::{ - buffer::EditBuffer, cursor::CursorCrtl, style::StyleManager, terminal::TermManager, - ui::InfoLevel, + buffer::EditBuffer, cursor::CursorCrtl, reg::Register, style::StyleManager, + terminal::TermManager, ui::InfoLevel, }, }; @@ -49,6 +49,7 @@ pub struct WinSize { pub struct UiCore { pub buffer: Arc, pub cursor: CursorCrtl, + pub register: Register, #[allow(dead_code)] setting: AppSetting, @@ -67,6 +68,7 @@ impl UiCore { setting, edited: false, edited_once: Once::new(), + register: Register::new(), } }