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 selection_span(
75 &self,
76 selection: strop_core::selection::Selection,
77 ) -> (usize, usize) {
78 let (start, last) = selection.range();
79 let end = self
80 .buf()
81 .ceil_boundary((last + 1).min(self.buf().len_bytes()));
82 (start, end.min(self.buf().len_bytes()))
83 }
84
85 pub(crate) fn all_cursors(&self) -> Vec<usize> {
87 self.sels().heads()
88 }
89
90 pub(crate) fn normalize_cursors(&mut self) {
94 self.sels_mut().normalize();
95 }
96
97 pub(crate) fn remap_after_mirrored_edit(&mut self, positions: &[usize], delta: isize) {
101 let map = |old: usize| -> usize {
102 let below = positions.partition_point(|&p| p < old);
103 let own = usize::from(positions.contains(&old));
104 (old as isize + delta * (below + own) as isize).max(0) as usize
105 };
106 self.sels_mut().map_positions(map);
109 }
110
111 pub(crate) fn toggle_cursor(&mut self) {
113 if self.buf().readonly {
114 self.message = "readonly buffer".into();
115 return;
116 }
117 self.sels_mut().toggle_extra();
118 let n = self.sels().count();
119 self.message = format!("{n} cursor{}", if n > 1 { "s" } else { "" });
120 }
121
122 pub(crate) fn add_cursor_next_line(&mut self) {
125 if self.buf().readonly {
126 self.message = "readonly buffer".into();
127 return;
128 }
129 let base = self
132 .extra_selections()
133 .last()
134 .map(|s| s.head)
135 .unwrap_or_else(|| self.head());
136 let line = self.buf().line_of(base);
137 if line + 1 >= self.buf().len_lines()
139 || self.buf().line_start(line + 1) >= self.buf().len_bytes()
140 {
141 self.message = "no line below".into();
142 return;
143 }
144 let col = self.buf().col_of(base);
145 let start = self.buf().line_start(line + 1);
146 let end = self.buf().line_end(line + 1);
147 let pos = (start + col).min(end.saturating_sub(1).max(start));
148 self.sels_mut().plant_extra(pos);
149 let n = self.sels().count();
150 self.message = format!("{n} cursors");
151 }
152
153 pub(crate) fn collapse_cursors(&mut self) {
157 let occurrence = self.occurrence.take().is_some();
158 if occurrence {
159 let head = self.head();
160 self.sels_mut().collapse_primary(head);
161 }
162 if self.sels().count() > 1 {
163 self.sels_mut().collapse_extras();
164 self.message = "1 cursor".into();
165 } else if occurrence {
166 self.message = "occurrence selection cleared".into();
167 }
168 }
169
170 pub fn scroll_to_cursor(&mut self, rows: usize) {
174 self.view_rows = rows;
175 let line = self.buf().line_of(self.head());
176 if line < self.view_top() {
177 self.view_mut().view_top = line;
178 } else if line >= self.view_top() + rows {
179 self.view_mut().view_top = line + 1 - rows;
180 }
181 }
182}