use crate::buffer::{Pos, Selection, TextBuffer};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VisualModeKind {
Char,
Line,
Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VisualSelectionEditPlan {
pub delete_ranges: Vec<(Pos, Pos)>,
pub text: String,
pub mode: VisualModeKind,
}
impl TextBuffer {
pub fn line_span_char_range(
&self,
start_line: usize,
end_line_inclusive: usize,
) -> std::ops::Range<usize> {
let start_line = self.clamp_line(start_line);
let end_line = self.clamp_line(end_line_inclusive.max(start_line));
let start = self.line_to_char(start_line);
let end = self.line_full_end_char(end_line);
start..end
}
pub fn line_span_pos_range(&self, start_line: usize, end_line_inclusive: usize) -> (Pos, Pos) {
let range = self.line_span_char_range(start_line, end_line_inclusive);
(self.char_to_pos(range.start), self.char_to_pos(range.end))
}
pub fn line_span_text(&self, start_line: usize, end_line_inclusive: usize) -> String {
let range = self.line_span_char_range(start_line, end_line_inclusive);
self.slice_chars(range.start, range.end)
}
pub fn line_span_text_linewise_register(
&self,
start_line: usize,
end_line_inclusive: usize,
) -> String {
let mut text = self.line_span_text(start_line, end_line_inclusive);
if !text.ends_with('\n') {
text.push('\n');
}
text
}
pub fn visual_charwise_pos_range(&self, selection: Selection) -> (Pos, Pos) {
let (start, end_inclusive) = selection.ordered();
let end_char = self.pos_to_char(end_inclusive);
let end_exclusive = if end_char < self.len_chars() {
self.char_to_pos(end_char + 1)
} else {
end_inclusive
};
(start, end_exclusive)
}
pub fn visual_linewise_pos_range(&self, selection: Selection) -> (Pos, Pos) {
let (start, end_inclusive) = selection.ordered();
self.line_span_pos_range(start.line, end_inclusive.line)
}
pub fn visual_charwise_text(&self, selection: Selection) -> String {
let (start, end_exclusive) = self.visual_charwise_pos_range(selection);
self.slice_pos_range(start, end_exclusive)
}
pub fn visual_linewise_text(&self, selection: Selection) -> String {
let (start, end) = selection.ordered();
self.line_span_text_linewise_register(start.line, end.line)
}
pub fn visual_blockwise_pos_ranges(&self, selection: Selection) -> Vec<(Pos, Pos)> {
let (start, end) = selection.ordered();
let left = start.col.min(end.col);
let right_exclusive = start.col.max(end.col).saturating_add(1);
let mut ranges = Vec::new();
for line_idx in start.line..=end.line {
let line_len = self.line_len_chars(line_idx);
let range_start = left.min(line_len);
let range_end = right_exclusive.min(line_len);
if range_start < range_end {
ranges.push((
Pos::new(line_idx, range_start),
Pos::new(line_idx, range_end),
));
}
}
ranges
}
pub fn visual_blockwise_delete_ranges(&self, selection: Selection) -> Vec<(Pos, Pos)> {
let (start, end) = selection.ordered();
let left = start.col.min(end.col);
let right_exclusive = start.col.max(end.col).saturating_add(1);
let mut ranges = Vec::new();
for line_idx in start.line..=end.line {
let line_len = self.line_len_chars(line_idx);
if left == 0 && right_exclusive >= line_len {
let full_line = self.line_span_pos_range(line_idx, line_idx);
if full_line.0 != full_line.1 {
ranges.push(full_line);
}
continue;
}
let range_start = left.min(line_len);
let range_end = right_exclusive.min(line_len);
if range_start < range_end {
ranges.push((
Pos::new(line_idx, range_start),
Pos::new(line_idx, range_end),
));
}
}
ranges
}
pub fn visual_blockwise_text(&self, selection: Selection) -> String {
self.visual_blockwise_pos_ranges(selection)
.into_iter()
.map(|(start, end)| self.slice_pos_range(start, end))
.collect::<Vec<_>>()
.join("\n")
}
pub fn visual_selection_pos_ranges(
&self,
selection: Selection,
mode: VisualModeKind,
) -> Vec<(Pos, Pos)> {
match mode {
VisualModeKind::Char => vec![self.visual_charwise_pos_range(selection)],
VisualModeKind::Line => vec![self.visual_linewise_pos_range(selection)],
VisualModeKind::Block => self.visual_blockwise_delete_ranges(selection),
}
}
pub fn visual_selection_text(&self, selection: Selection, mode: VisualModeKind) -> String {
match mode {
VisualModeKind::Char => self.visual_charwise_text(selection),
VisualModeKind::Line => self.visual_linewise_text(selection),
VisualModeKind::Block => self.visual_blockwise_text(selection),
}
}
pub fn visual_selection_edit_plan(
&self,
selection: Selection,
mode: VisualModeKind,
) -> VisualSelectionEditPlan {
let delete_ranges = self.visual_selection_pos_ranges(selection, mode);
let text = self.visual_selection_text(selection, mode);
VisualSelectionEditPlan {
delete_ranges,
text,
mode,
}
}
pub fn visual_selection_char_range_on_line(
&self,
selection: Selection,
mode: VisualModeKind,
line_idx: usize,
) -> Option<std::ops::Range<usize>> {
let line_len = self.line_len_chars(line_idx);
match mode {
VisualModeKind::Line => {
let (start_line, end_line) = selection.line_range();
if line_idx < start_line || line_idx > end_line {
return None;
}
Some(0..line_len)
}
VisualModeKind::Block => {
let (start, end) = selection.ordered();
if line_idx < start.line || line_idx > end.line {
return None;
}
let left = start.col.min(end.col).min(line_len);
let right = start.col.max(end.col).saturating_add(1).min(line_len);
if left < right {
Some(left..right)
} else {
None
}
}
VisualModeKind::Char => {
let (start, end) = selection.ordered();
if line_idx < start.line || line_idx > end.line {
return None;
}
if line_len == 0 {
return None;
}
let max_char = line_len.saturating_sub(1);
let sel_start = if line_idx == start.line {
start.col.min(max_char)
} else {
0
};
let sel_end_inclusive = if line_idx == end.line {
end.col.min(max_char)
} else {
max_char
};
if sel_start > sel_end_inclusive {
return None;
}
Some(sel_start..sel_end_inclusive.saturating_add(1))
}
}
}
}