|
|
|
|
@@ -321,7 +321,7 @@ pub struct LineBuf {
|
|
|
|
|
pub cursor: ClampedUsize, // Used to index grapheme_indices
|
|
|
|
|
|
|
|
|
|
pub select_mode: Option<SelectMode>,
|
|
|
|
|
pub select_range: Option<(usize, usize)>,
|
|
|
|
|
select_range: Option<(usize, usize)>,
|
|
|
|
|
pub last_selection: Option<(usize, usize)>,
|
|
|
|
|
|
|
|
|
|
pub insert_mode_start_pos: Option<usize>,
|
|
|
|
|
@@ -542,6 +542,9 @@ impl LineBuf {
|
|
|
|
|
}
|
|
|
|
|
pub fn slice_to(&mut self, end: usize) -> Option<&str> {
|
|
|
|
|
self.update_graphemes_lazy();
|
|
|
|
|
self.read_slice_to(end)
|
|
|
|
|
}
|
|
|
|
|
pub fn read_slice_to(&self, end: usize) -> Option<&str> {
|
|
|
|
|
let grapheme_index = self.grapheme_indices().get(end).copied().or_else(|| {
|
|
|
|
|
if end == self.grapheme_indices().len() {
|
|
|
|
|
Some(self.buffer.len())
|
|
|
|
|
@@ -559,6 +562,9 @@ impl LineBuf {
|
|
|
|
|
pub fn slice_to_cursor(&mut self) -> Option<&str> {
|
|
|
|
|
self.slice_to(self.cursor.get())
|
|
|
|
|
}
|
|
|
|
|
pub fn read_slice_to_cursor(&self) -> Option<&str> {
|
|
|
|
|
self.read_slice_to(self.cursor.get())
|
|
|
|
|
}
|
|
|
|
|
pub fn slice_to_cursor_inclusive(&mut self) -> Option<&str> {
|
|
|
|
|
self.slice_to(self.cursor.ret_add(1))
|
|
|
|
|
}
|
|
|
|
|
@@ -614,14 +620,31 @@ impl LineBuf {
|
|
|
|
|
self.update_graphemes();
|
|
|
|
|
}
|
|
|
|
|
pub fn select_range(&self) -> Option<(usize, usize)> {
|
|
|
|
|
self.select_range
|
|
|
|
|
match self.select_mode? {
|
|
|
|
|
SelectMode::Char(_) => {
|
|
|
|
|
self.select_range
|
|
|
|
|
}
|
|
|
|
|
SelectMode::Line(_) => {
|
|
|
|
|
let (start, end) = self.select_range?;
|
|
|
|
|
let start = self.pos_line_number(start);
|
|
|
|
|
let end = self.pos_line_number(end);
|
|
|
|
|
let (select_start,_) = self.line_bounds(start);
|
|
|
|
|
let (_,select_end) = self.line_bounds(end);
|
|
|
|
|
if self.read_grapheme_before(select_end).is_some_and(|gr| gr == "\n") {
|
|
|
|
|
Some((select_start, select_end - 1))
|
|
|
|
|
} else {
|
|
|
|
|
Some((select_start, select_end))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SelectMode::Block(_) => todo!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn start_selecting(&mut self, mode: SelectMode) {
|
|
|
|
|
let range_start = self.cursor;
|
|
|
|
|
let mut range_end = self.cursor;
|
|
|
|
|
range_end.add(1);
|
|
|
|
|
self.select_range = Some((range_start.get(), range_end.get()));
|
|
|
|
|
self.select_mode = Some(mode);
|
|
|
|
|
let range_start = self.cursor;
|
|
|
|
|
let mut range_end = self.cursor;
|
|
|
|
|
range_end.add(1);
|
|
|
|
|
self.select_range = Some((range_start.get(), range_end.get()));
|
|
|
|
|
}
|
|
|
|
|
pub fn stop_selecting(&mut self) {
|
|
|
|
|
self.select_mode = None;
|
|
|
|
|
@@ -629,12 +652,18 @@ impl LineBuf {
|
|
|
|
|
self.last_selection = self.select_range.take();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn total_lines(&mut self) -> usize {
|
|
|
|
|
pub fn total_lines(&self) -> usize {
|
|
|
|
|
self.buffer.graphemes(true).filter(|g| *g == "\n").count()
|
|
|
|
|
}
|
|
|
|
|
pub fn cursor_line_number(&mut self) -> usize {
|
|
|
|
|
|
|
|
|
|
pub fn pos_line_number(&self, pos: usize) -> usize {
|
|
|
|
|
self.read_slice_to(pos)
|
|
|
|
|
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
|
|
|
|
.unwrap_or(0)
|
|
|
|
|
}
|
|
|
|
|
pub fn cursor_line_number(&self) -> usize {
|
|
|
|
|
self
|
|
|
|
|
.slice_to_cursor()
|
|
|
|
|
.read_slice_to_cursor()
|
|
|
|
|
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
|
|
|
|
.unwrap_or(0)
|
|
|
|
|
}
|
|
|
|
|
@@ -772,7 +801,7 @@ impl LineBuf {
|
|
|
|
|
|
|
|
|
|
Some((start, end))
|
|
|
|
|
}
|
|
|
|
|
pub fn line_bounds(&mut self, n: usize) -> (usize, usize) {
|
|
|
|
|
pub fn line_bounds(&self, n: usize) -> (usize, usize) {
|
|
|
|
|
if n > self.total_lines() {
|
|
|
|
|
panic!(
|
|
|
|
|
"Attempted to find line {n} when there are only {} lines",
|
|
|
|
|
@@ -2321,7 +2350,7 @@ impl LineBuf {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
match mode {
|
|
|
|
|
SelectMode::Char(anchor) => match anchor {
|
|
|
|
|
SelectMode::Line(anchor) | SelectMode::Char(anchor) => match anchor {
|
|
|
|
|
SelectAnchor::Start => {
|
|
|
|
|
start = self.cursor.get();
|
|
|
|
|
}
|
|
|
|
|
@@ -2329,14 +2358,6 @@ impl LineBuf {
|
|
|
|
|
end = self.cursor.get();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
SelectMode::Line(anchor) => match anchor {
|
|
|
|
|
SelectAnchor::Start => {
|
|
|
|
|
start = self.start_of_line();
|
|
|
|
|
}
|
|
|
|
|
SelectAnchor::End => {
|
|
|
|
|
end = self.end_of_line();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SelectMode::Block(anchor) => todo!(),
|
|
|
|
|
}
|
|
|
|
|
if start >= end {
|
|
|
|
|
@@ -2381,7 +2402,7 @@ impl LineBuf {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MotionKind::Exclusive((start,end)) => {
|
|
|
|
|
if self.select_range().is_none() {
|
|
|
|
|
if self.select_range.is_none() {
|
|
|
|
|
self.cursor.set(start)
|
|
|
|
|
} else {
|
|
|
|
|
let end = end.saturating_sub(1);
|
|
|
|
|
@@ -2395,7 +2416,14 @@ impl LineBuf {
|
|
|
|
|
}
|
|
|
|
|
pub fn range_from_motion(&mut self, motion: &MotionKind) -> Option<(usize, usize)> {
|
|
|
|
|
let range = match motion {
|
|
|
|
|
MotionKind::On(pos) => ordered(self.cursor.get(), *pos),
|
|
|
|
|
MotionKind::On(pos) => {
|
|
|
|
|
let cursor_pos = self.cursor.get();
|
|
|
|
|
if cursor_pos == *pos {
|
|
|
|
|
ordered(cursor_pos, pos + 1) // scary
|
|
|
|
|
} else {
|
|
|
|
|
ordered(cursor_pos, *pos)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
MotionKind::Onto(pos) => {
|
|
|
|
|
// For motions which include the character at the cursor during operations
|
|
|
|
|
// but exclude the character during movements
|
|
|
|
|
@@ -2439,20 +2467,29 @@ impl LineBuf {
|
|
|
|
|
) -> ShResult<()> {
|
|
|
|
|
match verb {
|
|
|
|
|
Verb::Delete | Verb::Yank | Verb::Change => {
|
|
|
|
|
log::debug!("Executing verb: {verb:?} with motion: {motion:?}");
|
|
|
|
|
let Some((mut start, mut end)) = self.range_from_motion(&motion) else {
|
|
|
|
|
log::debug!("No range from motion, nothing to do");
|
|
|
|
|
return Ok(());
|
|
|
|
|
};
|
|
|
|
|
log::debug!("Initial range from motion: ({start}, {end})");
|
|
|
|
|
log::debug!("self.grapheme_indices().len(): {}", self.grapheme_indices().len());
|
|
|
|
|
|
|
|
|
|
let mut do_indent = false;
|
|
|
|
|
if verb == Verb::Change && (start,end) == self.this_line() {
|
|
|
|
|
do_indent = read_shopts(|o| o.prompt.auto_indent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let text = if verb == Verb::Yank {
|
|
|
|
|
let mut text = if verb == Verb::Yank {
|
|
|
|
|
self
|
|
|
|
|
.slice(start..end)
|
|
|
|
|
.map(|c| c.to_string())
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
} else if start == self.grapheme_indices().len() && end == self.grapheme_indices().len() {
|
|
|
|
|
// user is in normal mode and pressed 'x' on the last char in the buffer
|
|
|
|
|
let drained = self.drain(end.saturating_sub(1)..end);
|
|
|
|
|
self.update_graphemes();
|
|
|
|
|
drained
|
|
|
|
|
} else {
|
|
|
|
|
let drained = self.drain(start..end);
|
|
|
|
|
self.update_graphemes();
|
|
|
|
|
@@ -2460,9 +2497,13 @@ impl LineBuf {
|
|
|
|
|
};
|
|
|
|
|
let is_linewise = matches!(
|
|
|
|
|
motion,
|
|
|
|
|
MotionKind::InclusiveWithTargetCol(..) | MotionKind::ExclusiveWithTargetCol(..)
|
|
|
|
|
);
|
|
|
|
|
MotionKind::InclusiveWithTargetCol(..) |
|
|
|
|
|
MotionKind::ExclusiveWithTargetCol(..)
|
|
|
|
|
) || matches!(self.select_mode, Some(SelectMode::Line(_)));
|
|
|
|
|
let register_content = if is_linewise {
|
|
|
|
|
if !text.ends_with('\n') && !text.is_empty() {
|
|
|
|
|
text.push('\n');
|
|
|
|
|
}
|
|
|
|
|
RegisterContent::Line(text)
|
|
|
|
|
} else {
|
|
|
|
|
RegisterContent::Span(text)
|
|
|
|
|
@@ -2649,7 +2690,7 @@ impl LineBuf {
|
|
|
|
|
if content.is_empty() {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
if let Some(range) = self.select_range {
|
|
|
|
|
if let Some(range) = self.select_range() {
|
|
|
|
|
let register_text = self.drain_inclusive(range.0..=range.1);
|
|
|
|
|
write_register(None, RegisterContent::Span(register_text)); // swap deleted text into register
|
|
|
|
|
|
|
|
|
|
@@ -2688,7 +2729,7 @@ impl LineBuf {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Verb::SwapVisualAnchor => {
|
|
|
|
|
if let Some((start, end)) = self.select_range()
|
|
|
|
|
if let Some((start, end)) = self.select_range
|
|
|
|
|
&& let Some(mut mode) = self.select_mode
|
|
|
|
|
{
|
|
|
|
|
mode.invert_anchor();
|
|
|
|
|
@@ -2960,7 +3001,7 @@ impl LineBuf {
|
|
|
|
|
.map(|m| self.eval_motion(verb_ref.as_ref(), m))
|
|
|
|
|
.unwrap_or({
|
|
|
|
|
self
|
|
|
|
|
.select_range
|
|
|
|
|
.select_range()
|
|
|
|
|
.map(MotionKind::Inclusive)
|
|
|
|
|
.unwrap_or(MotionKind::Null)
|
|
|
|
|
})
|
|
|
|
|
@@ -3028,15 +3069,11 @@ impl LineBuf {
|
|
|
|
|
impl Display for LineBuf {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
let mut full_buf = self.buffer.clone();
|
|
|
|
|
if let Some((start, end)) = self.select_range {
|
|
|
|
|
if let Some((start, end)) = self.select_range() {
|
|
|
|
|
let mode = self.select_mode.unwrap();
|
|
|
|
|
let start_byte = self.read_idx_byte_pos(start);
|
|
|
|
|
let end_byte = self.read_idx_byte_pos(end);
|
|
|
|
|
let end_byte = self.read_idx_byte_pos(end).min(full_buf.len());
|
|
|
|
|
|
|
|
|
|
if start_byte >= full_buf.len() || end_byte >= full_buf.len() {
|
|
|
|
|
log::warn!("Selection range '{:?}' is out of bounds for buffer of length {}, clearing selection", (start, end), full_buf.len());
|
|
|
|
|
return write!(f, "{}", full_buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match mode.anchor() {
|
|
|
|
|
SelectAnchor::Start => {
|
|
|
|
|
@@ -3044,11 +3081,13 @@ impl Display for LineBuf {
|
|
|
|
|
if *inclusive.end() == full_buf.len() {
|
|
|
|
|
inclusive = start_byte..=end_byte.saturating_sub(1);
|
|
|
|
|
}
|
|
|
|
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[inclusive.clone()], markers::VISUAL_MODE_END);
|
|
|
|
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[inclusive.clone()], markers::VISUAL_MODE_END)
|
|
|
|
|
.replace("\n", format!("\n{}",markers::VISUAL_MODE_START).as_str());
|
|
|
|
|
full_buf.replace_range(inclusive, &selected);
|
|
|
|
|
}
|
|
|
|
|
SelectAnchor::End => {
|
|
|
|
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[start..end], markers::VISUAL_MODE_END);
|
|
|
|
|
let selected = format!("{}{}{}", markers::VISUAL_MODE_START, &full_buf[start_byte..end_byte], markers::VISUAL_MODE_END)
|
|
|
|
|
.replace("\n", format!("\n{}",markers::VISUAL_MODE_START).as_str());
|
|
|
|
|
full_buf.replace_range(start_byte..end_byte, &selected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|