early support for visual line motions like gk and gj
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
use std::{cmp::Ordering, fmt::Display, ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive}, str::FromStr, sync::Arc};
|
use std::{cmp::Ordering, fmt::Display, ops::{Range, RangeBounds}};
|
||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::libsh::{error::ShResult, sys::sh_quit, term::{Style, Styled}};
|
use crate::libsh::{error::ShResult, sys::sh_quit};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, RegisterName, TextObj, To, Verb, ViCmd, Word};
|
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, RegisterName, TextObj, To, Verb, ViCmd, Word};
|
||||||
@@ -28,7 +28,7 @@ pub enum MotionKind {
|
|||||||
|
|
||||||
/// Absolute position based on display width of characters
|
/// Absolute position based on display width of characters
|
||||||
/// Factors in the length of the prompt, and skips newlines
|
/// Factors in the length of the prompt, and skips newlines
|
||||||
ToScreenPos(usize),
|
ScreenLine(isize)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MotionKind {
|
impl MotionKind {
|
||||||
@@ -80,11 +80,6 @@ fn is_other_class_or_ws(a: &str, b: &str) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UndoPayload {
|
|
||||||
buffer: String,
|
|
||||||
cursor: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default,Debug)]
|
||||||
pub struct Edit {
|
pub struct Edit {
|
||||||
pub pos: usize,
|
pub pos: usize,
|
||||||
@@ -152,6 +147,7 @@ pub struct LineBuf {
|
|||||||
clamp_cursor: bool,
|
clamp_cursor: bool,
|
||||||
first_line_offset: usize,
|
first_line_offset: usize,
|
||||||
saved_col: Option<usize>,
|
saved_col: Option<usize>,
|
||||||
|
term_dims: (usize,usize), // Height, width
|
||||||
move_cursor_on_undo: bool,
|
move_cursor_on_undo: bool,
|
||||||
undo_stack: Vec<Edit>,
|
undo_stack: Vec<Edit>,
|
||||||
redo_stack: Vec<Edit>,
|
redo_stack: Vec<Edit>,
|
||||||
@@ -171,6 +167,9 @@ impl LineBuf {
|
|||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.buffer
|
&self.buffer
|
||||||
}
|
}
|
||||||
|
pub fn update_term_dims(&mut self, dims: (usize,usize)) {
|
||||||
|
self.term_dims = dims
|
||||||
|
}
|
||||||
pub fn take(&mut self) -> String {
|
pub fn take(&mut self) -> String {
|
||||||
let line = std::mem::take(&mut self.buffer);
|
let line = std::mem::take(&mut self.buffer);
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
@@ -237,7 +236,7 @@ impl LineBuf {
|
|||||||
pub fn grapheme_at_cursor_offset(&self, offset: isize) -> Option<&str> {
|
pub fn grapheme_at_cursor_offset(&self, offset: isize) -> Option<&str> {
|
||||||
match offset.cmp(&0) {
|
match offset.cmp(&0) {
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
return self.grapheme_at(self.cursor);
|
self.grapheme_at(self.cursor)
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
// Walk backward from the start of the line or buffer up to the cursor
|
// Walk backward from the start of the line or buffer up to the cursor
|
||||||
@@ -364,7 +363,7 @@ impl LineBuf {
|
|||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
pub fn display_coords(&self, term_width: usize) -> (usize,usize) {
|
pub fn display_coords(&self, term_width: usize) -> (usize,usize) {
|
||||||
let mut chars = self.slice_to_cursor().chars();
|
let chars = self.slice_to_cursor().chars();
|
||||||
|
|
||||||
let mut lines = 0;
|
let mut lines = 0;
|
||||||
let mut col = 0;
|
let mut col = 0;
|
||||||
@@ -883,6 +882,8 @@ impl LineBuf {
|
|||||||
Motion::ForwardChar => MotionKind::Forward(1),
|
Motion::ForwardChar => MotionKind::Forward(1),
|
||||||
Motion::LineUp => MotionKind::Line(-1),
|
Motion::LineUp => MotionKind::Line(-1),
|
||||||
Motion::LineDown => MotionKind::Line(1),
|
Motion::LineDown => MotionKind::Line(1),
|
||||||
|
Motion::ScreenLineUp => MotionKind::ScreenLine(-1),
|
||||||
|
Motion::ScreenLineDown => MotionKind::ScreenLine(1),
|
||||||
Motion::WholeBuffer => todo!(),
|
Motion::WholeBuffer => todo!(),
|
||||||
Motion::BeginningOfBuffer => MotionKind::To(0),
|
Motion::BeginningOfBuffer => MotionKind::To(0),
|
||||||
Motion::EndOfBuffer => MotionKind::To(self.byte_len()),
|
Motion::EndOfBuffer => MotionKind::To(self.byte_len()),
|
||||||
@@ -902,6 +903,64 @@ impl LineBuf {
|
|||||||
Motion::Null => todo!(),
|
Motion::Null => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn calculate_display_offset(&self, n_lines: isize) -> Option<usize> {
|
||||||
|
let (start,end) = self.this_line();
|
||||||
|
let graphemes: Vec<(usize, usize, &str)> = self.buffer[start..end]
|
||||||
|
.graphemes(true)
|
||||||
|
.scan(start, |idx, g| {
|
||||||
|
let current = *idx;
|
||||||
|
*idx += g.len(); // Advance by number of bytes
|
||||||
|
Some((g.width(), current, g))
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let mut cursor_line_index = 0;
|
||||||
|
let mut cursor_visual_col = 0;
|
||||||
|
let mut screen_lines = vec![];
|
||||||
|
let mut cur_line = vec![];
|
||||||
|
let mut line_width = 0;
|
||||||
|
|
||||||
|
for (width, byte_idx, grapheme) in graphemes {
|
||||||
|
if byte_idx == self.cursor {
|
||||||
|
// Save this to later find column
|
||||||
|
cursor_line_index = screen_lines.len();
|
||||||
|
cursor_visual_col = line_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_line_width = line_width + width;
|
||||||
|
if new_line_width > self.term_dims.1 {
|
||||||
|
screen_lines.push(std::mem::take(&mut cur_line));
|
||||||
|
cur_line.push((width, byte_idx, grapheme));
|
||||||
|
line_width = width;
|
||||||
|
} else {
|
||||||
|
cur_line.push((width, byte_idx, grapheme));
|
||||||
|
line_width = new_line_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cur_line.is_empty() {
|
||||||
|
screen_lines.push(cur_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if screen_lines.len() == 1 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_line_index = (cursor_line_index as isize + n_lines)
|
||||||
|
.clamp(0, (screen_lines.len() - 1) as isize) as usize;
|
||||||
|
|
||||||
|
let mut col = 0;
|
||||||
|
for (width, byte_idx, _) in &screen_lines[target_line_index] {
|
||||||
|
if col + width > cursor_visual_col {
|
||||||
|
return Some(*byte_idx);
|
||||||
|
}
|
||||||
|
col += width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you went past the end of the line
|
||||||
|
screen_lines[target_line_index]
|
||||||
|
.last()
|
||||||
|
.map(|(_, byte_idx, _)| *byte_idx)
|
||||||
|
}
|
||||||
pub fn get_range_from_motion(&self, verb: &Verb, motion: &MotionKind) -> Option<Range<usize>> {
|
pub fn get_range_from_motion(&self, verb: &Verb, motion: &MotionKind) -> Option<Range<usize>> {
|
||||||
match motion {
|
match motion {
|
||||||
MotionKind::Forward(n) => {
|
MotionKind::Forward(n) => {
|
||||||
@@ -945,8 +1004,11 @@ impl LineBuf {
|
|||||||
};
|
};
|
||||||
Some(range)
|
Some(range)
|
||||||
}
|
}
|
||||||
MotionKind::Null => return None,
|
MotionKind::Null => None,
|
||||||
MotionKind::ToScreenPos(n) => todo!(),
|
MotionKind::ScreenLine(n) => {
|
||||||
|
let pos = self.calculate_display_offset(*n)?;
|
||||||
|
Some(mk_range(pos, self.cursor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn exec_verb(&mut self, verb: Verb, motion: MotionKind, register: RegisterName) -> ShResult<()> {
|
pub fn exec_verb(&mut self, verb: Verb, motion: MotionKind, register: RegisterName) -> ShResult<()> {
|
||||||
@@ -1144,11 +1206,11 @@ impl LineBuf {
|
|||||||
self.cursor = start;
|
self.cursor = start;
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
let (start,_) = self.select_lines_up(n.abs() as usize);
|
let (start,_) = self.select_lines_up(n.unsigned_abs());
|
||||||
self.cursor = start;
|
self.cursor = start;
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
let (_,end) = self.select_lines_down(n.abs() as usize);
|
let (_,end) = self.select_lines_down(n.unsigned_abs());
|
||||||
self.cursor = end.saturating_sub(1);
|
self.cursor = end.saturating_sub(1);
|
||||||
let (start,_) = self.this_line();
|
let (start,_) = self.this_line();
|
||||||
self.cursor = start;
|
self.cursor = start;
|
||||||
@@ -1162,7 +1224,12 @@ impl LineBuf {
|
|||||||
self.cursor = start;
|
self.cursor = start;
|
||||||
}
|
}
|
||||||
MotionKind::Null => { /* Pass */ }
|
MotionKind::Null => { /* Pass */ }
|
||||||
MotionKind::ToScreenPos(_) => todo!(),
|
MotionKind::ScreenLine(n) => {
|
||||||
|
let Some(pos) = self.calculate_display_offset(n) else {
|
||||||
|
return
|
||||||
|
};
|
||||||
|
self.cursor = pos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn edit_is_merging(&self) -> bool {
|
pub fn edit_is_merging(&self) -> bool {
|
||||||
|
|||||||
@@ -85,12 +85,13 @@ impl FernVi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn print_buf(&mut self, refresh: bool) -> ShResult<()> {
|
pub fn print_buf(&mut self, refresh: bool) -> ShResult<()> {
|
||||||
let (_,width) = self.term.get_dimensions()?;
|
let (height,width) = self.term.get_dimensions()?;
|
||||||
if refresh {
|
if refresh {
|
||||||
self.term.unwrite()?;
|
self.term.unwrite()?;
|
||||||
}
|
}
|
||||||
let offset = self.calculate_prompt_offset();
|
let offset = self.calculate_prompt_offset();
|
||||||
self.line.set_first_line_offset(offset);
|
self.line.set_first_line_offset(offset);
|
||||||
|
self.line.update_term_dims((height,width));
|
||||||
let mut line_buf = self.prompt.clone();
|
let mut line_buf = self.prompt.clone();
|
||||||
line_buf.push_str(self.line.as_str());
|
line_buf.push_str(self.line.as_str());
|
||||||
|
|
||||||
|
|||||||
@@ -545,6 +545,14 @@ impl ViNormal {
|
|||||||
chars = chars_clone;
|
chars = chars_clone;
|
||||||
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::End, Word::Big)));
|
break 'motion_parse Some(MotionCmd(count, Motion::BackwardWord(To::End, Word::Big)));
|
||||||
}
|
}
|
||||||
|
'k' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineUp));
|
||||||
|
}
|
||||||
|
'j' => {
|
||||||
|
chars = chars_clone;
|
||||||
|
break 'motion_parse Some(MotionCmd(count, Motion::ScreenLineDown));
|
||||||
|
}
|
||||||
_ => return self.quit_parse()
|
_ => return self.quit_parse()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -251,8 +251,12 @@ pub enum Motion {
|
|||||||
ForwardChar,
|
ForwardChar,
|
||||||
/// move to the same column on the previous line
|
/// move to the same column on the previous line
|
||||||
LineUp,
|
LineUp,
|
||||||
|
/// move to the same column on the previous visual line
|
||||||
|
ScreenLineUp,
|
||||||
/// move to the same column on the next line
|
/// move to the same column on the next line
|
||||||
LineDown,
|
LineDown,
|
||||||
|
/// move to the same column on the next visual line
|
||||||
|
ScreenLineDown,
|
||||||
/// Whole user input (not really a movement but a range)
|
/// Whole user input (not really a movement but a range)
|
||||||
WholeBuffer,
|
WholeBuffer,
|
||||||
/// beginning-of-register
|
/// beginning-of-register
|
||||||
|
|||||||
Reference in New Issue
Block a user