diff --git a/src/utils/buffer.rs b/src/utils/buffer.rs index b0471e9..b4b220c 100644 --- a/src/utils/buffer.rs +++ b/src/utils/buffer.rs @@ -79,7 +79,6 @@ pub struct EditBuffer { locked_lines: RwLock>, } -#[allow(unused)] impl EditBuffer { pub fn new(buf: Vec) -> Self { let mut lines = buf @@ -157,6 +156,18 @@ impl EditBuffer { line.data.len() as u16 } + pub fn get_linesize_abs(&self, line: u16) -> u16 { + let buf = self.buf.read().unwrap(); + let line = buf.get(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(); @@ -379,7 +390,11 @@ impl EditBuffer { pub fn delete_line(&self, y: usize) { let mut buffer = self.buf.write().unwrap(); - let line = buffer.get(y).unwrap(); + let line = buffer.get(y); + if line.is_none() { + return; + } + let line = line.unwrap(); if line.data.is_empty() { return; } @@ -389,17 +404,20 @@ impl EditBuffer { } } + /// 删除 y 行 0..x 的字符 pub fn delete_until_line_beg(&self, x: usize, y: usize) -> Option { let mut buffer = self.buf.write().unwrap(); let line = buffer.get_mut(y).unwrap(); - if line.data.len() < 2 { + let len = line.data.len(); + if len < 2 { return None; } - line.data.drain(0..x); + line.data.drain(0..x.min(len - 1)); return Some(x - 1); } + /// 删除 y 行 x..end 的字符 pub fn delete_until_endl(&self, x: usize, y: usize) -> Option { let mut buffer = self.buf.write().unwrap(); let line = buffer.get_mut(y).unwrap(); @@ -418,13 +436,17 @@ impl EditBuffer { let mut right = left; let linesize = self.get_linesize(y) as usize; let buf = self.buf.read().unwrap(); - let line = buf - .get(self.offset.load(Ordering::SeqCst) + y as usize) - .unwrap(); + let line = match buf.get(self.offset.load(Ordering::SeqCst) + y as usize) { + Some(line) => line, + None => return x as usize, + }; while left <= right && right < linesize { let lchar = line[left] as char; let rchar = line[right] as char; + if rchar.is_ascii_punctuation() && right != x.into() { + break; + } if !(lchar == ' ' || lchar == '\t') { left += 1; right += 1; @@ -446,13 +468,17 @@ impl EditBuffer { let mut right = left; let linesize = self.get_linesize(y) as usize; let buf = self.buf.read().unwrap(); - let line = buf - .get(self.offset.load(Ordering::SeqCst) + y as usize) - .unwrap(); + let line = match buf.get(self.offset.load(Ordering::SeqCst) + y as usize) { + Some(line) => line, + None => return x as usize, + }; while left <= right && right < linesize { let lchar = line[left] as char; let rchar = line[right] as char; + if rchar.is_ascii_punctuation() && right != x.into() { + break; + } if lchar == ' ' || lchar == '\t' { left += 1; right += 1; @@ -477,14 +503,17 @@ impl EditBuffer { let mut left = x as i32; let mut right = left; let buf = self.buf.read().unwrap(); - let line = buf - .get(self.offset.load(Ordering::SeqCst) + y as usize) - .unwrap(); - + let line = match buf.get(self.offset.load(Ordering::SeqCst) + y as usize) { + Some(line) => line, + None => return Some(x as usize), + }; while left <= right && left >= 0 { let lchar = line[left as usize] as char; let rchar = line[right as usize] as char; + if lchar.is_ascii_punctuation() && left != x.into() { + return Some(left as usize); + } if rchar == ' ' || rchar == '\t' { left -= 1; right -= 1; @@ -503,6 +532,36 @@ impl EditBuffer { } return None; } + pub fn search_prevw_begin_abs(&self, x: u16, abs_y: u16) -> usize { + let mut left = x as i32; + let mut right = left; + let buf = self.buf.read().unwrap(); + let line = buf.get(abs_y as usize).unwrap(); + while left <= right && left >= 0 { + let lchar = line[left as usize] as char; + let rchar = line[right as usize] as char; + + if lchar.is_ascii_punctuation() && left != x.into() { + return left as usize; + } + if rchar == ' ' || rchar == '\t' { + left -= 1; + right -= 1; + continue; + } + + if lchar == ' ' || lchar == '\t' { + if left + 1 == x.into() { + right = left; + continue; + } + return left as usize + 1; + } + + left -= 1; + } + return 0; + } } bitflags! { diff --git a/src/utils/ui/mode/common.rs b/src/utils/ui/mode/common.rs new file mode 100644 index 0000000..9f8b6ba --- /dev/null +++ b/src/utils/ui/mode/common.rs @@ -0,0 +1,137 @@ +use std::{io, sync::MutexGuard}; + +use crate::utils::{ + terminal::TermManager, + ui::{ + event::{KeyEventCallback, WarpUiCallBackType}, + uicore::{UiCore, CONTENT_WINSIZE}, + }, +}; + +pub trait CommonOp: KeyEventCallback { + fn remove_line(&self, ui: &mut MutexGuard) -> io::Result<()> { + TermManager::clear_current_line()?; + TermManager::clear_under_cursor()?; + let y = ui.cursor.y() as usize; + let old_line_count = ui.buffer.line_count(); + let old_offset = ui.buffer.offset(); + + let count = old_line_count - y as usize; + ui.buffer.delete_line(y + ui.buffer.offset() as usize); + ui.render_content(y as u16, count.max(1))?; + + if y + old_offset == old_line_count - 1 { + self.up(ui)?; + } + + if old_line_count == 1 { + ui.cursor.move_to_columu(0)?; + ui.buffer.insert_char('\n' as u8, 0, 0); + ui.render_content(0, 1)?; + } + + Ok(()) + } + + fn remove_n_line(&self, ui: &mut MutexGuard, n: u16) -> io::Result<()> { + let linecount = ui.buffer.line_count() as u16; + let y = ui.cursor.y(); + + // 实际能删除的行数 + let to_delete = n.min(linecount - y); + for _ in 0..to_delete { + self.remove_line(ui)?; + } + Ok(()) + } + fn remove_word(&self, ui: &mut MutexGuard) -> io::Result<()> { + let x = ui.cursor.x(); + let y = ui.cursor.y(); + let next_word_pos = ui.buffer.search_nextw_begin(x, y); + let linesize = ui.buffer.get_linesize(y); + + // 如果下一个单词在当前行,则删除当前单词 + if next_word_pos < linesize.into() { + ui.buffer.remove_str(x, y, next_word_pos - x as usize); + } else { + // 如果下一个单词在下一行,则删除当前行剩余部分 + self.left(ui)?; + ui.buffer.delete_line(y.into()); + self.down(ui)?; + } + ui.render_content(y, 1)?; + return Ok(()); + } + fn jump_to_next_word(&self, ui: &mut MutexGuard) -> io::Result { + let x = ui.cursor.x(); + let y = ui.cursor.y(); + let pos = ui.buffer.search_nextw_begin(x, y); + let linesize = ui.buffer.get_linesize(y); + let abs_y = y + ui.buffer.offset() as u16; + + if pos < linesize as usize { + // 如果下一个单词在当前行,则移动光标到该单词的起始位置 + ui.cursor.move_to_columu(pos as u16)?; + } else if y as usize + ui.buffer.offset() < ui.buffer.line_count() - 1 { + // 如果当前行不是最后一行,则移动到下一行的单词起始位置 + let next_word_pos = ui.buffer.search_nextw_begin(0, y + 1) as u16; + let next_linesize = ui.buffer.get_linesize_abs(abs_y + 1); + self.down(ui)?; + ui.cursor + .move_to_columu(next_word_pos.min(next_linesize - 1))?; + ui.cursor.highlight(Some(y))?; + } else { + // 如果当前行是最后一行,则移动到当前行的末尾 + ui.cursor.move_to_columu(linesize as u16 - 1)?; + } + return Ok(WarpUiCallBackType::None); + } + fn move_to_line(&self, ui: &mut MutexGuard, line: u16) -> io::Result<()> { + let x = ui.cursor.x(); + let y = ui.cursor.y(); + let new_y = ui.buffer.goto_line(line as usize); + let new_x = x.min(ui.buffer.get_linesize(new_y)) as u16; + ui.cursor.move_to(new_x, new_y)?; + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + ui.cursor.highlight(Some(y))?; + return Ok(()); + } + + fn locate_prevw_begin(&self, ui: &mut MutexGuard, x: u16, abs_y: u16) -> (u16, u16) { + // 如果光标已在行首,则尝试移动到上一行的单词首字母 + if x == 0 { + if abs_y == 0 { + return (0, 0); + } + let last_y = abs_y - 1; + let end_of_prev_line = ui.buffer.get_linesize_abs(last_y) - 1; + let prev_word_pos = ui.buffer.search_prevw_begin_abs(end_of_prev_line, last_y); + return (prev_word_pos as u16, last_y); + } + + let prev_word_pos = ui.buffer.search_prevw_begin_abs(x, abs_y); + + return (prev_word_pos as u16, abs_y); + } + fn locate_nextw_ending(&self, ui: &mut MutexGuard, x: u16, y: u16) -> (u16, u16) { + let linesize = ui.buffer.get_linesize(y) as usize; + + // y的绝对位置 + let abs_y = ui.buffer.offset() as u16 + y; + // 如果光标已经在当前行的末尾或最后一个字符(x + 2),则尝试移动到下一行的末尾或单词末尾 + if x as usize + 2 >= linesize { + if abs_y < ui.buffer.line_count() as u16 - 1 { + let next_end_pos = ui.buffer.search_nextw_end(0, y + 1) as u16; + return (next_end_pos, abs_y + 1); + } else { + // 如果已经是最后一行,则保持光标在当前行的末尾 + let x = if linesize > 0 { linesize - 1 } else { 0 }; + return (x as u16, abs_y); + } + } + + let next_end_pos = ui.buffer.search_nextw_end(x, y) as u16; + // 如果下一个单词的末尾在当前行,则移动光标到该单词的末尾 + return (next_end_pos.min(linesize as u16 - 1), abs_y); + } +} diff --git a/src/utils/ui/mode/mod.rs b/src/utils/ui/mode/mod.rs index 7b7e0c7..cf5a3a5 100644 --- a/src/utils/ui/mode/mod.rs +++ b/src/utils/ui/mode/mod.rs @@ -1 +1,3 @@ +pub mod common; pub mod mode; +pub mod normal; diff --git a/src/utils/ui/mode/mode.rs b/src/utils/ui/mode/mode.rs index bd4ac12..08fd05b 100644 --- a/src/utils/ui/mode/mode.rs +++ b/src/utils/ui/mode/mode.rs @@ -18,6 +18,8 @@ use crate::utils::ui::{ use crate::utils::ui::event::WarpUiCallBackType; +use super::normal::Normal; + pub trait InputMode: KeyEventCallback + Debug { fn mode_type(&self) -> ModeType; @@ -105,6 +107,7 @@ pub enum ModeType { Command, LastLine, Insert, + Normal, } impl InputMode for Command { @@ -122,6 +125,11 @@ impl InputMode for Insert { ModeType::Insert } } +impl InputMode for Normal { + fn mode_type(&self) -> ModeType { + ModeType::Normal + } +} #[derive(Debug)] pub struct Command; @@ -557,6 +565,10 @@ impl KeyEventCallback for Command { return Ok(WarpUiCallBackType::None); } + b"n" => { + return Ok(WarpUiCallBackType::ChangMode(ModeType::Normal)); + } + b"H" => { self.move_to_nlines_of_screen(ui, 0)?; return Ok(WarpUiCallBackType::None); diff --git a/src/utils/ui/mode/normal.rs b/src/utils/ui/mode/normal.rs new file mode 100644 index 0000000..4bb712d --- /dev/null +++ b/src/utils/ui/mode/normal.rs @@ -0,0 +1,651 @@ +// 为了不影响 Command 模式的功能,参考Vim源码单独实现临时的 Normal 模式用于演示 + +// Normal模式下的状态机 + +// 在 input_data() 中处理输入的数据,根据输入的数据进行状态转移, +// 具体而言,根据输入数据及当前状态来更新状态机的参数,如命令字符,重复次数等 + +// 在 handle() 中处理状态的转移,根据状态的变化执行相应的操作 +// handle() 是真正作用于 ui 和 buffer 的地方,可以在这里调用 buffer 的方法,更新 ui 的显示 +// 因此,NormalState 提供给 handle() 的参数应该具有足够的一般性,以适应不同的需求 + +// 在 exit() 中处理状态的退出,清空状态 + +// 由此为 ndw,ndd 等命令的实现提供了多重字符匹配之外的方案: +// 状态机的下一个状态仅由输入 + 当前状态决定,避免了对输入的全局匹配 + +// 另:静态 HashMap 比起 match 性能更好是真的吗?后续是否考虑更换为 HashMap? +// 另:在现有框架下,增加一个新功能,需要在 input_data() 中增加一个分支,handle() 中增加一个分支 +// 是否有更简便的代码结构? + +// 参考:https://github.com/neovim/neovim/blob/master/src/nvim/normal.c#L89 + +use lazy_static::lazy_static; + +use crate::utils::ui::event::KeyEventCallback; +use crate::utils::ui::event::WarpUiCallBackType; +use crate::utils::ui::uicore::UiCore; +use crate::utils::ui::uicore::CONTENT_WINSIZE; +use std::io; +use std::sync::{Mutex, MutexGuard}; + +use super::common::CommonOp; +use super::mode::ModeType; + +#[derive(Debug)] +#[allow(dead_code)] +pub enum BufOpArg { + Around, // 操作引号内乃至引号的内容 + Inside, // 操作引号内的内容 + Line, // 操作整行 + Word, // 操作单词 + WordEnd, // 操作单词的末尾 + WordBegin, // 操作单词的开头 + Block, // 操作块 +} + +#[derive(Debug)] +pub struct NormalState { + pub cmdchar: Option, + pub count: Option, + pub count0: bool, + pub start_pos: Option<(u16, u16)>, + pub end_pos: Option<(u16, u16)>, + pub cmdbuf: Vec, + pub buf_op_arg: Option, +} + +impl CommonOp for NormalState {} + +lazy_static! { + static ref NORMALSTATE: Mutex = Mutex::new(NormalState { + cmdchar: None, // 命令开头的字符,通常决定了一类功能,如dw,dd系列命令 + count: None, // 命令的重复次数,如3j,4k + count0: false, // 是否将0作为命令的一部分,在normal模式下,0是一个独立的命令,也可能是一个数字的一部分 + start_pos: None, // 作用区域的起始位置 + end_pos: None, // 作用区域的结束位置 + cmdbuf: Vec::new(), // 用于存储输入的命令,可以与状态的显示通用? + buf_op_arg: None // 用于指定操作的区域,如daw,diw + }); +} + +#[derive(Debug)] +pub(crate) struct Normal; +impl Normal { + pub fn new() -> Self { + Self {} + } +} + +impl KeyEventCallback for Normal { + fn backspace(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::None); + } + fn esc(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::ChangMode(ModeType::Command)); + } + + fn enter(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::None); + } + fn tab(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::None); + } + fn input_data( + &self, + ui: &mut MutexGuard, + data: &[u8], + ) -> io::Result { + let mut normal_state = NORMALSTATE.lock().unwrap(); + normal_state.cmdbuf.extend_from_slice(data); + match data { + b"h" => { + normal_state.on_h_clicked(); + } + b"j" => { + normal_state.on_j_clicked(); + } + b"k" => { + normal_state.on_k_clicked(); + } + b"l" => { + normal_state.on_l_clicked(); + } + b"i" => { + normal_state.on_i_clicked(); + } + b"d" => { + normal_state.on_d_clicked(); + } + [b'1'..=b'9'] => { + normal_state.on_nonzero_clicked(data); + } + b"0" => { + normal_state.on_zero_clicked(); + } + b"w" => { + normal_state.on_w_clicked(); + } + b"g" => { + normal_state.on_g_clicked(ui); + } + b"G" => { + normal_state.on_G_clicked(ui); + } + b"b" => { + normal_state.on_b_clicked(ui); + } + b":" => { + if normal_state.cmdchar.is_none() { + ui.cursor.store_pos(); + return Ok(WarpUiCallBackType::ChangMode(ModeType::LastLine)); + } + } + b"$" => { + normal_state.on_dollar_clicked(); + } + b"e" => { + normal_state.on_e_clicked(ui); + } + b"f" => { + normal_state.on_f_clicked(); + } + b"F" => { + normal_state.on_F_clicked(); + } + b"x" => { + normal_state.on_x_clicked(); + } + _ => {} + } + return normal_state.handle(ui); + } +} + +impl KeyEventCallback for NormalState { + fn backspace(&self, ui: &mut MutexGuard) -> io::Result { + ui.cursor.move_left(1)?; + return Ok(WarpUiCallBackType::None); + } + + fn esc(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::ChangMode(ModeType::Command)); + } + + fn enter(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::None); + } + + fn tab(&self, _ui: &mut MutexGuard) -> io::Result { + return Ok(WarpUiCallBackType::None); + } + + fn input_data( + &self, + _ui: &mut MutexGuard, + _data: &[u8], + ) -> io::Result { + return Ok(WarpUiCallBackType::None); + } +} +impl NormalState { + pub fn reset(&mut self) { + self.cmdchar = None; + self.count = None; + self.count0 = false; + self.start_pos = None; + self.end_pos = None; + self.cmdbuf.clear(); + self.buf_op_arg = None; + } + + pub fn exec_0_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + ui.cursor.move_to_columu(0)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + pub fn on_h_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('h'); + } + } + /// 向左移动数列 + pub fn exec_h_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let old_x = ui.cursor.x(); + let exec_count = match self.count { + Some(count) => count.min(old_x as usize), + None => { + if old_x == 0 { + 0 + } else { + 1 + } + } // 如果在第一列,不再向左移动,防止溢出 + }; + let new_x = old_x - exec_count as u16; + ui.cursor.move_to_columu(new_x)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + pub fn on_j_clicked(&mut self) { + self.cmdchar = Some('j'); + } + /// 向下移动数行 + pub fn exec_j_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let old_y = ui.cursor.y(); + let old_abs_y = old_y + ui.buffer.offset() as u16; + // 限制最大移动行数 + let exec_count = match self.count { + Some(count) => count.min(ui.buffer.line_count() - old_abs_y as usize - 1), + None => 1, // goto_line 会自动处理最大值 + }; + let old_offset = ui.buffer.offset(); + let new_y = ui.buffer.goto_line(old_abs_y as usize + exec_count); + let new_linesize = ui.buffer.get_linesize(new_y); + let new_x = if new_linesize < ui.cursor.x() { + // 如果新行的长度小于原来的x坐标,将光标移动到新行的最后一个字符 + new_linesize - 1 + } else { + ui.cursor.x() + }; + ui.cursor.move_to(new_x, new_y)?; + ui.cursor.highlight(Some(old_y))?; + // 如果移动后,buffer的offset发生了变化,需要重新渲染 + if ui.buffer.offset() != old_offset { + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + } + self.reset(); + return Ok(WarpUiCallBackType::None); + } + pub fn on_k_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('k'); + } + } + + /// 向上移动数行 + pub fn exec_k_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let old_y = ui.cursor.y(); + let old_abs_y = old_y + ui.buffer.offset() as u16; + // 限制最大移动行数 + let exec_count = match self.count { + Some(count) => count.min(old_y as usize + ui.buffer.offset()), + None => { + if old_abs_y == 0 { + 0 + } else { + 1 + } + } // 如果在第一行,不再向上移动,防止溢出 + }; + let to_line = old_abs_y as usize - exec_count; + let old_offset = ui.buffer.offset(); + let new_y = ui.buffer.goto_line(to_line); + let new_linesize = ui.buffer.get_linesize(new_y); + let new_x = if new_linesize < ui.cursor.x() { + // 如果新行的长度小于原来的x坐标,将光标移动到新行的最后一个字符 + new_linesize - 1 + } else { + ui.cursor.x() + }; + ui.cursor.move_to(new_x, new_y)?; + ui.cursor.highlight(Some(old_y))?; + // 如果移动后,buffer的offset发生了变化,需要重新渲染 + if old_offset != ui.buffer.offset() { + ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + } + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + pub fn on_l_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('l'); + } + } + + /// 向右移动数列 + pub fn exec_l_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let old_x = ui.cursor.x(); + let linesize = ui.buffer.get_linesize(ui.cursor.y()) as usize; + let max_count = linesize - old_x as usize - 1; + let exec_count = match self.count { + Some(count) => count.min(max_count), + None => { + if old_x == linesize as u16 - 1 { + 0 + } else { + 1 + } + } + }; + let new_x = old_x + exec_count as u16; + ui.cursor.move_to_columu(new_x)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + pub fn on_i_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('i'); + } + } + pub fn exec_i_cmd(&mut self, _ui: &mut MutexGuard) -> io::Result { + self.exit()?; + return Ok(WarpUiCallBackType::ChangMode(ModeType::Insert)); + } + + /// 处理输入的非零数字 + pub fn on_nonzero_clicked(&mut self, data: &[u8]) { + let count = self.count; + if count.is_none() { + // 如果count为空,将第一个输入的数字作为count + let count = data[0] - b'0'; + self.count = Some(count as usize); + } else { + // 如果count不为空,将输入的数字添加到count的末尾 + let mut count = count.unwrap(); + count = count * 10 + (data[0] - b'0') as usize; + self.count = Some(count); + } + self.count0 = true; // 将后续输入的0作为执行次数的一部分 + } + + /// 处理输入的0 + pub fn on_zero_clicked(&mut self) { + // 如果0是命令的一部分,不再处理 + if !self.count0 && self.cmdchar.is_none() { + self.cmdchar = Some('0'); + self.count0 = true; + } + let count = self.count; + // 如果输入的是0,且count不为空,将count扩大10倍 + if count.is_some() { + let mut count = count.unwrap(); + count = count * 10; + self.count = Some(count); + } + } + + /// 处理输入的d + pub fn on_d_clicked(&mut self) { + match self.cmdchar { + None => { + // 处理d + self.cmdchar = Some('d'); + } + Some('d') => { + // 处理dd + self.buf_op_arg = Some(BufOpArg::Line); + } + _ => { + self.reset(); + } + } + } + + pub fn exec_d_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let count = match self.count { + Some(count) => count as u16, + None => 1, + }; + match self.buf_op_arg { + Some(BufOpArg::Line) => { + // 删除行 + let result = self + .remove_n_line(ui, count) + .map(|_| WarpUiCallBackType::None); + self.reset(); + return result; + } + Some(BufOpArg::Word) => { + // 删除单词 + for _ in 0..count { + self.remove_word(ui)?; + } + self.reset(); + return Ok(WarpUiCallBackType::None); + } + _ => { + return Ok(WarpUiCallBackType::None); + } + } + } + + pub fn on_w_clicked(&mut self) { + if self.cmdchar.is_none() { + // 按单词移动 + self.cmdchar = Some('w'); + } else { + // 按单词操作,具体由self.cmdchar决定 + self.buf_op_arg = Some(BufOpArg::Word); + } + } + + pub fn exec_w_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let count = match self.count { + Some(count) => count, + None => 1, + }; + for _ in 0..count { + self.jump_to_next_word(ui)?; + } + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + fn on_g_clicked(&mut self, ui: &mut MutexGuard) { + if self.cmdchar.is_none() { + self.cmdchar = Some('g'); + } else { + let first_line_size = ui.buffer.get_linesize(0); + self.end_pos = Some((ui.cursor.x().min(first_line_size - 1), 0)); + } + } + + fn exec_g_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let end_pos = self.end_pos; + if end_pos.is_none() { + return Ok(WarpUiCallBackType::None); + } + let old_y = ui.cursor.y(); + let (x, y) = end_pos.unwrap(); + let y = ui.buffer.goto_line(y.into()); + ui.cursor.move_to(x as u16, y as u16)?; + ui.render_content(y, CONTENT_WINSIZE.read().unwrap().rows as usize)?; + ui.cursor.highlight(Some(old_y))?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + #[allow(non_snake_case)] + fn on_G_clicked(&mut self, _ui: &mut MutexGuard) { + if self.cmdchar.is_none() { + self.cmdchar = Some('G'); + } + } + + #[allow(non_snake_case)] + fn exec_G_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let lineidx = match self.count { + Some(count) => count - 1, + None => ui.buffer.line_count() - 1, + }; + self.move_to_line(ui, lineidx as u16)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + fn on_b_clicked(&mut self, ui: &mut MutexGuard) { + if self.cmdchar.is_none() { + self.cmdchar = Some('b'); + } else { + self.buf_op_arg = Some(BufOpArg::WordBegin); + } + let count = match self.count { + Some(count) => count, + None => 1, + }; + let mut pos = (ui.cursor.x(), ui.cursor.y() + ui.buffer.offset() as u16); + for _ in 0..count { + pos = self.locate_prevw_begin(ui, pos.0, pos.1); + } + self.end_pos = Some(pos); + } + + fn exec_b_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let end_pos = self.end_pos.unwrap(); + self.move_to_line(ui, end_pos.1)?; + ui.cursor.move_to_columu(end_pos.0)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + fn on_dollar_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('$'); + } + } + + fn exec_dollar_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let line_end = ui.buffer.get_linesize(ui.cursor.y()) as u16 - 1; + ui.cursor.move_to_columu(line_end)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + fn on_e_clicked(&mut self, ui: &mut MutexGuard) { + if self.cmdchar.is_none() { + self.cmdchar = Some('e'); + } else { + self.buf_op_arg = Some(BufOpArg::WordEnd); + } + let count = match self.count { + Some(count) => count, + None => 1, + }; + let mut pos = (ui.cursor.x(), ui.cursor.y()); + for _ in 0..count { + pos = self.locate_nextw_ending(ui, pos.0, pos.1); + } + self.end_pos = Some(pos); + } + + fn exec_e_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let end_pos = self.end_pos; + if end_pos.is_none() { + return Ok(WarpUiCallBackType::None); + } + let end_pos = end_pos.unwrap(); + self.move_to_line(ui, end_pos.1)?; + ui.cursor.move_to_columu(end_pos.0)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + fn on_f_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('f'); + } + } + + fn exec_f_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + if self.cmdbuf.len() < 2 { + return Ok(WarpUiCallBackType::None); + } + let to_find = self.cmdbuf.last().unwrap().clone() as char; + 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); + if pos.is_none() { + return Ok(WarpUiCallBackType::None); + } + ui.cursor + .move_to_columu((old_x + pos.unwrap() as u16) as u16)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + #[allow(non_snake_case)] + fn on_F_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('F'); + } + } + + #[allow(non_snake_case)] + fn exec_F_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + if self.cmdbuf.len() < 2 { + return Ok(WarpUiCallBackType::None); + } + let to_find = self.cmdbuf.last().unwrap().clone() as char; + 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); + if pos.is_none() { + return Ok(WarpUiCallBackType::None); + } + ui.cursor.move_to_columu(pos.unwrap() as u16)?; + self.reset(); + return Ok(WarpUiCallBackType::None); + } + + fn on_x_clicked(&mut self) { + if self.cmdchar.is_none() { + self.cmdchar = Some('x'); + } + } + + fn exec_x_cmd(&mut self, ui: &mut MutexGuard) -> io::Result { + let y = ui.cursor.y(); + let x = ui.cursor.x(); + if x < ui.buffer.get_linesize(y) - 1 { + ui.buffer.remove_char(x, y); + ui.render_content(y, 1)?; + } + return Ok(WarpUiCallBackType::None); + } +} + +pub trait StateMachine { + fn handle(&mut self, ui: &mut MutexGuard) -> io::Result; + fn exit(&mut self) -> io::Result<()>; +} + +impl StateMachine for NormalState { + fn handle(&mut self, ui: &mut MutexGuard) -> io::Result { + if self.cmdchar.is_none() { + return Ok(WarpUiCallBackType::None); + } + match self.cmdchar.unwrap() { + 'h' => self.exec_h_cmd(ui), + 'j' => self.exec_j_cmd(ui), + 'k' => self.exec_k_cmd(ui), + 'l' => self.exec_l_cmd(ui), + 'i' => self.exec_i_cmd(ui), + '0' => self.exec_0_cmd(ui), + 'd' => self.exec_d_cmd(ui), + 'w' => self.exec_w_cmd(ui), + 'g' => self.exec_g_cmd(ui), + 'G' => self.exec_G_cmd(ui), + 'b' => self.exec_b_cmd(ui), + '$' => self.exec_dollar_cmd(ui), + 'e' => self.exec_e_cmd(ui), + 'f' => self.exec_f_cmd(ui), + 'F' => self.exec_F_cmd(ui), + 'x' => self.exec_x_cmd(ui), + _ => return Ok(WarpUiCallBackType::None), + } + } + + fn exit(&mut self) -> io::Result<()> { + self.reset(); + Ok(()) + } +} diff --git a/src/utils/ui/uicore.rs b/src/utils/ui/uicore.rs index b6ae810..ccf86ad 100644 --- a/src/utils/ui/uicore.rs +++ b/src/utils/ui/uicore.rs @@ -22,6 +22,7 @@ use crate::utils::input::Input; use super::{ mode::mode::{Command, InputMode, Insert, LastLine, ModeType}, + mode::normal::Normal, AppInfo, }; @@ -29,6 +30,7 @@ lazy_static! { static ref COMMAND: Arc = Arc::new(Command); static ref INSERT: Arc = Arc::new(Insert); static ref LASTLINE: Arc = Arc::new(LastLine::new()); + static ref NORMAL: Arc = Arc::new(Normal::new()); pub static ref APP_INFO: Mutex = Mutex::new(AppInfo { level: InfoLevel::Info, info: String::new() @@ -378,6 +380,7 @@ impl Ui { ui.cursor.write(':')?; } ModeType::Insert => *self.mode.write().unwrap() = INSERT.clone(), + ModeType::Normal => *self.mode.write().unwrap() = NORMAL.clone(), } Ok(())