strop_engine/editor/
cursor.rs1use strop_core::Range;
5
6use super::{Editor, Mode, FLASH_FOR};
7
8impl Editor {
9 #[inline]
11 pub fn head(&self) -> usize {
12 self.sels().primary().head
13 }
14
15 #[inline]
16 pub fn set_head(&mut self, pos: impl Into<strop_core::id::ByteOffset>) {
17 self.view_mut().desired_column = None;
18 self.sels_mut().set_head(pos.into().get());
19 }
20
21 #[inline]
23 pub fn anchor(&self) -> usize {
24 self.sels().primary().anchor
25 }
26
27 pub fn extra_selections(&self) -> &[strop_core::selection::Selection] {
29 self.sels().extra_heads()
30 }
31
32 pub(crate) fn flash(&mut self, range: Range) {
33 self.flash = Some((range, self.tape.now()));
34 }
35
36 pub fn flash_range(&self) -> Option<Range> {
37 self.flash.and_then(|(range, at)| {
38 (self.tape.now().monotonic_ms.saturating_sub(at.monotonic_ms)
39 < FLASH_FOR.as_millis() as u64)
40 .then_some(range)
41 })
42 }
43
44 pub fn clamp_cursor(&mut self) {
45 let line = self.buf().line_of(self.head());
46 let start = self.buf().line_start(line);
47 let end = self.buf().line_end(line);
48 let max = if self.mode == Mode::Insert {
49 end
50 } else {
51 end.max(start + 1) - 1
52 };
53 let pos = self
54 .buf()
55 .clamp_boundary(self.head().clamp(start, max.max(start)));
56 self.set_head(pos);
57 }
58
59 pub(crate) fn clamp_pos(&self, pos: usize) -> usize {
61 let line = self.buf().line_of(pos);
62 let start = self.buf().line_start(line);
63 let end = self.buf().line_end(line);
64 let max = if self.mode == Mode::Insert {
65 end
66 } else {
67 end.max(start + 1) - 1
68 };
69 self.buf().clamp_boundary(pos.clamp(start, max.max(start)))
70 }
71
72 pub(crate) fn all_cursors(&self) -> Vec<usize> {
74 self.sels().heads()
75 }
76
77 pub(crate) fn normalize_cursors(&mut self) {
81 self.sels_mut().normalize();
82 }
83
84 pub(crate) fn remap_after_mirrored_edit(&mut self, positions: &[usize], delta: isize) {
88 let map = |old: usize| -> usize {
89 let below = positions.partition_point(|&p| p < old);
90 let own = usize::from(positions.contains(&old));
91 (old as isize + delta * (below + own) as isize).max(0) as usize
92 };
93 self.set_head(map(self.head()));
94 let extras: Vec<usize> = self
95 .extra_selections()
96 .iter()
97 .map(|s| map(s.head))
98 .collect();
99 self.sels_mut().set_extras(extras);
100 }
101
102 pub(crate) fn toggle_cursor(&mut self) {
104 if self.buf().readonly {
105 self.message = "readonly buffer".into();
106 return;
107 }
108 self.sels_mut().toggle_extra();
109 let n = self.sels().count();
110 self.message = format!("{n} cursor{}", if n > 1 { "s" } else { "" });
111 }
112
113 pub(crate) fn add_cursor_next_line(&mut self) {
116 if self.buf().readonly {
117 self.message = "readonly buffer".into();
118 return;
119 }
120 let base = self
123 .extra_selections()
124 .last()
125 .map(|s| s.head)
126 .unwrap_or_else(|| self.head());
127 let line = self.buf().line_of(base);
128 if line + 1 >= self.buf().len_lines()
130 || self.buf().line_start(line + 1) >= self.buf().len_bytes()
131 {
132 self.message = "no line below".into();
133 return;
134 }
135 let col = self.buf().col_of(base);
136 let start = self.buf().line_start(line + 1);
137 let end = self.buf().line_end(line + 1);
138 let pos = (start + col).min(end.saturating_sub(1).max(start));
139 self.sels_mut().plant_extra(pos);
140 let n = self.sels().count();
141 self.message = format!("{n} cursors");
142 }
143
144 pub(crate) fn collapse_cursors(&mut self) {
146 if self.sels().count() > 1 {
147 self.sels_mut().collapse_extras();
148 self.message = "1 cursor".into();
149 }
150 }
151
152 pub fn scroll_to_cursor(&mut self, rows: usize) {
156 self.view_rows = rows;
157 let line = self.buf().line_of(self.head());
158 if line < self.view_top() {
159 self.view_mut().view_top = line;
160 } else if line >= self.view_top() + rows {
161 self.view_mut().view_top = line + 1 - rows;
162 }
163 }
164}