longcipher_leptos_components/components/editor/
cursor.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
9pub struct CursorPosition {
10 pub line: usize,
12 pub column: usize,
14}
15
16impl CursorPosition {
17 #[must_use]
19 pub const fn new(line: usize, column: usize) -> Self {
20 Self { line, column }
21 }
22
23 #[must_use]
25 pub const fn zero() -> Self {
26 Self { line: 0, column: 0 }
27 }
28
29 #[must_use]
31 pub fn is_before(&self, other: &Self) -> bool {
32 self.line < other.line || (self.line == other.line && self.column < other.column)
33 }
34
35 #[must_use]
37 pub fn min(&self, other: &Self) -> Self {
38 if self.is_before(other) { *self } else { *other }
39 }
40
41 #[must_use]
43 pub fn max(&self, other: &Self) -> Self {
44 if self.is_before(other) { *other } else { *self }
45 }
46}
47
48impl PartialOrd for CursorPosition {
49 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
50 Some(self.cmp(other))
51 }
52}
53
54impl Ord for CursorPosition {
55 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
56 match self.line.cmp(&other.line) {
57 std::cmp::Ordering::Equal => self.column.cmp(&other.column),
58 ord => ord,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
65pub struct Cursor {
66 pub head: CursorPosition,
68 pub anchor: CursorPosition,
70 pub preferred_column: Option<usize>,
72}
73
74impl Cursor {
75 #[must_use]
77 pub const fn new(position: CursorPosition) -> Self {
78 Self {
79 head: position,
80 anchor: position,
81 preferred_column: None,
82 }
83 }
84
85 #[must_use]
87 pub const fn zero() -> Self {
88 Self::new(CursorPosition::zero())
89 }
90
91 #[must_use]
93 pub const fn with_selection(head: CursorPosition, anchor: CursorPosition) -> Self {
94 Self {
95 head,
96 anchor,
97 preferred_column: None,
98 }
99 }
100
101 #[must_use]
103 pub fn has_selection(&self) -> bool {
104 self.head != self.anchor
105 }
106
107 #[must_use]
109 pub fn selection_start(&self) -> CursorPosition {
110 self.head.min(self.anchor)
111 }
112
113 #[must_use]
115 pub fn selection_end(&self) -> CursorPosition {
116 self.head.max(self.anchor)
117 }
118
119 pub fn collapse(&mut self) {
121 self.anchor = self.head;
122 }
123
124 pub fn move_to(&mut self, position: CursorPosition, extend_selection: bool) {
126 self.head = position;
127 if !extend_selection {
128 self.anchor = position;
129 }
130 }
131
132 pub fn set_preferred_column(&mut self, column: usize) {
134 self.preferred_column = Some(column);
135 }
136
137 pub fn clear_preferred_column(&mut self) {
139 self.preferred_column = None;
140 }
141}
142
143#[derive(Debug, Clone, Default, Serialize, Deserialize)]
145pub struct CursorSet {
146 cursors: Vec<Cursor>,
148}
149
150impl CursorSet {
151 #[must_use]
153 pub fn new(cursor: Cursor) -> Self {
154 Self {
155 cursors: vec![cursor],
156 }
157 }
158
159 #[must_use]
165 pub fn primary(&self) -> &Cursor {
166 self.cursors
167 .first()
168 .expect("CursorSet must have at least one cursor")
169 }
170
171 pub fn primary_mut(&mut self) -> &mut Cursor {
177 self.cursors
178 .first_mut()
179 .expect("CursorSet must have at least one cursor")
180 }
181
182 #[must_use]
184 pub fn all(&self) -> &[Cursor] {
185 &self.cursors
186 }
187
188 #[must_use]
190 pub fn is_multi(&self) -> bool {
191 self.cursors.len() > 1
192 }
193
194 pub fn add(&mut self, cursor: Cursor) {
196 self.cursors.push(cursor);
197 self.merge_overlapping();
198 }
199
200 pub fn collapse_to_primary(&mut self) {
202 if self.cursors.len() > 1 {
203 let primary = self.cursors[0];
204 self.cursors.clear();
205 self.cursors.push(primary);
206 }
207 }
208
209 fn merge_overlapping(&mut self) {
211 if self.cursors.len() <= 1 {
212 return;
213 }
214
215 self.cursors.sort_by(|a, b| {
217 let a_start = a.selection_start();
218 let b_start = b.selection_start();
219 a_start.cmp(&b_start)
220 });
221
222 let mut merged = Vec::with_capacity(self.cursors.len());
223 merged.push(self.cursors[0]);
224
225 for cursor in &self.cursors[1..] {
226 let last = merged.last_mut().expect("merged is not empty");
227 let last_end = last.selection_end();
228 let cursor_start = cursor.selection_start();
229
230 if cursor_start <= last_end {
231 let cursor_end = cursor.selection_end();
233 if cursor_end > last_end {
234 if cursor.head > cursor.anchor {
235 last.head = cursor.head;
236 } else {
237 last.anchor = cursor_end;
238 }
239 }
240 } else {
241 merged.push(*cursor);
243 }
244 }
245
246 self.cursors = merged;
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_cursor_position_ordering() {
256 let a = CursorPosition::new(0, 5);
257 let b = CursorPosition::new(1, 0);
258 let c = CursorPosition::new(1, 3);
259
260 assert!(a.is_before(&b));
261 assert!(b.is_before(&c));
262 assert!(!c.is_before(&a));
263 }
264
265 #[test]
266 fn test_cursor_selection() {
267 let cursor = Cursor::with_selection(CursorPosition::new(1, 5), CursorPosition::new(0, 3));
268
269 assert!(cursor.has_selection());
270 assert_eq!(cursor.selection_start(), CursorPosition::new(0, 3));
271 assert_eq!(cursor.selection_end(), CursorPosition::new(1, 5));
272 }
273
274 #[test]
275 fn test_cursor_set_merge() {
276 let mut set = CursorSet::new(Cursor::with_selection(
277 CursorPosition::new(0, 0),
278 CursorPosition::new(0, 2),
279 ));
280 set.add(Cursor::new(CursorPosition::new(0, 1)));
281 set.add(Cursor::new(CursorPosition::new(2, 0)));
282
283 assert_eq!(set.all().len(), 2);
285 }
286}