Browse Source

增加按键功能 (#6)

实现了一系列与Vim类似的功能:

hjkl上下左右移动光标
'0'与'$'行首行末跳转
‘w'与'e'单词跳转
'dd'删除行等
'x'删除字符
Z YS 8 months ago
parent
commit
ec52bc291c
4 changed files with 396 additions and 6 deletions
  1. 145 2
      src/utils/buffer.rs
  2. 2 1
      src/utils/cursor.rs
  3. 248 2
      src/utils/ui/mode/mode.rs
  4. 1 1
      src/utils/ui/uicore.rs

+ 145 - 2
src/utils/buffer.rs

@@ -167,6 +167,16 @@ impl EditBuffer {
         line.unwrap().remove(x as usize);
     }
 
+    pub fn remove_str(&self, x: u16, y: u16, n: usize) {
+        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;
+        }
+        let x = x as usize;
+        line.unwrap().data.drain(x..x + n);
+    }
+
     /// 获取一份对应行的拷贝
     pub fn get_line(&self, line: u16) -> LineBuffer {
         let buf = self.buf.read().unwrap();
@@ -213,8 +223,14 @@ impl EditBuffer {
     #[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);
+        let line = buf.get_mut(self.offset() + y as usize);
+        match line {
+            Some(line) => line.insert(x as usize, ch),
+            None => {
+                let newline = vec!['\n' as u8];
+                buf.push(LineBuffer::new(newline));
+            }
+        }
     }
 
     #[inline]
@@ -353,6 +369,133 @@ impl EditBuffer {
 
         count
     }
+
+    pub fn delete_line(&self, y: usize) {
+        let mut buffer = self.buf.write().unwrap();
+        let line = buffer.get(y).unwrap();
+        if line.data.is_empty() {
+            return;
+        }
+
+        if !line.flags.contains(LineState::LOCKED) {
+            buffer.remove(y);
+        }
+    }
+
+    pub fn delete_until_line_beg(&self, x: usize, y: usize) -> Option<usize> {
+        let mut buffer = self.buf.write().unwrap();
+        let line = buffer.get_mut(y).unwrap();
+
+        if line.data.len() < 2 {
+            return None;
+        }
+        line.data.drain(0..x);
+        return Some(x - 1);
+    }
+
+    pub fn delete_until_endl(&self, x: usize, y: usize) -> Option<usize> {
+        let mut buffer = self.buf.write().unwrap();
+        let line = buffer.get_mut(y).unwrap();
+        let len = line.data.len();
+        if len < 2 {
+            return None;
+        }
+        line.data.drain(x..len - 1);
+        return Some(x);
+    }
+
+    /// 返回下一个单词的起始位置
+    /// 如果为该行最后一单词,返回该行长度
+    pub fn search_nextw_begin(&self, x: u16, y: u16) -> usize {
+        let mut left = x as usize;
+        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();
+
+        while left <= right && right < linesize {
+            let lchar = line[left] as char;
+            let rchar = line[right] as char;
+            if !(lchar == ' ' || lchar == '\t') {
+                left += 1;
+                right += 1;
+                continue;
+            }
+            if rchar != ' ' && rchar != '\t' {
+                break;
+            }
+            right += 1;
+        }
+
+        return right;
+    }
+
+    /// 搜索下一个单词的末尾
+    /// 如果为该行最后一单词,返回该行长度
+    pub fn search_nextw_end(&self, x: u16, y: u16) -> usize {
+        let mut left = x as usize;
+        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();
+
+        while left <= right && right < linesize {
+            let lchar = line[left] as char;
+            let rchar = line[right] as char;
+            if lchar == ' ' || lchar == '\t' {
+                left += 1;
+                right += 1;
+                continue;
+            }
+            if rchar == ' ' || rchar == '\t' {
+                if right == x as usize + 1 {
+                    left = right;
+                    continue;
+                }
+                right -= 1;
+                break;
+            }
+            right += 1;
+        }
+
+        return right;
+    }
+
+    /// 返回前一单词首字母位置,如果是当前行首单词,返回 None
+    pub fn search_prevw_begin(&self, x: u16, y: u16) -> Option<usize> {
+        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();
+
+        while left <= right && left >= 0 {
+            let lchar = line[left as usize] as char;
+            let rchar = line[right as usize] as char;
+
+            if rchar == ' ' || rchar == '\t' {
+                left -= 1;
+                right -= 1;
+                continue;
+            }
+
+            if lchar == ' ' || lchar == '\t' {
+                if left + 1 == x.into() {
+                    right = left;
+                    continue;
+                }
+                return Some(left as usize + 1);
+            }
+
+            left -= 1;
+        }
+        return None;
+    }
 }
 
 bitflags! {

+ 2 - 1
src/utils/cursor.rs

@@ -176,7 +176,8 @@ impl CursorCrtl {
         let size = *WINSIZE.read().unwrap();
         if self.y + lines >= size.rows {
             // 向上滚动
-            todo!()
+            // todo!()
+            return Ok(());
         }
 
         CursorManager::move_to_nextline(lines)?;

+ 248 - 2
src/utils/ui/mode/mode.rs

@@ -1,3 +1,4 @@
+use std::io::Read;
 use std::sync::atomic::Ordering;
 use std::sync::{Mutex, MutexGuard};
 use std::{fmt::Debug, io};
@@ -183,6 +184,195 @@ impl Command {
 
         Ok(())
     }
+
+    fn jump_to_first_char(&self, ui: &mut MutexGuard<UiCore>) -> io::Result<WarpUiCallBackType> {
+        // 移动到行第一个单词的首字母
+        let first_char = {
+            let line = ui.buffer.get_line(ui.cursor.y()).data;
+            let mut idx = 0;
+            for char in line {
+                if char == b" "[0] {
+                    idx += 1;
+                } else if char == b"\t"[0] {
+                    idx += 4;
+                }
+            }
+            idx
+        };
+        ui.cursor.move_to_columu(first_char)?;
+        return Ok(WarpUiCallBackType::None);
+    }
+
+    fn do_delete_on_d_clicked(
+        &self,
+        ui: &mut MutexGuard<UiCore>,
+    ) -> io::Result<WarpUiCallBackType> {
+        let buf: &mut [u8] = &mut [0; 8];
+        let _ = io::stdin().read(buf)?;
+
+        match buf[0] {
+            b'd' => {
+                TermManager::clear_current_line()?;
+                TermManager::clear_under_cursor()?;
+                let y = ui.cursor.y() as usize;
+                let old_line_count = ui.buffer.line_count();
+
+                let count = old_line_count - y as usize;
+                ui.buffer.delete_line(y);
+                ui.render_content(y as u16, count.max(1))?;
+
+                if y == 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)?;
+                }
+            }
+            b'0' => {
+                let x = ui.cursor.x() as usize;
+                let y = ui.cursor.y() as usize;
+                match ui.buffer.delete_until_line_beg(x, y) {
+                    Some(..) => {
+                        // 文本变动重新渲染
+                        ui.cursor.move_to_columu(0)?;
+                        ui.render_content(y as u16, 1)?;
+                    }
+                    None => {}
+                };
+            }
+            b'$' => {
+                let x = ui.cursor.x() as usize;
+                let y = ui.cursor.y() as usize;
+                match ui.buffer.delete_until_endl(x, y) {
+                    Some(..) => {
+                        ui.cursor.move_left(1)?;
+                        ui.render_content(y as u16, 1)?;
+                    }
+                    None => {}
+                }
+            }
+
+            b'w' | b'e' => {
+                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_until_endl(x.into(), y.into());
+                }
+                ui.render_content(y, 1)?;
+            }
+
+            b'b' => {
+                let old_x = ui.cursor.x();
+                let old_y = ui.cursor.y();
+
+                self.jump_to_prevw_beg(ui)?;
+
+                let x = ui.cursor.x();
+                let y = ui.cursor.y();
+                if old_y == y {
+                    ui.buffer.remove_str(x, y, old_x as usize - x as usize);
+                    ui.render_content(y, 1)?;
+                } else {
+                    ui.buffer.delete_until_endl(x as usize, y as usize);
+                    ui.buffer
+                        .delete_until_line_beg(old_x as usize, old_y as usize);
+                    ui.buffer.merge_line(old_y);
+                    let linecount = ui.buffer.line_count();
+                    TermManager::clear_under_cursor()?;
+                    ui.render_content(y, linecount - y as usize - 1)?;
+                }
+            }
+            _ => {}
+        }
+        return Ok(WarpUiCallBackType::None);
+    }
+
+    fn jump_to_next_word(&self, ui: &mut MutexGuard<UiCore>) -> io::Result<WarpUiCallBackType> {
+        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);
+
+        if pos < linesize as usize {
+            // 如果下一个单词在当前行,则移动光标到该单词的起始位置
+            ui.cursor.move_to_columu(pos as u16)?;
+        } else if y + 1 < ui.buffer.line_count() as u16 {
+            // 如果当前行不是最后一行,则移动到下一行的开头
+            self.down(ui)?;
+            ui.cursor.move_to_columu(0)?;
+        } else {
+            // 如果当前行是最后一行,则移动到当前行的末尾
+            ui.cursor.move_to_columu(linesize as u16 - 1)?;
+        }
+        return Ok(WarpUiCallBackType::None);
+    }
+
+    fn jump_to_nextw_ending(&self, ui: &mut MutexGuard<UiCore>) -> io::Result<WarpUiCallBackType> {
+        let x = ui.cursor.x();
+        let y = ui.cursor.y();
+        let linesize = ui.buffer.get_linesize(y) as usize;
+
+        // 如果光标已经在当前行的末尾或最后一个字符,则尝试移动到下一行的末尾或单词末尾
+        let final_char_pos = linesize - 2;
+        if x as usize >= final_char_pos {
+            if y < ui.buffer.line_count() as u16 - 1 {
+                let next_end_pos = ui.buffer.search_nextw_end(0, y + 1) as u16;
+                ui.cursor.move_to(next_end_pos, y + 1)?;
+                ui.cursor.highlight(Some(y))?;
+            } else {
+                // 如果已经是最后一行,则保持光标在当前行的末尾
+                ui.cursor.move_to_columu(linesize as u16 - 1)?;
+            }
+            return Ok(WarpUiCallBackType::None);
+        }
+
+        let next_end_pos = ui.buffer.search_nextw_end(x, y) as u16;
+        // 如果下一个单词的末尾在当前行,则移动光标到该单词的末尾
+        ui.cursor
+            .move_to_columu(next_end_pos.min(linesize as u16 - 2))?;
+        return Ok(WarpUiCallBackType::None);
+    }
+
+    fn jump_to_prevw_beg(&self, ui: &mut MutexGuard<UiCore>) -> io::Result<WarpUiCallBackType> {
+        let x = ui.cursor.x();
+        let y = ui.cursor.y();
+
+        // 如果光标已在行首,则尝试移动到上一行的单词首字母
+        if x == 0 {
+            if y > 0 {
+                let end_of_prev_line = ui.buffer.get_linesize(y - 1) - 1;
+                let prev_word_pos = match ui.buffer.search_prevw_begin(end_of_prev_line, y - 1) {
+                    Some(pos) => pos,
+                    None => 0,
+                };
+                ui.cursor.move_to(prev_word_pos as u16, y - 1)?;
+                ui.cursor.highlight(Some(y))?;
+            } else {
+                // 如果已经是第一行,则保持光标在当前行的起始位置
+                ui.cursor.move_to_columu(0)?;
+            }
+            return Ok(WarpUiCallBackType::None);
+        }
+
+        let prev_word_pos = match ui.buffer.search_prevw_begin(x, y) {
+            Some(pos) => pos,
+            None => 0,
+        };
+
+        ui.cursor.move_to(prev_word_pos as u16, y)?;
+        return Ok(WarpUiCallBackType::None);
+    }
 }
 
 impl KeyEventCallback for Command {
@@ -218,7 +408,19 @@ impl KeyEventCallback for Command {
                 return Ok(WarpUiCallBackType::ChangMode(ModeType::Insert));
             }
 
-            b"l" | b"L" => {
+            // hjkl 与 Vim 的效果一致
+            b"h" => self.left(ui),
+
+            // 向下
+            b"j" => self.down(ui),
+
+            // 向上
+            b"k" => self.up(ui),
+
+            //  向右
+            b"l" => self.right(ui),
+
+            b"L" => {
                 // 设置当前行lock
                 let flag = ui.buffer.line_flags(ui.cursor.y());
                 let offset = ui.buffer.offset();
@@ -257,7 +459,13 @@ impl KeyEventCallback for Command {
                 return Ok(WarpUiCallBackType::None);
             }
 
-            b"w" | b"W" => {
+            b"w" => self.jump_to_next_word(ui),
+
+            b"e" => self.jump_to_nextw_ending(ui),
+
+            b"b" => self.jump_to_prevw_beg(ui),
+
+            b"W" => {
                 // 跳转到下一个flag行
                 self.jump_to_next_flag(ui, LineState::FLAGED)?;
                 return Ok(WarpUiCallBackType::None);
@@ -273,6 +481,44 @@ impl KeyEventCallback for Command {
                 return Ok(WarpUiCallBackType::None);
             }
 
+            b"0" => {
+                // 移动到行首
+                ui.cursor.move_to_columu(0)?;
+                return Ok(WarpUiCallBackType::None);
+            }
+
+            b"^" => self.jump_to_first_char(ui),
+
+            b"$" => {
+                // 移动到行末
+                let line_end = ui.buffer.get_linesize(ui.cursor.y()) - 1;
+                ui.cursor.move_to_columu(line_end)?;
+                return Ok(WarpUiCallBackType::None);
+            }
+
+            b"d" => self.do_delete_on_d_clicked(ui),
+
+            b"x" => {
+                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);
+            }
+
+            b"G" => {
+                // 移动到最后一行
+                let line_count = ui.buffer.line_count() as u16;
+                let y = ui.cursor.y();
+                let new_y = ui.buffer.goto_line(line_count as usize - 1);
+                ui.render_content(0, CONTENT_WINSIZE.read().unwrap().rows as usize)?;
+                ui.cursor.move_to_row(new_y)?;
+                ui.cursor.highlight(Some(y))?;
+                return Ok(WarpUiCallBackType::None);
+            }
+
             _ => {
                 return Ok(WarpUiCallBackType::None);
             }

+ 1 - 1
src/utils/ui/uicore.rs

@@ -213,11 +213,11 @@ impl UiCore {
         }
 
         self.buffer.set_offset(offset - count as usize);
-        // 将光标移动第count行
 
         // 执行滚动
         TermManager::scroll_down(count)?;
 
+        // 将光标移动第count行
         self.cursor.move_to_row(count - 1)?;
         // 清除光标以上的内容
         TermManager::clear_up_cursor()?;