use crate::selection::{Anchor, Selection, SelectionSpan, SelectionType, Side};
use super::Term;
enum Resolved {
Linear {
start_line: usize,
from: usize,
end_line: usize,
to: usize,
},
Block {
line0: usize,
line1: usize,
from: usize,
to: usize,
},
}
impl Term {
pub fn selection_begin(&mut self, row: usize, col: usize, side: Side, ty: SelectionType) {
let anchor = Anchor {
point: self.viewport_to_abs(row, col),
side,
};
self.selection = Some(Selection {
ty,
anchor,
focus: anchor,
});
}
pub fn selection_extend(&mut self, row: usize, col: usize, side: Side) {
let focus = Anchor {
point: self.viewport_to_abs(row, col),
side,
};
if let Some(sel) = &mut self.selection {
sel.focus = focus;
}
}
pub fn selection_clear(&mut self) {
self.selection = None;
}
pub(super) fn selection_shift_below_margin(&mut self, from: usize) {
if let Some(sel) = &mut self.selection {
if sel.anchor.point.line >= from {
sel.anchor.point.line += 1;
}
if sel.focus.point.line >= from {
sel.focus.point.line += 1;
}
}
}
pub(super) fn selection_evict_oldest(&mut self) {
let Some((a, f)) = self
.selection
.as_ref()
.map(|s| (s.anchor.point.line, s.focus.point.line))
else {
return;
};
if a == 0 && f == 0 {
self.selection = None;
return;
}
if let Some(sel) = &mut self.selection {
sel.anchor.point.line = a.saturating_sub(1);
sel.focus.point.line = f.saturating_sub(1);
}
}
pub(super) fn selection_rotate_region(&mut self, top: usize, bottom: usize, up: bool) {
let (ty, anchor, focus) = match self.selection.as_ref() {
Some(s) => (s.ty, s.anchor, s.focus),
None => return,
};
let last_col = self.grid.cols().saturating_sub(1);
let anchor_is_start = anchor.point <= focus.point;
let (mut start, mut end) = if anchor_is_start {
(anchor, focus)
} else {
(focus, anchor)
};
let (top_i, bottom_i) = (top as isize, bottom as isize);
let shift = |line: usize| -> Option<isize> {
if line < top || line > bottom {
None
} else if up {
Some(line as isize - 1)
} else {
Some(line as isize + 1)
}
};
if let Some(nl) = shift(start.point.line) {
if nl > bottom_i && (end.point.line as isize) <= bottom_i {
self.selection = None;
return;
}
if nl < top_i {
start.point.line = top;
if ty != SelectionType::Block {
start.point.col = 0;
start.side = Side::Left;
}
} else {
start.point.line = nl as usize;
}
}
if let Some(nl) = shift(end.point.line) {
if nl < start.point.line as isize {
self.selection = None;
return;
}
if nl > bottom_i {
end.point.line = bottom;
if ty != SelectionType::Block {
end.point.col = last_col;
end.side = Side::Right;
}
} else {
end.point.line = nl as usize;
}
}
if let Some(sel) = &mut self.selection {
if anchor_is_start {
(sel.anchor, sel.focus) = (start, end);
} else {
(sel.anchor, sel.focus) = (end, start);
}
}
}
pub fn selection_range(&self) -> Vec<SelectionSpan> {
let Some(resolved) = self.resolve() else {
return Vec::new();
};
let rows = self.grid.rows();
let top = self.scrollback.len() - self.display_offset;
let mut spans = Vec::new();
let mut push = |line: usize, left: usize, right: usize| {
if line >= top {
let row = line - top;
if row < rows {
spans.push(SelectionSpan { row, left, right });
}
}
};
match resolved {
Resolved::Linear {
start_line,
from,
end_line,
to,
} => {
for line in start_line..=end_line {
if line < top {
continue;
}
if line - top >= rows {
break;
}
let len = self.abs_line(line).len();
let left = if line == start_line { from.min(len) } else { 0 };
let right_excl = if line == end_line { to.min(len) } else { len };
if right_excl > left {
push(line, left, right_excl - 1);
}
}
}
Resolved::Block {
line0,
line1,
from,
to,
} => {
let cols = self.grid.cols();
let from = from.min(cols);
if to > from {
for line in line0..=line1 {
push(line, from, to - 1);
}
}
}
}
spans
}
fn resolve(&self) -> Option<Resolved> {
let sel = self.selection.as_ref()?;
let (start, end) = sel.ordered();
Some(match sel.ty {
SelectionType::Char => {
let from = match start.side {
Side::Left => start.point.col,
Side::Right => start.point.col + 1,
};
let to = match end.side {
Side::Left => end.point.col,
Side::Right => end.point.col + 1,
};
Resolved::Linear {
start_line: start.point.line,
from,
end_line: end.point.line,
to,
}
}
SelectionType::Word => {
let ws = self.word_start(start.point);
let we = self.word_end(end.point);
Resolved::Linear {
start_line: ws.line,
from: ws.col,
end_line: we.line,
to: we.col + 1,
}
}
SelectionType::Line => Resolved::Linear {
start_line: start.point.line,
from: 0,
end_line: end.point.line,
to: self.grid.cols(),
},
SelectionType::Block => {
let cols = self.grid.cols();
let (a, b) = (sel.anchor, sel.focus);
let (lcol, lside, rcol, rside) = if a.point.col <= b.point.col {
(a.point.col, a.side, b.point.col, b.side)
} else {
(b.point.col, b.side, a.point.col, a.side)
};
let from = match lside {
Side::Left => lcol,
Side::Right => lcol + 1,
};
let to = match rside {
Side::Left => rcol,
Side::Right => rcol + 1,
};
Resolved::Block {
line0: a.point.line.min(b.point.line),
line1: a.point.line.max(b.point.line),
from,
to: to.min(cols).max(from),
}
}
})
}
pub fn selection_text(&self) -> Option<String> {
match self.resolve()? {
Resolved::Linear {
start_line,
from,
end_line,
to,
} => Some(self.extract_lines(&self.grid, start_line, from, end_line, to)),
Resolved::Block {
line0,
line1,
from,
to,
} => {
let mut out = String::new();
for line in line0..=line1 {
let hi = to.min(self.abs_line(line).len());
let mut seg = String::new();
for col in from..hi {
self.append_cell(&self.grid, &mut seg, line, col);
}
out.push_str(seg.trim_end_matches(' '));
if line != line1 {
out.push('\n');
}
}
Some(out)
}
}
}
pub fn accessible_text(&self) -> String {
let total = self.scrollback.len() + self.grid.rows();
if total == 0 {
return String::new();
}
let start = self.abs_floor();
let mut doc = self.extract_lines(&self.grid, start, 0, total - 1, usize::MAX);
doc.truncate(doc.trim_end_matches('\n').len());
doc
}
}