pdfrum_form/edit/select.rs
1//! The text selection: two places, stored directional.
2//!
3//! `begin` is the **anchor**, the end that stays put; `end` is the **active**
4//! end, the one the caret drags around. Nothing orders them on the way in, so
5//! a backwards selection is a legal, reachable state — a shift-left from the
6//! middle of a field produces one, and replacing it has to put the caret in
7//! the same place a forwards selection of the same span would.
8//!
9//! # The distinction that is easy to miss
10//!
11//! *Collapsed* and *reset* are different states and the difference is
12//! observable. After every mutation and after a mouse-down, the selection is
13//! set to `(caret, caret)`: it selects nothing, but it is a **live anchor**,
14//! so the next shift-arrow or drag extends from exactly there. A reset
15//! selection has no anchor at all, and a shift-arrow starting from one has to
16//! anchor on the caret's *previous* position instead. Both report
17//! `is_empty()`; only [`Selection::is_reset`] tells them apart.
18//!
19//! ```
20//! use pdfrum_form::edit::{Place, Selection};
21//!
22//! let collapsed = Selection::collapsed_at(Place::new(0, 0, Some(3)));
23//! let reset = Selection::reset();
24//!
25//! // Both select nothing, and both report empty.
26//! assert!(collapsed.is_empty());
27//! assert!(reset.is_empty());
28//! // Only one of them has an anchor for a shift-move to extend from.
29//! assert!(!collapsed.is_reset());
30//! assert!(reset.is_reset());
31//! ```
32
33use super::place::{Place, Range};
34
35/// A selection: an anchor and an active end, in that order, unordered by
36/// value.
37///
38/// ```
39/// use pdfrum_form::edit::{Place, Selection};
40///
41/// let lo = Place::new(0, 0, Some(1));
42/// let hi = Place::new(0, 0, Some(5));
43/// let forwards = Selection::new(lo, hi);
44/// let backwards = Selection::new(hi, lo);
45///
46/// // The direction is kept, so the two are not equal.
47/// assert!(!forwards.is_backwards());
48/// assert!(backwards.is_backwards());
49/// assert_ne!(forwards, backwards);
50/// // They still cover the same text.
51/// assert_eq!(forwards.range(), backwards.range());
52/// ```
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct Selection {
55 /// The fixed end.
56 pub begin: Place,
57 /// The end that moves with the caret.
58 pub end: Place,
59}
60
61impl Selection {
62 /// A collapsed selection at the start of the text.
63 ///
64 /// ```
65 /// use pdfrum_form::edit::{Place, PlaceExt, Selection};
66 ///
67 /// assert!(Selection::empty().is_empty());
68 /// assert_eq!(Selection::empty().begin, Place::start());
69 /// // An anchor at the start is still an anchor, so this is no reset.
70 /// assert!(!Selection::empty().is_reset());
71 /// ```
72 #[must_use]
73 pub fn empty() -> Selection {
74 Selection {
75 begin: Place::start(),
76 end: Place::start(),
77 }
78 }
79
80 /// A selection anchored at `begin`, active at `end`.
81 ///
82 /// ```
83 /// use pdfrum_form::edit::{Place, Selection};
84 ///
85 /// let sel = Selection::new(Place::new(0, 0, Some(7)), Place::new(0, 0, Some(2)));
86 /// // Nothing orders the two on the way in: this one runs backwards.
87 /// assert!(sel.is_backwards());
88 /// assert_eq!(sel.range().begin(), Place::new(0, 0, Some(2)));
89 /// ```
90 #[must_use]
91 pub fn new(begin: Place, end: Place) -> Selection {
92 Selection { begin, end }
93 }
94
95 /// A live anchor at one place, selecting nothing.
96 ///
97 /// What a mutation or a mouse-down leaves behind, and what a following
98 /// shift-move extends from.
99 ///
100 /// ```
101 /// use pdfrum_form::edit::{Place, Selection};
102 ///
103 /// let here = Place::new(0, 0, Some(3));
104 /// let sel = Selection::collapsed_at(here);
105 /// assert!(sel.is_empty());
106 /// assert!(!sel.is_reset(), "the anchor is live");
107 /// assert_eq!(sel.begin, here);
108 /// ```
109 #[must_use]
110 pub fn collapsed_at(place: Place) -> Selection {
111 Selection {
112 begin: place,
113 end: place,
114 }
115 }
116
117 /// No selection and no anchor.
118 ///
119 /// Distinct from a collapsed selection: a shift-move from here anchors on
120 /// the caret's previous position rather than on this one.
121 ///
122 /// ```
123 /// use pdfrum_form::edit::{Place, Selection};
124 ///
125 /// assert!(Selection::reset().is_reset());
126 /// assert_ne!(
127 /// Selection::reset(),
128 /// Selection::collapsed_at(Place::new(0, 0, Some(3)))
129 /// );
130 /// ```
131 #[must_use]
132 pub fn reset() -> Selection {
133 Selection {
134 begin: Place {
135 section: u32::MAX,
136 line: u32::MAX,
137 word: None,
138 },
139 end: Place {
140 section: u32::MAX,
141 line: u32::MAX,
142 word: None,
143 },
144 }
145 }
146
147 /// Whether the selection covers no text.
148 ///
149 /// Says nothing about whether an anchor exists; see
150 /// [`Selection::is_reset`].
151 ///
152 /// ```
153 /// use pdfrum_form::edit::{Place, Selection};
154 ///
155 /// let here = Place::new(0, 0, Some(3));
156 /// assert!(Selection::collapsed_at(here).is_empty());
157 /// assert!(!Selection::new(here, Place::new(0, 0, Some(5))).is_empty());
158 /// ```
159 #[must_use]
160 pub fn is_empty(self) -> bool {
161 self.begin == self.end
162 }
163
164 /// Whether the selection has no anchor at all.
165 ///
166 /// ```
167 /// use pdfrum_form::edit::{Place, Selection};
168 ///
169 /// assert!(Selection::reset().is_reset());
170 /// assert!(!Selection::collapsed_at(Place::new(0, 0, Some(3))).is_reset());
171 /// ```
172 #[must_use]
173 pub fn is_reset(self) -> bool {
174 self.begin
175 == Place {
176 section: u32::MAX,
177 line: u32::MAX,
178 word: None,
179 }
180 && self.end
181 == Place {
182 section: u32::MAX,
183 line: u32::MAX,
184 word: None,
185 }
186 }
187
188 /// Whether the active end lies before the anchor.
189 ///
190 /// ```
191 /// use pdfrum_form::edit::{Place, Selection};
192 ///
193 /// let lo = Place::new(0, 0, Some(1));
194 /// let hi = Place::new(0, 0, Some(5));
195 /// assert!(Selection::new(hi, lo).is_backwards());
196 /// assert!(!Selection::new(lo, hi).is_backwards());
197 /// ```
198 #[must_use]
199 pub fn is_backwards(self) -> bool {
200 self.end < self.begin
201 }
202
203 /// The span, ordered. Direction is discarded here and nowhere else.
204 ///
205 /// ```
206 /// use pdfrum_form::edit::{Place, Selection};
207 ///
208 /// let sel = Selection::new(Place::new(0, 0, Some(7)), Place::new(0, 0, Some(2)));
209 /// assert!(sel.range().begin() <= sel.range().end());
210 /// assert_eq!(sel.range().begin(), Place::new(0, 0, Some(2)));
211 /// ```
212 #[must_use]
213 pub fn range(self) -> Range {
214 Range::new(self.begin, self.end)
215 }
216
217 /// Moves the active end, keeping the anchor. What a shift-move does once
218 /// an anchor exists.
219 ///
220 /// ```
221 /// use pdfrum_form::edit::{Place, Selection};
222 ///
223 /// let anchor = Place::new(0, 0, Some(1));
224 /// let mut sel = Selection::collapsed_at(anchor);
225 /// sel.set_active(Place::new(0, 0, Some(4)));
226 ///
227 /// assert_eq!(sel.begin, anchor, "the anchor stays put");
228 /// assert_eq!(sel.end, Place::new(0, 0, Some(4)));
229 /// assert!(!sel.is_empty());
230 /// ```
231 pub fn set_active(&mut self, place: Place) {
232 self.end = place;
233 }
234}
235
236impl Default for Selection {
237 fn default() -> Selection {
238 Selection::empty()
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn a_selection_keeps_the_direction_it_was_built_with() {
248 let lo = Place::new(0, 0, Some(1));
249 let hi = Place::new(0, 0, Some(5));
250 let forwards = Selection::new(lo, hi);
251 let backwards = Selection::new(hi, lo);
252
253 assert!(!forwards.is_backwards());
254 assert!(backwards.is_backwards());
255 assert_ne!(forwards, backwards);
256 // …but they cover the same text.
257 assert_eq!(forwards.range(), backwards.range());
258 }
259
260 /// Both report empty; only one has an anchor to extend from.
261 #[test]
262 fn collapsed_and_reset_are_different_states() {
263 let here = Place::new(0, 0, Some(3));
264 let collapsed = Selection::collapsed_at(here);
265 let reset = Selection::reset();
266
267 assert!(collapsed.is_empty());
268 assert!(reset.is_empty());
269
270 assert!(!collapsed.is_reset());
271 assert!(reset.is_reset());
272 assert_ne!(collapsed, reset);
273 }
274
275 #[test]
276 fn extending_moves_only_the_active_end() {
277 let anchor = Place::new(0, 0, Some(1));
278 let mut sel = Selection::collapsed_at(anchor);
279 sel.set_active(Place::new(0, 0, Some(4)));
280
281 assert_eq!(sel.begin, anchor);
282 assert_eq!(sel.end, Place::new(0, 0, Some(4)));
283 assert!(!sel.is_empty());
284 }
285
286 #[test]
287 fn the_range_is_always_ordered() {
288 let sel = Selection::new(Place::new(0, 0, Some(7)), Place::new(0, 0, Some(2)));
289 assert!(sel.range().begin() <= sel.range().end());
290 assert_eq!(sel.range().begin(), Place::new(0, 0, Some(2)));
291 }
292}