use std::{
cell::Cell,
ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive},
};
pub use self::selections::{Selection, Selections, VPoint};
use crate::{
buffer::{Buffer, BufferId, Change},
opts::PrintOpts,
text::{Matches, Point, RegexHaystack, RegexPattern, Strs, Text, TextIndex, TextRange},
ui::{Area, Widget},
};
mod selections;
macro_rules! sel {
($cursor:expr) => {
$cursor.selections[$cursor.sels_i]
.as_ref()
.unwrap()
.selection
};
}
macro_rules! sel_mut {
($cursor:expr) => {{
let mod_sel = $cursor.selections[$cursor.sels_i].as_mut().unwrap();
mod_sel.has_changed = true;
&mut mod_sel.selection
}};
}
pub struct Cursor<'w, W: Widget + ?Sized = crate::buffer::Buffer> {
selections: &'w mut Vec<Option<ModSelection>>,
sels_i: usize,
initial: Selection,
widget: &'w mut W,
area: &'w Area,
next_i: Option<&'w Cell<usize>>,
}
impl<'w, W: Widget + ?Sized> Cursor<'w, W> {
pub(crate) fn new(
selections: &'w mut Vec<Option<ModSelection>>,
sels_i: usize,
(widget, area): (&'w mut W, &'w Area),
next_i: Option<&'w Cell<usize>>,
) -> Self {
let initial = selections[sels_i].as_ref().unwrap().selection.clone();
Self {
selections,
sels_i,
initial,
widget,
area,
next_i,
}
}
pub fn replace(&mut self, edit: impl ToString) {
let change = {
let edit = edit.to_string();
let range = sel!(self).point_range(self.widget.text());
let (p0, p1) = (range.start, range.end);
let p1 = if self.anchor().is_some() { p1 } else { p0 };
Change::new(edit, p0..p1, self.widget.text())
};
if change.added_str().len() < 10 && change.added_str() == change.taken_str() {
return;
}
let (start, end) = (change.start(), change.added_end());
self.edit(change.clone());
let anchor_was_on_start = self.anchor_is_start();
self.move_to(start..end);
if !anchor_was_on_start {
self.set_caret_on_start();
}
}
pub fn insert(&mut self, edit: impl ToString) {
let caret_point = sel!(self).caret();
let range = caret_point..caret_point;
let change = Change::new(edit.to_string(), range, self.widget.text());
let (added, taken) = (change.added_end(), change.taken_end());
self.edit(change);
if let Some(anchor) = sel!(self).anchor()
&& anchor > sel!(self).caret()
{
let new_anchor = anchor + added - taken;
sel_mut!(self).swap_ends();
sel_mut!(self).move_to(new_anchor, self.widget.text());
sel_mut!(self).swap_ends();
}
}
pub fn append(&mut self, edit: impl ToString) {
let caret = sel!(self).caret();
let after = caret.fwd(self.widget.text().char_at(caret).unwrap());
let change = Change::new(edit.to_string(), after..after, self.widget.text());
let (added, taken) = (change.added_end(), change.taken_end());
self.edit(change);
if let Some(anchor) = sel!(self).anchor()
&& anchor > after
{
let new_anchor = anchor + added - taken;
sel_mut!(self).swap_ends();
sel_mut!(self).move_to(new_anchor, self.widget.text());
sel_mut!(self).swap_ends();
}
}
fn edit(&mut self, change: Change<'static, String>) {
let mut text = self.widget.text_mut();
let (change_i, selections_taken) =
text.apply_change(sel!(self).change_i.map(|i| i as usize), change);
sel_mut!(self).change_i = change_i.map(|i| i as u32);
if let Some(change_i) = change_i
&& let Some(next_i) = self.next_i.as_ref()
&& change_i <= next_i.get()
{
next_i.set(next_i.get().saturating_sub(selections_taken));
}
}
#[track_caller]
pub fn move_hor(&mut self, by: i32) -> i32 {
if by == 0 {
return 0;
}
sel_mut!(self).move_hor(by, self.widget.text())
}
#[track_caller]
pub fn move_ver(&mut self, by: i32) -> bool {
if by == 0 {
return false;
}
sel_mut!(self).move_ver(by, self.widget.text(), self.area, self.widget.print_opts())
}
#[track_caller]
pub fn move_ver_wrapped(&mut self, count: i32) -> bool {
if count == 0 {
return false;
}
sel_mut!(self).move_ver_wrapped(
count,
self.widget.text(),
self.area,
self.widget.print_opts(),
)
}
#[track_caller]
pub fn move_to(&mut self, point_or_points: impl CaretOrRange) {
point_or_points.move_to(self);
}
#[track_caller]
pub fn move_to_start(&mut self) {
sel_mut!(self).move_to(Point::default(), self.widget.text());
}
#[track_caller]
pub fn move_to_coords(&mut self, line: usize, column: usize) {
let point = self.text().point_at_coords(line, column);
self.move_to(point);
}
#[track_caller]
pub fn move_to_col(&mut self, column: usize) {
self.move_to_coords(self.caret().line(), column);
}
pub fn unset_anchor(&mut self) -> Option<Point> {
sel_mut!(self).unset_anchor()
}
pub fn set_anchor(&mut self) {
sel_mut!(self).set_anchor()
}
pub fn set_anchor_if_needed(&mut self) -> bool {
if self.anchor().is_none() {
sel_mut!(self).set_anchor();
true
} else {
false
}
}
pub fn swap_ends(&mut self) {
sel_mut!(self).swap_ends();
}
pub fn set_caret_on_start(&mut self) -> bool {
if let Some(anchor) = self.anchor()
&& anchor < self.caret()
{
self.swap_ends();
true
} else {
false
}
}
pub fn set_caret_on_end(&mut self) -> bool {
if let Some(anchor) = self.anchor()
&& anchor > self.caret()
{
self.swap_ends();
true
} else {
false
}
}
pub fn reset(&mut self) {
*sel_mut!(self) = self.initial.clone();
}
pub fn copy(&mut self) -> Cursor<'_, W> {
let copy = self.selections[self.sels_i].clone().unwrap();
self.selections
.push(Some(ModSelection { was_main: false, ..copy }));
let sels_i = self.selections.len() - 1;
Cursor::new(
self.selections,
sels_i,
(self.widget, self.area),
self.next_i,
)
}
pub fn destroy(self) {
if self.widget.text().selections().is_empty()
&& self.selections.iter().flatten().count() <= 1
{
return;
}
if self.selections[self.sels_i].as_ref().unwrap().was_main {
if self.widget.text().selections().is_empty() {
let len = self.selections.len();
let ranges = self
.selections
.get_disjoint_mut([self.sels_i..len, 0..self.sels_i])
.unwrap();
let next_sel = ranges.into_iter().flatten().rev().flatten().next().unwrap();
next_sel.was_main = true;
} else {
self.widget.text_mut().selections_mut().rotate_main(-1);
}
}
self.selections[self.sels_i] = None;
}
pub fn set_desired_vcol(&mut self, x: usize) {
sel_mut!(self).set_desired_cols(x, x);
}
#[track_caller]
pub fn matches_pat<R: RegexPattern>(&self, pat: R) -> bool {
let range = sel!(self).byte_range(self.widget.text());
match self.widget.text()[range].matches_pat(pat) {
Ok(result) => result,
Err(err) => panic!("{err}"),
}
}
#[track_caller]
pub fn search<R: RegexPattern>(&self, pat: R) -> CursorMatches<'_, R> {
let text = self.widget.text();
let caret = self.caret();
CursorMatches {
text_byte_len: text.len(),
caret_range: caret.byte()..caret.fwd(self.char()).byte(),
matches: text.search(pat),
}
}
pub fn char(&self) -> char {
self.text().char_at(sel!(self).caret()).unwrap()
}
pub fn char_at(&self, i: impl TextIndex) -> Option<char> {
self.text().char_at(i)
}
pub fn selection(&self) -> &Strs {
let range = sel!(self).byte_range(self.text());
&self.text()[range]
}
#[track_caller]
pub fn strs(&self, range: impl TextRange) -> &Strs {
&self.widget.text()[range]
}
pub fn try_strs(&self, range: impl TextRange) -> Option<&Strs> {
self.widget.text().get(range)
}
pub fn len(&self) -> Point {
self.text().end_point()
}
pub fn last_point(&self) -> Point {
self.text().last_point()
}
pub fn indent(&self) -> usize {
self.widget
.text()
.line(self.caret().line())
.indent(self.opts())
}
#[track_caller]
pub fn indent_on(&self, line: usize) -> usize {
self.widget.text().line(line).indent(self.opts())
}
pub fn caret(&self) -> Point {
sel!(self).caret()
}
pub fn anchor(&self) -> Option<Point> {
sel!(self).anchor()
}
pub fn range(&self) -> Range<Point> {
sel!(self).point_range(self.text())
}
pub fn range_excl(&self) -> Range<Point> {
sel!(self).point_range_excl()
}
pub fn v_caret(&self) -> VPoint {
sel!(self).v_caret(self.widget.text(), self.area, self.widget.print_opts())
}
pub fn v_anchor(&self) -> Option<VPoint> {
sel!(self).v_anchor(self.widget.text(), self.area, self.widget.print_opts())
}
pub fn anchor_is_start(&self) -> bool {
self.anchor().is_none_or(|anchor| anchor <= self.caret())
}
pub fn is_main(&self) -> bool {
self.selections[self.sels_i].as_ref().unwrap().was_main
}
pub fn text(&self) -> &Text {
self.widget.text()
}
pub fn opts(&self) -> PrintOpts {
self.widget.print_opts()
}
pub fn widget(&self) -> &W {
self.widget
}
}
impl Cursor<'_, Buffer> {
pub fn buffer_id(&self) -> BufferId {
self.widget.buffer_id()
}
}
impl<'a, W: Widget + ?Sized> std::fmt::Debug for Cursor<'a, W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cursor")
.field("selection", &sel!(self))
.finish_non_exhaustive()
}
}
pub struct CursorMatches<'c, R: RegexPattern> {
text_byte_len: usize,
caret_range: Range<usize>,
matches: Matches<'c, R>,
}
impl<'c, R: RegexPattern> CursorMatches<'c, R> {
pub fn range(self, range: impl TextRange) -> Self {
Self {
matches: self.matches.range(range),
..self
}
}
#[allow(clippy::wrong_self_convention)]
pub fn from_caret(self) -> Self {
Self {
matches: self
.matches
.range(self.caret_range.start..self.text_byte_len),
..self
}
}
#[allow(clippy::wrong_self_convention)]
pub fn from_caret_excl(self) -> Self {
Self {
matches: self.matches.range(self.caret_range.end..self.text_byte_len),
..self
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_caret(self) -> Self {
Self {
matches: self.matches.range(0..self.caret_range.start),
..self
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_caret_incl(self) -> Self {
Self {
matches: self.matches.range(0..self.caret_range.end),
..self
}
}
}
impl<'c, R: RegexPattern> Iterator for CursorMatches<'c, R> {
type Item = R::Match;
fn next(&mut self) -> Option<Self::Item> {
self.matches.next()
}
}
impl<'c, R: RegexPattern> DoubleEndedIterator for CursorMatches<'c, R> {
fn next_back(&mut self) -> Option<Self::Item> {
self.matches.next_back()
}
}
pub(crate) fn on_each_cursor<W: Widget + ?Sized>(
widget: &mut W,
area: &Area,
mut func: impl FnMut(Cursor<W>),
) {
let mut current = Vec::new();
let mut next_i = Cell::new(0);
while let Some((sel, was_main)) = widget.text_mut().selections_mut().remove(next_i.get()) {
current.push(Some(ModSelection::new(sel, next_i.get(), was_main)));
func(Cursor::new(&mut current, 0, (widget, area), Some(&next_i)));
reinsert_selections(current.drain(..).flatten(), widget, Some(next_i.get_mut()));
}
}
#[inline]
pub(crate) fn reinsert_selections(
mod_sels: impl Iterator<Item = ModSelection>,
widget: &mut (impl Widget + ?Sized),
mut next_i: Option<&mut usize>,
) {
for mod_sel in mod_sels {
let ([inserted_i, selections_taken], last_selection_overhangs) = widget
.text_mut()
.selections_mut()
.insert(mod_sel.index, mod_sel.selection, mod_sel.was_main);
if let Some(next_i) = next_i.as_mut()
&& inserted_i <= **next_i
{
let go_to_next = !last_selection_overhangs as usize;
**next_i = next_i.saturating_sub(selections_taken).max(inserted_i) + go_to_next;
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ModSelection {
selection: Selection,
index: usize,
was_main: bool,
has_changed: bool,
}
impl ModSelection {
pub(crate) fn new(selection: Selection, index: usize, was_main: bool) -> Self {
Self {
selection,
index,
was_main,
has_changed: false,
}
}
}
pub trait CaretOrRange {
#[doc(hidden)]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>);
}
impl CaretOrRange for Point {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
sel_mut!(cursor).move_to(self, cursor.widget.text());
}
}
impl CaretOrRange for usize {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
sel_mut!(cursor).move_to(
cursor.widget.text().point_at_byte(self),
cursor.widget.text(),
)
}
}
impl<Idx: TextIndex> CaretOrRange for Range<Idx> {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
let range = self.start.to_byte_index()..self.end.to_byte_index();
assert!(
range.start <= range.end,
"slice index start is larger than end"
);
sel_mut!(cursor).move_to(range.start, cursor.widget.text());
if range.start < range.end {
cursor.set_anchor();
sel_mut!(cursor).move_to(range.end, cursor.widget.text());
if range.end < cursor.widget.text().len() {
cursor.move_hor(-1);
}
} else {
cursor.unset_anchor();
}
}
}
impl<Idx: TextIndex> CaretOrRange for RangeInclusive<Idx> {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
let range = self.start().to_byte_index()..=self.end().to_byte_index();
assert!(
range.start() <= range.end(),
"slice index start is larger than end"
);
sel_mut!(cursor).move_to(*range.start(), cursor.widget.text());
cursor.set_anchor();
sel_mut!(cursor).move_to(*range.end(), cursor.widget.text());
}
}
impl<Idx: TextIndex> CaretOrRange for RangeFrom<Idx> {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
let start = self.start.to_byte_index();
sel_mut!(cursor).move_to(start, cursor.widget.text());
if start < cursor.text().len() {
cursor.set_anchor();
sel_mut!(cursor).move_to(cursor.widget.text().len(), cursor.widget.text());
cursor.move_hor(-1);
} else {
cursor.unset_anchor();
}
}
}
impl<Idx: TextIndex> CaretOrRange for RangeTo<Idx> {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
let end = self
.end
.to_byte_index()
.min(cursor.text().last_point().byte());
cursor.move_to_start();
if 0 < end {
cursor.set_anchor();
sel_mut!(cursor).move_to(end, cursor.widget.text());
cursor.move_hor(-1);
} else {
cursor.unset_anchor();
}
}
}
impl<Idx: TextIndex> CaretOrRange for RangeToInclusive<Idx> {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
cursor.move_to_start();
cursor.set_anchor();
sel_mut!(cursor).move_to(self.end, cursor.widget.text());
}
}
impl CaretOrRange for RangeFull {
#[track_caller]
fn move_to<W: Widget + ?Sized>(self, cursor: &mut Cursor<'_, W>) {
cursor.move_to_start();
cursor.set_anchor();
sel_mut!(cursor).move_to(cursor.widget.text().len(), cursor.widget.text());
}
}