1 | // Copyright 2022 The AccessKit Authors. All rights reserved. |
2 | // Licensed under the Apache License, Version 2.0 (found in |
3 | // the LICENSE-APACHE file) or the MIT license (found in |
4 | // the LICENSE-MIT file), at your option. |
5 | |
6 | use accesskit::{ |
7 | NodeId, Point, Rect, Role, TextDirection, TextPosition as WeakPosition, TextSelection, |
8 | }; |
9 | use std::{cmp::Ordering, iter::FusedIterator}; |
10 | |
11 | use crate::{FilterResult, Node, TreeState}; |
12 | |
13 | #[derive (Clone, Copy)] |
14 | pub(crate) struct InnerPosition<'a> { |
15 | pub(crate) node: Node<'a>, |
16 | pub(crate) character_index: usize, |
17 | } |
18 | |
19 | impl<'a> InnerPosition<'a> { |
20 | fn upgrade(tree_state: &'a TreeState, weak: WeakPosition) -> Option<Self> { |
21 | let node = tree_state.node_by_id(weak.node)?; |
22 | if node.role() != Role::InlineTextBox { |
23 | return None; |
24 | } |
25 | let character_index = weak.character_index; |
26 | if character_index > node.data().character_lengths().len() { |
27 | return None; |
28 | } |
29 | Some(Self { |
30 | node, |
31 | character_index, |
32 | }) |
33 | } |
34 | |
35 | fn is_word_start(&self) -> bool { |
36 | let mut total_length = 0usize; |
37 | for length in self.node.data().word_lengths().iter() { |
38 | if total_length == self.character_index { |
39 | return true; |
40 | } |
41 | total_length += *length as usize; |
42 | } |
43 | false |
44 | } |
45 | |
46 | fn is_box_start(&self) -> bool { |
47 | self.character_index == 0 |
48 | } |
49 | |
50 | fn is_line_start(&self) -> bool { |
51 | self.is_box_start() && self.node.data().previous_on_line().is_none() |
52 | } |
53 | |
54 | fn is_box_end(&self) -> bool { |
55 | self.character_index == self.node.data().character_lengths().len() |
56 | } |
57 | |
58 | fn is_line_end(&self) -> bool { |
59 | self.is_box_end() && self.node.data().next_on_line().is_none() |
60 | } |
61 | |
62 | fn is_paragraph_end(&self) -> bool { |
63 | self.is_line_end() && self.node.data().value().unwrap().ends_with(' \n' ) |
64 | } |
65 | |
66 | fn is_document_start(&self, root_node: &Node) -> bool { |
67 | self.is_box_start() |
68 | && self |
69 | .node |
70 | .preceding_inline_text_boxes(root_node) |
71 | .next() |
72 | .is_none() |
73 | } |
74 | |
75 | fn is_document_end(&self, root_node: &Node) -> bool { |
76 | self.is_box_end() |
77 | && self |
78 | .node |
79 | .following_inline_text_boxes(root_node) |
80 | .next() |
81 | .is_none() |
82 | } |
83 | |
84 | fn biased_to_start(&self, root_node: &Node) -> Self { |
85 | if self.is_box_end() { |
86 | if let Some(node) = self.node.following_inline_text_boxes(root_node).next() { |
87 | return Self { |
88 | node, |
89 | character_index: 0, |
90 | }; |
91 | } |
92 | } |
93 | *self |
94 | } |
95 | |
96 | fn biased_to_end(&self, root_node: &Node) -> Self { |
97 | if self.is_box_start() { |
98 | if let Some(node) = self.node.preceding_inline_text_boxes(root_node).next() { |
99 | return Self { |
100 | node, |
101 | character_index: node.data().character_lengths().len(), |
102 | }; |
103 | } |
104 | } |
105 | *self |
106 | } |
107 | |
108 | fn comparable(&self, root_node: &Node) -> (Vec<usize>, usize) { |
109 | let normalized = self.biased_to_start(root_node); |
110 | ( |
111 | normalized.node.relative_index_path(root_node.id()), |
112 | normalized.character_index, |
113 | ) |
114 | } |
115 | |
116 | fn previous_word_start(&self) -> Self { |
117 | let mut total_length_before = 0usize; |
118 | for length in self.node.data().word_lengths().iter() { |
119 | let new_total_length = total_length_before + (*length as usize); |
120 | if new_total_length >= self.character_index { |
121 | break; |
122 | } |
123 | total_length_before = new_total_length; |
124 | } |
125 | Self { |
126 | node: self.node, |
127 | character_index: total_length_before, |
128 | } |
129 | } |
130 | |
131 | fn word_end(&self) -> Self { |
132 | let mut total_length = 0usize; |
133 | for length in self.node.data().word_lengths().iter() { |
134 | total_length += *length as usize; |
135 | if total_length > self.character_index { |
136 | break; |
137 | } |
138 | } |
139 | Self { |
140 | node: self.node, |
141 | character_index: total_length, |
142 | } |
143 | } |
144 | |
145 | fn line_start(&self) -> Self { |
146 | let mut node = self.node; |
147 | while let Some(id) = node.data().previous_on_line() { |
148 | node = node.tree_state.node_by_id(id).unwrap(); |
149 | } |
150 | Self { |
151 | node, |
152 | character_index: 0, |
153 | } |
154 | } |
155 | |
156 | fn line_end(&self) -> Self { |
157 | let mut node = self.node; |
158 | while let Some(id) = node.data().next_on_line() { |
159 | node = node.tree_state.node_by_id(id).unwrap(); |
160 | } |
161 | Self { |
162 | node, |
163 | character_index: node.data().character_lengths().len(), |
164 | } |
165 | } |
166 | |
167 | pub(crate) fn downgrade(&self) -> WeakPosition { |
168 | WeakPosition { |
169 | node: self.node.id(), |
170 | character_index: self.character_index, |
171 | } |
172 | } |
173 | } |
174 | |
175 | impl<'a> PartialEq for InnerPosition<'a> { |
176 | fn eq(&self, other: &Self) -> bool { |
177 | self.node.id() == other.node.id() && self.character_index == other.character_index |
178 | } |
179 | } |
180 | |
181 | impl<'a> Eq for InnerPosition<'a> {} |
182 | |
183 | #[derive (Clone, Copy)] |
184 | pub struct Position<'a> { |
185 | root_node: Node<'a>, |
186 | pub(crate) inner: InnerPosition<'a>, |
187 | } |
188 | |
189 | impl<'a> Position<'a> { |
190 | pub fn inner_node(&self) -> &Node { |
191 | &self.inner.node |
192 | } |
193 | |
194 | pub fn is_format_start(&self) -> bool { |
195 | // TODO: support variable text formatting (part of rich text) |
196 | self.is_document_start() |
197 | } |
198 | |
199 | pub fn is_word_start(&self) -> bool { |
200 | self.inner.is_word_start() |
201 | } |
202 | |
203 | pub fn is_line_start(&self) -> bool { |
204 | self.inner.is_line_start() |
205 | } |
206 | |
207 | pub fn is_line_end(&self) -> bool { |
208 | self.inner.is_line_end() |
209 | } |
210 | |
211 | pub fn is_paragraph_start(&self) -> bool { |
212 | self.is_document_start() |
213 | || (self.is_line_start() |
214 | && self.inner.biased_to_end(&self.root_node).is_paragraph_end()) |
215 | } |
216 | |
217 | pub fn is_paragraph_end(&self) -> bool { |
218 | self.is_document_end() || self.inner.is_paragraph_end() |
219 | } |
220 | |
221 | pub fn is_page_start(&self) -> bool { |
222 | self.is_document_start() |
223 | } |
224 | |
225 | pub fn is_document_start(&self) -> bool { |
226 | self.inner.is_document_start(&self.root_node) |
227 | } |
228 | |
229 | pub fn is_document_end(&self) -> bool { |
230 | self.inner.is_document_end(&self.root_node) |
231 | } |
232 | |
233 | pub fn to_degenerate_range(&self) -> Range<'a> { |
234 | Range::new(self.root_node, self.inner, self.inner) |
235 | } |
236 | |
237 | pub fn to_global_utf16_index(&self) -> usize { |
238 | let mut total_length = 0usize; |
239 | for node in self.root_node.inline_text_boxes() { |
240 | let node_text = node.data().value().unwrap(); |
241 | if node.id() == self.inner.node.id() { |
242 | let character_lengths = node.data().character_lengths(); |
243 | let slice_end = character_lengths[..self.inner.character_index] |
244 | .iter() |
245 | .copied() |
246 | .map(usize::from) |
247 | .sum::<usize>(); |
248 | return total_length |
249 | + node_text[..slice_end] |
250 | .chars() |
251 | .map(char::len_utf16) |
252 | .sum::<usize>(); |
253 | } |
254 | total_length += node_text.chars().map(char::len_utf16).sum::<usize>(); |
255 | } |
256 | panic!("invalid position" ) |
257 | } |
258 | |
259 | pub fn to_line_index(&self) -> usize { |
260 | let mut pos = *self; |
261 | if !pos.is_line_start() { |
262 | pos = pos.backward_to_line_start(); |
263 | } |
264 | let mut lines_before_current = 0usize; |
265 | while !pos.is_document_start() { |
266 | pos = pos.backward_to_line_start(); |
267 | lines_before_current += 1; |
268 | } |
269 | lines_before_current |
270 | } |
271 | |
272 | pub fn forward_to_character_start(&self) -> Self { |
273 | let pos = self.inner.biased_to_start(&self.root_node); |
274 | Self { |
275 | root_node: self.root_node, |
276 | inner: InnerPosition { |
277 | node: pos.node, |
278 | character_index: pos.character_index + 1, |
279 | } |
280 | .biased_to_start(&self.root_node), |
281 | } |
282 | } |
283 | |
284 | pub fn forward_to_character_end(&self) -> Self { |
285 | let pos = self.inner.biased_to_start(&self.root_node); |
286 | Self { |
287 | root_node: self.root_node, |
288 | inner: InnerPosition { |
289 | node: pos.node, |
290 | character_index: pos.character_index + 1, |
291 | }, |
292 | } |
293 | } |
294 | |
295 | pub fn backward_to_character_start(&self) -> Self { |
296 | let pos = self.inner.biased_to_end(&self.root_node); |
297 | Self { |
298 | root_node: self.root_node, |
299 | inner: InnerPosition { |
300 | node: pos.node, |
301 | character_index: pos.character_index - 1, |
302 | } |
303 | .biased_to_start(&self.root_node), |
304 | } |
305 | } |
306 | |
307 | pub fn forward_to_format_start(&self) -> Self { |
308 | // TODO: support variable text formatting (part of rich text) |
309 | self.document_end() |
310 | } |
311 | |
312 | pub fn forward_to_format_end(&self) -> Self { |
313 | // TODO: support variable text formatting (part of rich text) |
314 | self.document_end() |
315 | } |
316 | |
317 | pub fn backward_to_format_start(&self) -> Self { |
318 | // TODO: support variable text formatting (part of rich text) |
319 | self.document_start() |
320 | } |
321 | |
322 | pub fn forward_to_word_start(&self) -> Self { |
323 | let pos = self.inner.biased_to_start(&self.root_node); |
324 | Self { |
325 | root_node: self.root_node, |
326 | inner: pos.word_end().biased_to_start(&self.root_node), |
327 | } |
328 | } |
329 | |
330 | pub fn forward_to_word_end(&self) -> Self { |
331 | let pos = self.inner.biased_to_start(&self.root_node); |
332 | Self { |
333 | root_node: self.root_node, |
334 | inner: pos.word_end(), |
335 | } |
336 | } |
337 | |
338 | pub fn backward_to_word_start(&self) -> Self { |
339 | let pos = self.inner.biased_to_end(&self.root_node); |
340 | Self { |
341 | root_node: self.root_node, |
342 | inner: pos.previous_word_start().biased_to_start(&self.root_node), |
343 | } |
344 | } |
345 | |
346 | pub fn forward_to_line_start(&self) -> Self { |
347 | let pos = self.inner.biased_to_start(&self.root_node); |
348 | Self { |
349 | root_node: self.root_node, |
350 | inner: pos.line_end().biased_to_start(&self.root_node), |
351 | } |
352 | } |
353 | |
354 | pub fn forward_to_line_end(&self) -> Self { |
355 | let pos = self.inner.biased_to_start(&self.root_node); |
356 | Self { |
357 | root_node: self.root_node, |
358 | inner: pos.line_end(), |
359 | } |
360 | } |
361 | |
362 | pub fn backward_to_line_start(&self) -> Self { |
363 | let pos = self.inner.biased_to_end(&self.root_node); |
364 | Self { |
365 | root_node: self.root_node, |
366 | inner: pos.line_start().biased_to_start(&self.root_node), |
367 | } |
368 | } |
369 | |
370 | pub fn forward_to_paragraph_start(&self) -> Self { |
371 | let mut current = *self; |
372 | loop { |
373 | current = current.forward_to_line_start(); |
374 | if current.is_document_end() |
375 | || current |
376 | .inner |
377 | .biased_to_end(&self.root_node) |
378 | .is_paragraph_end() |
379 | { |
380 | break; |
381 | } |
382 | } |
383 | current |
384 | } |
385 | |
386 | pub fn forward_to_paragraph_end(&self) -> Self { |
387 | let mut current = *self; |
388 | loop { |
389 | current = current.forward_to_line_end(); |
390 | if current.is_document_end() || current.inner.is_paragraph_end() { |
391 | break; |
392 | } |
393 | } |
394 | current |
395 | } |
396 | |
397 | pub fn backward_to_paragraph_start(&self) -> Self { |
398 | let mut current = *self; |
399 | loop { |
400 | current = current.backward_to_line_start(); |
401 | if current.is_paragraph_start() { |
402 | break; |
403 | } |
404 | } |
405 | current |
406 | } |
407 | |
408 | pub fn forward_to_page_start(&self) -> Self { |
409 | self.document_end() |
410 | } |
411 | |
412 | pub fn forward_to_page_end(&self) -> Self { |
413 | self.document_end() |
414 | } |
415 | |
416 | pub fn backward_to_page_start(&self) -> Self { |
417 | self.document_start() |
418 | } |
419 | |
420 | pub fn document_end(&self) -> Self { |
421 | Self { |
422 | root_node: self.root_node, |
423 | inner: self.root_node.document_end(), |
424 | } |
425 | } |
426 | |
427 | pub fn document_start(&self) -> Self { |
428 | Self { |
429 | root_node: self.root_node, |
430 | inner: self.root_node.document_start(), |
431 | } |
432 | } |
433 | } |
434 | |
435 | impl<'a> PartialEq for Position<'a> { |
436 | fn eq(&self, other: &Self) -> bool { |
437 | self.root_node.id() == other.root_node.id() && self.inner == other.inner |
438 | } |
439 | } |
440 | |
441 | impl<'a> Eq for Position<'a> {} |
442 | |
443 | impl<'a> PartialOrd for Position<'a> { |
444 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
445 | if self.root_node.id() != other.root_node.id() { |
446 | return None; |
447 | } |
448 | let self_comparable: (Vec, usize) = self.inner.comparable(&self.root_node); |
449 | let other_comparable: (Vec, usize) = other.inner.comparable(&self.root_node); |
450 | Some(self_comparable.cmp(&other_comparable)) |
451 | } |
452 | } |
453 | |
454 | pub enum AttributeValue<T> { |
455 | Single(T), |
456 | Mixed, |
457 | } |
458 | |
459 | #[derive (Clone, Copy)] |
460 | pub struct Range<'a> { |
461 | pub(crate) node: Node<'a>, |
462 | pub(crate) start: InnerPosition<'a>, |
463 | pub(crate) end: InnerPosition<'a>, |
464 | } |
465 | |
466 | impl<'a> Range<'a> { |
467 | fn new(node: Node<'a>, mut start: InnerPosition<'a>, mut end: InnerPosition<'a>) -> Self { |
468 | if start.comparable(&node) > end.comparable(&node) { |
469 | std::mem::swap(&mut start, &mut end); |
470 | } |
471 | Self { node, start, end } |
472 | } |
473 | |
474 | pub fn node(&self) -> &Node { |
475 | &self.node |
476 | } |
477 | |
478 | pub fn start(&self) -> Position<'a> { |
479 | Position { |
480 | root_node: self.node, |
481 | inner: self.start, |
482 | } |
483 | } |
484 | |
485 | pub fn end(&self) -> Position<'a> { |
486 | Position { |
487 | root_node: self.node, |
488 | inner: self.end, |
489 | } |
490 | } |
491 | |
492 | pub fn is_degenerate(&self) -> bool { |
493 | self.start.comparable(&self.node) == self.end.comparable(&self.node) |
494 | } |
495 | |
496 | fn walk<F, T>(&self, mut f: F) -> Option<T> |
497 | where |
498 | F: FnMut(&Node) -> Option<T>, |
499 | { |
500 | // If the range is degenerate, we don't want to normalize it. |
501 | // This is important e.g. when getting the bounding rectangle |
502 | // of the caret range when the caret is at the end of a wrapped line. |
503 | let (start, end) = if self.is_degenerate() { |
504 | (self.start, self.start) |
505 | } else { |
506 | let start = self.start.biased_to_start(&self.node); |
507 | let end = self.end.biased_to_end(&self.node); |
508 | (start, end) |
509 | }; |
510 | if let Some(result) = f(&start.node) { |
511 | return Some(result); |
512 | } |
513 | if start.node.id() == end.node.id() { |
514 | return None; |
515 | } |
516 | for node in start.node.following_inline_text_boxes(&self.node) { |
517 | if let Some(result) = f(&node) { |
518 | return Some(result); |
519 | } |
520 | if node.id() == end.node.id() { |
521 | break; |
522 | } |
523 | } |
524 | None |
525 | } |
526 | |
527 | pub fn text(&self) -> String { |
528 | let mut result = String::new(); |
529 | self.walk::<_, ()>(|node| { |
530 | let character_lengths = node.data().character_lengths(); |
531 | let start_index = if node.id() == self.start.node.id() { |
532 | self.start.character_index |
533 | } else { |
534 | 0 |
535 | }; |
536 | let end_index = if node.id() == self.end.node.id() { |
537 | self.end.character_index |
538 | } else { |
539 | character_lengths.len() |
540 | }; |
541 | let value = node.data().value().unwrap(); |
542 | let s = if start_index == end_index { |
543 | "" |
544 | } else if start_index == 0 && end_index == character_lengths.len() { |
545 | value |
546 | } else { |
547 | let slice_start = character_lengths[..start_index] |
548 | .iter() |
549 | .copied() |
550 | .map(usize::from) |
551 | .sum::<usize>(); |
552 | let slice_end = slice_start |
553 | + character_lengths[start_index..end_index] |
554 | .iter() |
555 | .copied() |
556 | .map(usize::from) |
557 | .sum::<usize>(); |
558 | &value[slice_start..slice_end] |
559 | }; |
560 | result.push_str(s); |
561 | None |
562 | }); |
563 | result |
564 | } |
565 | |
566 | /// Returns the range's transformed bounding boxes relative to the tree's |
567 | /// container (e.g. window). |
568 | /// |
569 | /// If the return value is empty, it means that the source tree doesn't |
570 | /// provide enough information to calculate bounding boxes. Otherwise, |
571 | /// there will always be at least one box, even if it's zero-width, |
572 | /// as it is for a degenerate range. |
573 | pub fn bounding_boxes(&self) -> Vec<Rect> { |
574 | let mut result = Vec::new(); |
575 | self.walk(|node| { |
576 | let mut rect = match node.data().bounds() { |
577 | Some(rect) => rect, |
578 | None => { |
579 | return Some(Vec::new()); |
580 | } |
581 | }; |
582 | let positions = match node.data().character_positions() { |
583 | Some(positions) => positions, |
584 | None => { |
585 | return Some(Vec::new()); |
586 | } |
587 | }; |
588 | let widths = match node.data().character_widths() { |
589 | Some(widths) => widths, |
590 | None => { |
591 | return Some(Vec::new()); |
592 | } |
593 | }; |
594 | let direction = match node.data().text_direction() { |
595 | Some(direction) => direction, |
596 | None => { |
597 | return Some(Vec::new()); |
598 | } |
599 | }; |
600 | let character_lengths = node.data().character_lengths(); |
601 | let start_index = if node.id() == self.start.node.id() { |
602 | self.start.character_index |
603 | } else { |
604 | 0 |
605 | }; |
606 | let end_index = if node.id() == self.end.node.id() { |
607 | self.end.character_index |
608 | } else { |
609 | character_lengths.len() |
610 | }; |
611 | if start_index != 0 || end_index != character_lengths.len() { |
612 | let pixel_start = if start_index < character_lengths.len() { |
613 | positions[start_index] |
614 | } else { |
615 | positions[start_index - 1] + widths[start_index - 1] |
616 | }; |
617 | let pixel_end = if end_index == start_index { |
618 | pixel_start |
619 | } else { |
620 | positions[end_index - 1] + widths[end_index - 1] |
621 | }; |
622 | let pixel_start = f64::from(pixel_start); |
623 | let pixel_end = f64::from(pixel_end); |
624 | match direction { |
625 | TextDirection::LeftToRight => { |
626 | let orig_left = rect.x0; |
627 | rect.x0 = orig_left + pixel_start; |
628 | rect.x1 = orig_left + pixel_end; |
629 | } |
630 | TextDirection::RightToLeft => { |
631 | let orig_right = rect.x1; |
632 | rect.x1 = orig_right - pixel_start; |
633 | rect.x0 = orig_right - pixel_end; |
634 | } |
635 | // Note: The following directions assume that the rectangle, |
636 | // in the node's coordinate space, is y-down. TBD: Will we |
637 | // ever encounter a case where this isn't true? |
638 | TextDirection::TopToBottom => { |
639 | let orig_top = rect.y0; |
640 | rect.y0 = orig_top + pixel_start; |
641 | rect.y1 = orig_top + pixel_end; |
642 | } |
643 | TextDirection::BottomToTop => { |
644 | let orig_bottom = rect.y1; |
645 | rect.y1 = orig_bottom - pixel_start; |
646 | rect.y0 = orig_bottom - pixel_end; |
647 | } |
648 | } |
649 | } |
650 | result.push(node.transform().transform_rect_bbox(rect)); |
651 | None |
652 | }) |
653 | .unwrap_or(result) |
654 | } |
655 | |
656 | pub fn attribute<F, T>(&self, f: F) -> AttributeValue<T> |
657 | where |
658 | F: Fn(&Node) -> T, |
659 | T: PartialEq, |
660 | { |
661 | let mut value = None; |
662 | self.walk(|node| { |
663 | let current = f(node); |
664 | if let Some(value) = &value { |
665 | if *value != current { |
666 | return Some(AttributeValue::Mixed); |
667 | } |
668 | } else { |
669 | value = Some(current); |
670 | } |
671 | None |
672 | }) |
673 | .unwrap_or_else(|| AttributeValue::Single(value.unwrap())) |
674 | } |
675 | |
676 | fn fix_start_bias(&mut self) { |
677 | if !self.is_degenerate() { |
678 | self.start = self.start.biased_to_start(&self.node); |
679 | } |
680 | } |
681 | |
682 | pub fn set_start(&mut self, pos: Position<'a>) { |
683 | assert_eq!(pos.root_node.id(), self.node.id()); |
684 | self.start = pos.inner; |
685 | // We use `>=` here because if the two endpoints are equivalent |
686 | // but with a different bias, we want to normalize the bias. |
687 | if self.start.comparable(&self.node) >= self.end.comparable(&self.node) { |
688 | self.end = self.start; |
689 | } |
690 | self.fix_start_bias(); |
691 | } |
692 | |
693 | pub fn set_end(&mut self, pos: Position<'a>) { |
694 | assert_eq!(pos.root_node.id(), self.node.id()); |
695 | self.end = pos.inner; |
696 | // We use `>=` here because if the two endpoints are equivalent |
697 | // but with a different bias, we want to normalize the bias. |
698 | if self.start.comparable(&self.node) >= self.end.comparable(&self.node) { |
699 | self.start = self.end; |
700 | } |
701 | self.fix_start_bias(); |
702 | } |
703 | |
704 | pub fn to_text_selection(&self) -> TextSelection { |
705 | TextSelection { |
706 | anchor: self.start.downgrade(), |
707 | focus: self.end.downgrade(), |
708 | } |
709 | } |
710 | |
711 | pub fn downgrade(&self) -> WeakRange { |
712 | WeakRange { |
713 | node_id: self.node.id(), |
714 | start: self.start.downgrade(), |
715 | end: self.end.downgrade(), |
716 | start_comparable: self.start.comparable(&self.node), |
717 | end_comparable: self.end.comparable(&self.node), |
718 | } |
719 | } |
720 | } |
721 | |
722 | impl<'a> PartialEq for Range<'a> { |
723 | fn eq(&self, other: &Self) -> bool { |
724 | self.node.id() == other.node.id() && self.start == other.start && self.end == other.end |
725 | } |
726 | } |
727 | |
728 | impl<'a> Eq for Range<'a> {} |
729 | |
730 | #[derive (Clone, Debug, PartialEq, Eq)] |
731 | pub struct WeakRange { |
732 | node_id: NodeId, |
733 | start: WeakPosition, |
734 | end: WeakPosition, |
735 | start_comparable: (Vec<usize>, usize), |
736 | end_comparable: (Vec<usize>, usize), |
737 | } |
738 | |
739 | impl WeakRange { |
740 | pub fn node_id(&self) -> NodeId { |
741 | self.node_id |
742 | } |
743 | |
744 | pub fn start_comparable(&self) -> &(Vec<usize>, usize) { |
745 | &self.start_comparable |
746 | } |
747 | |
748 | pub fn end_comparable(&self) -> &(Vec<usize>, usize) { |
749 | &self.end_comparable |
750 | } |
751 | |
752 | pub fn upgrade_node<'a>(&self, tree_state: &'a TreeState) -> Option<Node<'a>> { |
753 | tree_state |
754 | .node_by_id(self.node_id) |
755 | .filter(Node::supports_text_ranges) |
756 | } |
757 | |
758 | pub fn upgrade<'a>(&self, tree_state: &'a TreeState) -> Option<Range<'a>> { |
759 | let node = self.upgrade_node(tree_state)?; |
760 | let start = InnerPosition::upgrade(tree_state, self.start)?; |
761 | let end = InnerPosition::upgrade(tree_state, self.end)?; |
762 | Some(Range { node, start, end }) |
763 | } |
764 | } |
765 | |
766 | fn text_node_filter(root_id: NodeId, node: &Node) -> FilterResult { |
767 | if node.id() == root_id || node.role() == Role::InlineTextBox { |
768 | FilterResult::Include |
769 | } else { |
770 | FilterResult::ExcludeNode |
771 | } |
772 | } |
773 | |
774 | fn character_index_at_point(node: &Node, point: Point) -> usize { |
775 | // We know the node has a bounding rectangle because it was returned |
776 | // by a hit test. |
777 | let rect = node.data().bounds().unwrap(); |
778 | let character_lengths = node.data().character_lengths(); |
779 | let positions = match node.data().character_positions() { |
780 | Some(positions) => positions, |
781 | None => { |
782 | return 0; |
783 | } |
784 | }; |
785 | let widths = match node.data().character_widths() { |
786 | Some(widths) => widths, |
787 | None => { |
788 | return 0; |
789 | } |
790 | }; |
791 | let direction = match node.data().text_direction() { |
792 | Some(direction) => direction, |
793 | None => { |
794 | return 0; |
795 | } |
796 | }; |
797 | for (i, (position, width)) in positions.iter().zip(widths.iter()).enumerate().rev() { |
798 | let relative_pos = match direction { |
799 | TextDirection::LeftToRight => point.x - rect.x0, |
800 | TextDirection::RightToLeft => rect.x1 - point.x, |
801 | // Note: The following directions assume that the rectangle, |
802 | // in the node's coordinate space, is y-down. TBD: Will we |
803 | // ever encounter a case where this isn't true? |
804 | TextDirection::TopToBottom => point.y - rect.y0, |
805 | TextDirection::BottomToTop => rect.y1 - point.y, |
806 | }; |
807 | if relative_pos >= f64::from(*position) && relative_pos < f64::from(*position + *width) { |
808 | return i; |
809 | } |
810 | } |
811 | character_lengths.len() |
812 | } |
813 | |
814 | impl<'a> Node<'a> { |
815 | fn inline_text_boxes( |
816 | &self, |
817 | ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a { |
818 | let id = self.id(); |
819 | self.filtered_children(move |node| text_node_filter(id, node)) |
820 | } |
821 | |
822 | fn following_inline_text_boxes( |
823 | &self, |
824 | root_node: &Node, |
825 | ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a { |
826 | let id = root_node.id(); |
827 | self.following_filtered_siblings(move |node| text_node_filter(id, node)) |
828 | } |
829 | |
830 | fn preceding_inline_text_boxes( |
831 | &self, |
832 | root_node: &Node, |
833 | ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a { |
834 | let id = root_node.id(); |
835 | self.preceding_filtered_siblings(move |node| text_node_filter(id, node)) |
836 | } |
837 | |
838 | pub fn supports_text_ranges(&self) -> bool { |
839 | (self.is_text_input() |
840 | || matches!( |
841 | self.role(), |
842 | Role::StaticText | Role::Document | Role::Terminal |
843 | )) |
844 | && self.inline_text_boxes().next().is_some() |
845 | } |
846 | |
847 | fn document_start(&self) -> InnerPosition<'a> { |
848 | let node = self.inline_text_boxes().next().unwrap(); |
849 | InnerPosition { |
850 | node, |
851 | character_index: 0, |
852 | } |
853 | } |
854 | |
855 | fn document_end(&self) -> InnerPosition<'a> { |
856 | let node = self.inline_text_boxes().next_back().unwrap(); |
857 | InnerPosition { |
858 | node, |
859 | character_index: node.data().character_lengths().len(), |
860 | } |
861 | } |
862 | |
863 | pub fn document_range(&self) -> Range { |
864 | let start = self.document_start(); |
865 | let end = self.document_end(); |
866 | Range::new(*self, start, end) |
867 | } |
868 | |
869 | pub fn has_text_selection(&self) -> bool { |
870 | self.data().text_selection().is_some() |
871 | } |
872 | |
873 | pub fn text_selection(&self) -> Option<Range> { |
874 | self.data().text_selection().map(|selection| { |
875 | let anchor = InnerPosition::upgrade(self.tree_state, selection.anchor).unwrap(); |
876 | let focus = InnerPosition::upgrade(self.tree_state, selection.focus).unwrap(); |
877 | Range::new(*self, anchor, focus) |
878 | }) |
879 | } |
880 | |
881 | pub fn text_selection_focus(&self) -> Option<Position> { |
882 | self.data().text_selection().map(|selection| { |
883 | let focus = InnerPosition::upgrade(self.tree_state, selection.focus).unwrap(); |
884 | Position { |
885 | root_node: *self, |
886 | inner: focus, |
887 | } |
888 | }) |
889 | } |
890 | |
891 | /// Returns the nearest text position to the given point |
892 | /// in this node's coordinate space. |
893 | pub fn text_position_at_point(&self, point: Point) -> Position { |
894 | let id = self.id(); |
895 | if let Some((node, point)) = self.hit_test(point, &move |node| text_node_filter(id, node)) { |
896 | if node.role() == Role::InlineTextBox { |
897 | let pos = InnerPosition { |
898 | node, |
899 | character_index: character_index_at_point(&node, point), |
900 | }; |
901 | return Position { |
902 | root_node: *self, |
903 | inner: pos, |
904 | }; |
905 | } |
906 | } |
907 | |
908 | // The following tests can assume that the point is not within |
909 | // any inline text box. |
910 | |
911 | if let Some(node) = self.inline_text_boxes().next() { |
912 | if let Some(rect) = node.bounding_box_in_coordinate_space(self) { |
913 | let origin = rect.origin(); |
914 | if point.x < origin.x || point.y < origin.y { |
915 | return Position { |
916 | root_node: *self, |
917 | inner: self.document_start(), |
918 | }; |
919 | } |
920 | } |
921 | } |
922 | |
923 | for node in self.inline_text_boxes().rev() { |
924 | if let Some(rect) = node.bounding_box_in_coordinate_space(self) { |
925 | if let Some(direction) = node.data().text_direction() { |
926 | let is_past_end = match direction { |
927 | TextDirection::LeftToRight => { |
928 | point.y >= rect.y0 && point.y < rect.y1 && point.x >= rect.x1 |
929 | } |
930 | TextDirection::RightToLeft => { |
931 | point.y >= rect.y0 && point.y < rect.y1 && point.x < rect.x0 |
932 | } |
933 | // Note: The following directions assume that the rectangle, |
934 | // in the root node's coordinate space, is y-down. TBD: Will we |
935 | // ever encounter a case where this isn't true? |
936 | TextDirection::TopToBottom => { |
937 | point.x >= rect.x0 && point.x < rect.x1 && point.y >= rect.y1 |
938 | } |
939 | TextDirection::BottomToTop => { |
940 | point.x >= rect.x0 && point.x < rect.x1 && point.y < rect.y0 |
941 | } |
942 | }; |
943 | if is_past_end { |
944 | return Position { |
945 | root_node: *self, |
946 | inner: InnerPosition { |
947 | node, |
948 | character_index: node.data().character_lengths().len(), |
949 | }, |
950 | }; |
951 | } |
952 | } |
953 | } |
954 | } |
955 | |
956 | Position { |
957 | root_node: *self, |
958 | inner: self.document_end(), |
959 | } |
960 | } |
961 | |
962 | pub fn line_range_from_index(&self, line_index: usize) -> Option<Range> { |
963 | let mut pos = self.document_range().start(); |
964 | |
965 | if line_index > 0 { |
966 | if pos.is_document_end() || pos.forward_to_line_end().is_document_end() { |
967 | return None; |
968 | } |
969 | for _ in 0..line_index { |
970 | if pos.is_document_end() { |
971 | return None; |
972 | } |
973 | pos = pos.forward_to_line_start(); |
974 | } |
975 | } |
976 | |
977 | let end = if pos.is_document_end() { |
978 | pos |
979 | } else { |
980 | pos.forward_to_line_end() |
981 | }; |
982 | Some(Range::new(*self, pos.inner, end.inner)) |
983 | } |
984 | |
985 | pub fn text_position_from_global_utf16_index(&self, index: usize) -> Option<Position> { |
986 | let mut total_length = 0usize; |
987 | for node in self.inline_text_boxes() { |
988 | let node_text = node.data().value().unwrap(); |
989 | let node_text_length = node_text.chars().map(char::len_utf16).sum::<usize>(); |
990 | let new_total_length = total_length + node_text_length; |
991 | if index >= total_length && index < new_total_length { |
992 | let index = index - total_length; |
993 | let mut utf8_length = 0usize; |
994 | let mut utf16_length = 0usize; |
995 | for (character_index, utf8_char_length) in |
996 | node.data().character_lengths().iter().enumerate() |
997 | { |
998 | let new_utf8_length = utf8_length + (*utf8_char_length as usize); |
999 | let char_str = &node_text[utf8_length..new_utf8_length]; |
1000 | let utf16_char_length = char_str.chars().map(char::len_utf16).sum::<usize>(); |
1001 | let new_utf16_length = utf16_length + utf16_char_length; |
1002 | if index >= utf16_length && index < new_utf16_length { |
1003 | return Some(Position { |
1004 | root_node: *self, |
1005 | inner: InnerPosition { |
1006 | node, |
1007 | character_index, |
1008 | }, |
1009 | }); |
1010 | } |
1011 | utf8_length = new_utf8_length; |
1012 | utf16_length = new_utf16_length; |
1013 | } |
1014 | panic!("index out of range" ); |
1015 | } |
1016 | total_length = new_total_length; |
1017 | } |
1018 | if index == total_length { |
1019 | return Some(Position { |
1020 | root_node: *self, |
1021 | inner: self.document_end(), |
1022 | }); |
1023 | } |
1024 | None |
1025 | } |
1026 | } |
1027 | |
1028 | #[cfg (test)] |
1029 | mod tests { |
1030 | use accesskit::{NodeId, Point, Rect, TextSelection}; |
1031 | |
1032 | // This is based on an actual tree produced by egui. |
1033 | fn main_multiline_tree(selection: Option<TextSelection>) -> crate::Tree { |
1034 | use accesskit::{ |
1035 | Action, Affine, NodeBuilder, NodeClassSet, Role, TextDirection, Tree, TreeUpdate, |
1036 | }; |
1037 | |
1038 | let mut classes = NodeClassSet::new(); |
1039 | let update = TreeUpdate { |
1040 | nodes: vec![ |
1041 | (NodeId(0), { |
1042 | let mut builder = NodeBuilder::new(Role::Window); |
1043 | builder.set_transform(Affine::scale(1.5)); |
1044 | builder.set_children(vec![NodeId(1)]); |
1045 | builder.build(&mut classes) |
1046 | }), |
1047 | (NodeId(1), { |
1048 | let mut builder = NodeBuilder::new(Role::MultilineTextInput); |
1049 | builder.set_bounds(Rect { |
1050 | x0: 8.0, |
1051 | y0: 31.666664123535156, |
1052 | x1: 296.0, |
1053 | y1: 123.66666412353516, |
1054 | }); |
1055 | builder.set_children(vec![ |
1056 | NodeId(2), |
1057 | NodeId(3), |
1058 | NodeId(4), |
1059 | NodeId(5), |
1060 | NodeId(6), |
1061 | NodeId(7), |
1062 | ]); |
1063 | builder.add_action(Action::Focus); |
1064 | if let Some(selection) = selection { |
1065 | builder.set_text_selection(selection); |
1066 | } |
1067 | builder.build(&mut classes) |
1068 | }), |
1069 | (NodeId(2), { |
1070 | let mut builder = NodeBuilder::new(Role::InlineTextBox); |
1071 | builder.set_bounds(Rect { |
1072 | x0: 12.0, |
1073 | y0: 33.666664123535156, |
1074 | x1: 290.9189147949219, |
1075 | y1: 48.33333206176758, |
1076 | }); |
1077 | // The non-breaking space in the following text |
1078 | // is in an arbitrary spot; its only purpose |
1079 | // is to test conversion between UTF-8 and UTF-16 |
1080 | // indices. |
1081 | builder.set_value("This paragraph is \u{a0}long enough to wrap " ); |
1082 | builder.set_text_direction(TextDirection::LeftToRight); |
1083 | builder.set_character_lengths([ |
1084 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, |
1085 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
1086 | ]); |
1087 | builder.set_character_positions([ |
1088 | 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, |
1089 | 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, |
1090 | 117.333336, 124.666664, 132.0, 139.33333, 146.66667, 154.0, 161.33333, |
1091 | 168.66667, 176.0, 183.33333, 190.66667, 198.0, 205.33333, 212.66667, 220.0, |
1092 | 227.33333, 234.66667, 242.0, 249.33333, 256.66666, 264.0, 271.33334, |
1093 | ]); |
1094 | builder.set_character_widths([ |
1095 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1096 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1097 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1098 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1099 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1100 | ]); |
1101 | builder.set_word_lengths([5, 10, 3, 5, 7, 3, 5]); |
1102 | builder.build(&mut classes) |
1103 | }), |
1104 | (NodeId(3), { |
1105 | let mut builder = NodeBuilder::new(Role::InlineTextBox); |
1106 | builder.set_bounds(Rect { |
1107 | x0: 12.0, |
1108 | y0: 48.33333206176758, |
1109 | x1: 129.5855712890625, |
1110 | y1: 63.0, |
1111 | }); |
1112 | builder.set_value("to another line. \n" ); |
1113 | builder.set_text_direction(TextDirection::LeftToRight); |
1114 | builder |
1115 | .set_character_lengths([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); |
1116 | builder.set_character_positions([ |
1117 | 0.0, 7.3333435, 14.666687, 22.0, 29.333344, 36.666687, 44.0, 51.333344, |
1118 | 58.666687, 66.0, 73.33334, 80.66669, 88.0, 95.33334, 102.66669, 110.0, |
1119 | 117.58557, |
1120 | ]); |
1121 | builder.set_character_widths([ |
1122 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1123 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1124 | 0.0, |
1125 | ]); |
1126 | builder.set_word_lengths([3, 8, 6]); |
1127 | builder.build(&mut classes) |
1128 | }), |
1129 | (NodeId(4), { |
1130 | let mut builder = NodeBuilder::new(Role::InlineTextBox); |
1131 | builder.set_bounds(Rect { |
1132 | x0: 12.0, |
1133 | y0: 63.0, |
1134 | x1: 144.25222778320313, |
1135 | y1: 77.66666412353516, |
1136 | }); |
1137 | builder.set_value("Another paragraph. \n" ); |
1138 | builder.set_text_direction(TextDirection::LeftToRight); |
1139 | builder.set_character_lengths([ |
1140 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
1141 | ]); |
1142 | builder.set_character_positions([ |
1143 | 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, |
1144 | 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, |
1145 | 117.333336, 124.666664, 132.25223, |
1146 | ]); |
1147 | builder.set_character_widths([ |
1148 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1149 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1150 | 7.58557, 7.58557, 0.0, |
1151 | ]); |
1152 | builder.set_word_lengths([8, 11]); |
1153 | builder.build(&mut classes) |
1154 | }), |
1155 | (NodeId(5), { |
1156 | let mut builder = NodeBuilder::new(Role::InlineTextBox); |
1157 | builder.set_bounds(Rect { |
1158 | x0: 12.0, |
1159 | y0: 77.66666412353516, |
1160 | x1: 12.0, |
1161 | y1: 92.33332824707031, |
1162 | }); |
1163 | builder.set_value(" \n" ); |
1164 | builder.set_text_direction(TextDirection::LeftToRight); |
1165 | builder.set_character_lengths([1]); |
1166 | builder.set_character_positions([0.0]); |
1167 | builder.set_character_widths([0.0]); |
1168 | builder.set_word_lengths([1]); |
1169 | builder.build(&mut classes) |
1170 | }), |
1171 | (NodeId(6), { |
1172 | let mut builder = NodeBuilder::new(Role::InlineTextBox); |
1173 | builder.set_bounds(Rect { |
1174 | x0: 12.0, |
1175 | y0: 92.33332824707031, |
1176 | x1: 158.9188995361328, |
1177 | y1: 107.0, |
1178 | }); |
1179 | // Use an arbitrary emoji that encodes to two |
1180 | // UTF-16 code units to fully test conversion between |
1181 | // UTF-8, UTF-16, and character indices. |
1182 | builder.set_value("Last non-blank line \u{1f60a}\n" ); |
1183 | builder.set_text_direction(TextDirection::LeftToRight); |
1184 | builder.set_character_lengths([ |
1185 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, |
1186 | ]); |
1187 | builder.set_character_positions([ |
1188 | 0.0, 7.3333335, 14.666667, 22.0, 29.333334, 36.666668, 44.0, 51.333332, |
1189 | 58.666668, 66.0, 73.333336, 80.666664, 88.0, 95.333336, 102.666664, 110.0, |
1190 | 117.333336, 124.666664, 132.0, 139.33333, 146.9189, |
1191 | ]); |
1192 | builder.set_character_widths([ |
1193 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1194 | 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, 7.58557, |
1195 | 7.58557, 7.58557, 7.58557, 7.58557, 0.0, |
1196 | ]); |
1197 | builder.set_word_lengths([5, 4, 6, 6]); |
1198 | builder.build(&mut classes) |
1199 | }), |
1200 | (NodeId(7), { |
1201 | let mut builder = NodeBuilder::new(Role::InlineTextBox); |
1202 | builder.set_bounds(Rect { |
1203 | x0: 12.0, |
1204 | y0: 107.0, |
1205 | x1: 12.0, |
1206 | y1: 121.66666412353516, |
1207 | }); |
1208 | builder.set_value("" ); |
1209 | builder.set_text_direction(TextDirection::LeftToRight); |
1210 | builder.set_character_lengths([]); |
1211 | builder.set_character_positions([]); |
1212 | builder.set_character_widths([]); |
1213 | builder.set_word_lengths([0]); |
1214 | builder.build(&mut classes) |
1215 | }), |
1216 | ], |
1217 | tree: Some(Tree::new(NodeId(0))), |
1218 | focus: NodeId(1), |
1219 | }; |
1220 | |
1221 | crate::Tree::new(update, true) |
1222 | } |
1223 | |
1224 | fn multiline_end_selection() -> TextSelection { |
1225 | use accesskit::TextPosition; |
1226 | |
1227 | TextSelection { |
1228 | anchor: TextPosition { |
1229 | node: NodeId(7), |
1230 | character_index: 0, |
1231 | }, |
1232 | focus: TextPosition { |
1233 | node: NodeId(7), |
1234 | character_index: 0, |
1235 | }, |
1236 | } |
1237 | } |
1238 | |
1239 | fn multiline_wrapped_line_end_selection() -> TextSelection { |
1240 | use accesskit::TextPosition; |
1241 | |
1242 | TextSelection { |
1243 | anchor: TextPosition { |
1244 | node: NodeId(2), |
1245 | character_index: 38, |
1246 | }, |
1247 | focus: TextPosition { |
1248 | node: NodeId(2), |
1249 | character_index: 38, |
1250 | }, |
1251 | } |
1252 | } |
1253 | |
1254 | fn multiline_first_line_middle_selection() -> TextSelection { |
1255 | use accesskit::TextPosition; |
1256 | |
1257 | TextSelection { |
1258 | anchor: TextPosition { |
1259 | node: NodeId(2), |
1260 | character_index: 5, |
1261 | }, |
1262 | focus: TextPosition { |
1263 | node: NodeId(2), |
1264 | character_index: 5, |
1265 | }, |
1266 | } |
1267 | } |
1268 | |
1269 | fn multiline_second_line_middle_selection() -> TextSelection { |
1270 | use accesskit::TextPosition; |
1271 | |
1272 | TextSelection { |
1273 | anchor: TextPosition { |
1274 | node: NodeId(3), |
1275 | character_index: 5, |
1276 | }, |
1277 | focus: TextPosition { |
1278 | node: NodeId(3), |
1279 | character_index: 5, |
1280 | }, |
1281 | } |
1282 | } |
1283 | |
1284 | #[test ] |
1285 | fn supports_text_ranges() { |
1286 | let tree = main_multiline_tree(None); |
1287 | let state = tree.state(); |
1288 | assert!(!state.node_by_id(NodeId(0)).unwrap().supports_text_ranges()); |
1289 | assert!(state.node_by_id(NodeId(1)).unwrap().supports_text_ranges()); |
1290 | } |
1291 | |
1292 | #[test ] |
1293 | fn multiline_document_range() { |
1294 | let tree = main_multiline_tree(None); |
1295 | let state = tree.state(); |
1296 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1297 | let range = node.document_range(); |
1298 | let start = range.start(); |
1299 | assert!(start.is_word_start()); |
1300 | assert!(start.is_line_start()); |
1301 | assert!(!start.is_line_end()); |
1302 | assert!(start.is_paragraph_start()); |
1303 | assert!(start.is_document_start()); |
1304 | assert!(!start.is_document_end()); |
1305 | let end = range.end(); |
1306 | assert!(start < end); |
1307 | assert!(end.is_word_start()); |
1308 | assert!(end.is_line_start()); |
1309 | assert!(end.is_line_end()); |
1310 | assert!(end.is_paragraph_start()); |
1311 | assert!(!end.is_document_start()); |
1312 | assert!(end.is_document_end()); |
1313 | assert_eq!(range.text(), "This paragraph is \u{a0}long enough to wrap to another line. \nAnother paragraph. \n\nLast non-blank line \u{1f60a}\n" ); |
1314 | assert_eq!( |
1315 | range.bounding_boxes(), |
1316 | vec![ |
1317 | Rect { |
1318 | x0: 18.0, |
1319 | y0: 50.499996185302734, |
1320 | x1: 436.3783721923828, |
1321 | y1: 72.49999809265137 |
1322 | }, |
1323 | Rect { |
1324 | x0: 18.0, |
1325 | y0: 72.49999809265137, |
1326 | x1: 194.37835693359375, |
1327 | y1: 94.5 |
1328 | }, |
1329 | Rect { |
1330 | x0: 18.0, |
1331 | y0: 94.5, |
1332 | x1: 216.3783416748047, |
1333 | y1: 116.49999618530273 |
1334 | }, |
1335 | Rect { |
1336 | x0: 18.0, |
1337 | y0: 116.49999618530273, |
1338 | x1: 18.0, |
1339 | y1: 138.49999237060547 |
1340 | }, |
1341 | Rect { |
1342 | x0: 18.0, |
1343 | y0: 138.49999237060547, |
1344 | x1: 238.37834930419922, |
1345 | y1: 160.5 |
1346 | } |
1347 | ] |
1348 | ); |
1349 | } |
1350 | |
1351 | #[test ] |
1352 | fn multiline_end_degenerate_range() { |
1353 | let tree = main_multiline_tree(Some(multiline_end_selection())); |
1354 | let state = tree.state(); |
1355 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1356 | let range = node.text_selection().unwrap(); |
1357 | assert!(range.is_degenerate()); |
1358 | let pos = range.start(); |
1359 | assert!(pos.is_word_start()); |
1360 | assert!(pos.is_line_start()); |
1361 | assert!(pos.is_line_end()); |
1362 | assert!(pos.is_paragraph_start()); |
1363 | assert!(!pos.is_document_start()); |
1364 | assert!(pos.is_document_end()); |
1365 | assert_eq!(range.text(), "" ); |
1366 | assert_eq!( |
1367 | range.bounding_boxes(), |
1368 | vec![Rect { |
1369 | x0: 18.0, |
1370 | y0: 160.5, |
1371 | x1: 18.0, |
1372 | y1: 182.49999618530273, |
1373 | }] |
1374 | ); |
1375 | } |
1376 | |
1377 | #[test ] |
1378 | fn multiline_wrapped_line_end_range() { |
1379 | let tree = main_multiline_tree(Some(multiline_wrapped_line_end_selection())); |
1380 | let state = tree.state(); |
1381 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1382 | let range = node.text_selection().unwrap(); |
1383 | assert!(range.is_degenerate()); |
1384 | let pos = range.start(); |
1385 | assert!(!pos.is_word_start()); |
1386 | assert!(!pos.is_line_start()); |
1387 | assert!(pos.is_line_end()); |
1388 | assert!(!pos.is_paragraph_start()); |
1389 | assert!(!pos.is_document_start()); |
1390 | assert!(!pos.is_document_end()); |
1391 | assert_eq!(range.text(), "" ); |
1392 | assert_eq!( |
1393 | range.bounding_boxes(), |
1394 | vec![Rect { |
1395 | x0: 436.3783721923828, |
1396 | y0: 50.499996185302734, |
1397 | x1: 436.3783721923828, |
1398 | y1: 72.49999809265137 |
1399 | }] |
1400 | ); |
1401 | let char_end_pos = pos.forward_to_character_end(); |
1402 | let mut line_start_range = range; |
1403 | line_start_range.set_end(char_end_pos); |
1404 | assert!(!line_start_range.is_degenerate()); |
1405 | assert!(line_start_range.start().is_line_start()); |
1406 | assert_eq!(line_start_range.text(), "t" ); |
1407 | assert_eq!( |
1408 | line_start_range.bounding_boxes(), |
1409 | vec![Rect { |
1410 | x0: 18.0, |
1411 | y0: 72.49999809265137, |
1412 | x1: 29.378354787826538, |
1413 | y1: 94.5 |
1414 | }] |
1415 | ); |
1416 | let prev_char_pos = pos.backward_to_character_start(); |
1417 | let mut prev_char_range = range; |
1418 | prev_char_range.set_start(prev_char_pos); |
1419 | assert!(!prev_char_range.is_degenerate()); |
1420 | assert!(prev_char_range.end().is_line_end()); |
1421 | assert_eq!(prev_char_range.text(), " " ); |
1422 | assert_eq!( |
1423 | prev_char_range.bounding_boxes(), |
1424 | vec![Rect { |
1425 | x0: 425.00001525878906, |
1426 | y0: 50.499996185302734, |
1427 | x1: 436.3783721923828, |
1428 | y1: 72.49999809265137 |
1429 | }] |
1430 | ); |
1431 | assert!(prev_char_pos.forward_to_character_end().is_line_end()); |
1432 | assert!(prev_char_pos.forward_to_word_end().is_line_end()); |
1433 | assert!(prev_char_pos.forward_to_line_end().is_line_end()); |
1434 | assert!(prev_char_pos.forward_to_character_start().is_line_start()); |
1435 | assert!(prev_char_pos.forward_to_word_start().is_line_start()); |
1436 | assert!(prev_char_pos.forward_to_line_start().is_line_start()); |
1437 | } |
1438 | |
1439 | #[test ] |
1440 | fn multiline_find_line_ends_from_middle() { |
1441 | let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); |
1442 | let state = tree.state(); |
1443 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1444 | let mut range = node.text_selection().unwrap(); |
1445 | assert!(range.is_degenerate()); |
1446 | let pos = range.start(); |
1447 | assert!(!pos.is_line_start()); |
1448 | assert!(!pos.is_line_end()); |
1449 | assert!(!pos.is_document_start()); |
1450 | assert!(!pos.is_document_end()); |
1451 | let line_start = pos.backward_to_line_start(); |
1452 | range.set_start(line_start); |
1453 | let line_end = line_start.forward_to_line_end(); |
1454 | range.set_end(line_end); |
1455 | assert!(!range.is_degenerate()); |
1456 | assert!(range.start().is_line_start()); |
1457 | assert!(range.end().is_line_end()); |
1458 | assert_eq!(range.text(), "to another line. \n" ); |
1459 | assert_eq!( |
1460 | range.bounding_boxes(), |
1461 | vec![Rect { |
1462 | x0: 18.0, |
1463 | y0: 72.49999809265137, |
1464 | x1: 194.37835693359375, |
1465 | y1: 94.5 |
1466 | },] |
1467 | ); |
1468 | assert!(line_start.forward_to_line_start().is_line_start()); |
1469 | } |
1470 | |
1471 | #[test ] |
1472 | fn multiline_find_wrapped_line_ends_from_middle() { |
1473 | let tree = main_multiline_tree(Some(multiline_first_line_middle_selection())); |
1474 | let state = tree.state(); |
1475 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1476 | let mut range = node.text_selection().unwrap(); |
1477 | assert!(range.is_degenerate()); |
1478 | let pos = range.start(); |
1479 | assert!(!pos.is_line_start()); |
1480 | assert!(!pos.is_line_end()); |
1481 | assert!(!pos.is_document_start()); |
1482 | assert!(!pos.is_document_end()); |
1483 | let line_start = pos.backward_to_line_start(); |
1484 | range.set_start(line_start); |
1485 | let line_end = line_start.forward_to_line_end(); |
1486 | range.set_end(line_end); |
1487 | assert!(!range.is_degenerate()); |
1488 | assert!(range.start().is_line_start()); |
1489 | assert!(range.end().is_line_end()); |
1490 | assert_eq!(range.text(), "This paragraph is \u{a0}long enough to wrap " ); |
1491 | assert_eq!( |
1492 | range.bounding_boxes(), |
1493 | vec![Rect { |
1494 | x0: 18.0, |
1495 | y0: 50.499996185302734, |
1496 | x1: 436.3783721923828, |
1497 | y1: 72.49999809265137 |
1498 | }] |
1499 | ); |
1500 | assert!(line_start.forward_to_line_start().is_line_start()); |
1501 | } |
1502 | |
1503 | #[test ] |
1504 | fn multiline_find_paragraph_ends_from_middle() { |
1505 | let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); |
1506 | let state = tree.state(); |
1507 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1508 | let mut range = node.text_selection().unwrap(); |
1509 | assert!(range.is_degenerate()); |
1510 | let pos = range.start(); |
1511 | assert!(!pos.is_paragraph_start()); |
1512 | assert!(!pos.is_document_start()); |
1513 | assert!(!pos.is_document_end()); |
1514 | let paragraph_start = pos.backward_to_paragraph_start(); |
1515 | range.set_start(paragraph_start); |
1516 | let paragraph_end = paragraph_start.forward_to_paragraph_end(); |
1517 | range.set_end(paragraph_end); |
1518 | assert!(!range.is_degenerate()); |
1519 | assert!(range.start().is_paragraph_start()); |
1520 | assert!(range.end().is_paragraph_end()); |
1521 | assert_eq!( |
1522 | range.text(), |
1523 | "This paragraph is \u{a0}long enough to wrap to another line. \n" |
1524 | ); |
1525 | assert_eq!( |
1526 | range.bounding_boxes(), |
1527 | vec![ |
1528 | Rect { |
1529 | x0: 18.0, |
1530 | y0: 50.499996185302734, |
1531 | x1: 436.3783721923828, |
1532 | y1: 72.49999809265137 |
1533 | }, |
1534 | Rect { |
1535 | x0: 18.0, |
1536 | y0: 72.49999809265137, |
1537 | x1: 194.37835693359375, |
1538 | y1: 94.5 |
1539 | }, |
1540 | ] |
1541 | ); |
1542 | assert!(paragraph_start |
1543 | .forward_to_paragraph_start() |
1544 | .is_paragraph_start()); |
1545 | } |
1546 | |
1547 | #[test ] |
1548 | fn multiline_find_word_ends_from_middle() { |
1549 | let tree = main_multiline_tree(Some(multiline_second_line_middle_selection())); |
1550 | let state = tree.state(); |
1551 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1552 | let mut range = node.text_selection().unwrap(); |
1553 | assert!(range.is_degenerate()); |
1554 | let pos = range.start(); |
1555 | assert!(!pos.is_word_start()); |
1556 | assert!(!pos.is_document_start()); |
1557 | assert!(!pos.is_document_end()); |
1558 | let word_start = pos.backward_to_word_start(); |
1559 | range.set_start(word_start); |
1560 | let word_end = word_start.forward_to_word_end(); |
1561 | range.set_end(word_end); |
1562 | assert!(!range.is_degenerate()); |
1563 | assert_eq!(range.text(), "another " ); |
1564 | assert_eq!( |
1565 | range.bounding_boxes(), |
1566 | vec![Rect { |
1567 | x0: 51.0, |
1568 | y0: 72.49999809265137, |
1569 | x1: 139.3783721923828, |
1570 | y1: 94.5 |
1571 | }] |
1572 | ); |
1573 | } |
1574 | |
1575 | #[test ] |
1576 | fn text_position_at_point() { |
1577 | let tree = main_multiline_tree(None); |
1578 | let state = tree.state(); |
1579 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1580 | |
1581 | { |
1582 | let pos = node.text_position_at_point(Point::new(8.0, 31.666664123535156)); |
1583 | assert!(pos.is_document_start()); |
1584 | } |
1585 | |
1586 | { |
1587 | let pos = node.text_position_at_point(Point::new(12.0, 33.666664123535156)); |
1588 | assert!(pos.is_document_start()); |
1589 | } |
1590 | |
1591 | { |
1592 | let pos = node.text_position_at_point(Point::new(16.0, 40.0)); |
1593 | assert!(pos.is_document_start()); |
1594 | } |
1595 | |
1596 | { |
1597 | let pos = node.text_position_at_point(Point::new(144.0, 40.0)); |
1598 | assert!(!pos.is_document_start()); |
1599 | assert!(!pos.is_document_end()); |
1600 | assert!(!pos.is_line_end()); |
1601 | let mut range = pos.to_degenerate_range(); |
1602 | range.set_end(pos.forward_to_character_end()); |
1603 | assert_eq!(range.text(), "l" ); |
1604 | } |
1605 | |
1606 | { |
1607 | let pos = node.text_position_at_point(Point::new(150.0, 40.0)); |
1608 | assert!(!pos.is_document_start()); |
1609 | assert!(!pos.is_document_end()); |
1610 | assert!(!pos.is_line_end()); |
1611 | let mut range = pos.to_degenerate_range(); |
1612 | range.set_end(pos.forward_to_character_end()); |
1613 | assert_eq!(range.text(), "l" ); |
1614 | } |
1615 | |
1616 | { |
1617 | let pos = node.text_position_at_point(Point::new(291.0, 40.0)); |
1618 | assert!(!pos.is_document_start()); |
1619 | assert!(!pos.is_document_end()); |
1620 | assert!(pos.is_line_end()); |
1621 | let mut range = pos.to_degenerate_range(); |
1622 | range.set_start(pos.backward_to_word_start()); |
1623 | assert_eq!(range.text(), "wrap " ); |
1624 | } |
1625 | |
1626 | { |
1627 | let pos = node.text_position_at_point(Point::new(12.0, 50.0)); |
1628 | assert!(!pos.is_document_start()); |
1629 | assert!(pos.is_line_start()); |
1630 | assert!(!pos.is_paragraph_start()); |
1631 | let mut range = pos.to_degenerate_range(); |
1632 | range.set_end(pos.forward_to_word_end()); |
1633 | assert_eq!(range.text(), "to " ); |
1634 | } |
1635 | |
1636 | { |
1637 | let pos = node.text_position_at_point(Point::new(130.0, 50.0)); |
1638 | assert!(!pos.is_document_start()); |
1639 | assert!(!pos.is_document_end()); |
1640 | assert!(pos.is_line_end()); |
1641 | let mut range = pos.to_degenerate_range(); |
1642 | range.set_start(pos.backward_to_word_start()); |
1643 | assert_eq!(range.text(), "line. \n" ); |
1644 | } |
1645 | |
1646 | { |
1647 | let pos = node.text_position_at_point(Point::new(12.0, 80.0)); |
1648 | assert!(!pos.is_document_start()); |
1649 | assert!(!pos.is_document_end()); |
1650 | assert!(pos.is_line_end()); |
1651 | let mut range = pos.to_degenerate_range(); |
1652 | range.set_start(pos.backward_to_line_start()); |
1653 | assert_eq!(range.text(), " \n" ); |
1654 | } |
1655 | |
1656 | { |
1657 | let pos = node.text_position_at_point(Point::new(12.0, 120.0)); |
1658 | assert!(pos.is_document_end()); |
1659 | } |
1660 | |
1661 | { |
1662 | let pos = node.text_position_at_point(Point::new(250.0, 122.0)); |
1663 | assert!(pos.is_document_end()); |
1664 | } |
1665 | } |
1666 | |
1667 | #[test ] |
1668 | fn to_global_utf16_index() { |
1669 | let tree = main_multiline_tree(None); |
1670 | let state = tree.state(); |
1671 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1672 | |
1673 | { |
1674 | let range = node.document_range(); |
1675 | assert_eq!(range.start().to_global_utf16_index(), 0); |
1676 | assert_eq!(range.end().to_global_utf16_index(), 97); |
1677 | } |
1678 | |
1679 | { |
1680 | let range = node.document_range(); |
1681 | let pos = range.start().forward_to_line_end(); |
1682 | assert_eq!(pos.to_global_utf16_index(), 38); |
1683 | let pos = range.start().forward_to_line_start(); |
1684 | assert_eq!(pos.to_global_utf16_index(), 38); |
1685 | let pos = pos.forward_to_character_start(); |
1686 | assert_eq!(pos.to_global_utf16_index(), 39); |
1687 | let pos = pos.forward_to_line_start(); |
1688 | assert_eq!(pos.to_global_utf16_index(), 55); |
1689 | } |
1690 | } |
1691 | |
1692 | #[test ] |
1693 | fn to_line_index() { |
1694 | let tree = main_multiline_tree(None); |
1695 | let state = tree.state(); |
1696 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1697 | |
1698 | { |
1699 | let range = node.document_range(); |
1700 | assert_eq!(range.start().to_line_index(), 0); |
1701 | assert_eq!(range.end().to_line_index(), 5); |
1702 | } |
1703 | |
1704 | { |
1705 | let range = node.document_range(); |
1706 | let pos = range.start().forward_to_line_end(); |
1707 | assert_eq!(pos.to_line_index(), 0); |
1708 | let pos = range.start().forward_to_line_start(); |
1709 | assert_eq!(pos.to_line_index(), 1); |
1710 | let pos = pos.forward_to_character_start(); |
1711 | assert_eq!(pos.to_line_index(), 1); |
1712 | assert_eq!(pos.forward_to_line_end().to_line_index(), 1); |
1713 | let pos = pos.forward_to_line_start(); |
1714 | assert_eq!(pos.to_line_index(), 2); |
1715 | } |
1716 | } |
1717 | |
1718 | #[test ] |
1719 | fn line_range_from_index() { |
1720 | let tree = main_multiline_tree(None); |
1721 | let state = tree.state(); |
1722 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1723 | |
1724 | { |
1725 | let range = node.line_range_from_index(0).unwrap(); |
1726 | assert_eq!(range.text(), "This paragraph is \u{a0}long enough to wrap " ); |
1727 | } |
1728 | |
1729 | { |
1730 | let range = node.line_range_from_index(1).unwrap(); |
1731 | assert_eq!(range.text(), "to another line. \n" ); |
1732 | } |
1733 | |
1734 | { |
1735 | let range = node.line_range_from_index(2).unwrap(); |
1736 | assert_eq!(range.text(), "Another paragraph. \n" ); |
1737 | } |
1738 | |
1739 | { |
1740 | let range = node.line_range_from_index(3).unwrap(); |
1741 | assert_eq!(range.text(), " \n" ); |
1742 | } |
1743 | |
1744 | { |
1745 | let range = node.line_range_from_index(4).unwrap(); |
1746 | assert_eq!(range.text(), "Last non-blank line \u{1f60a}\n" ); |
1747 | } |
1748 | |
1749 | { |
1750 | let range = node.line_range_from_index(5).unwrap(); |
1751 | assert_eq!(range.text(), "" ); |
1752 | } |
1753 | |
1754 | assert!(node.line_range_from_index(6).is_none()); |
1755 | } |
1756 | |
1757 | #[test ] |
1758 | fn text_position_from_global_utf16_index() { |
1759 | let tree = main_multiline_tree(None); |
1760 | let state = tree.state(); |
1761 | let node = state.node_by_id(NodeId(1)).unwrap(); |
1762 | |
1763 | { |
1764 | let pos = node.text_position_from_global_utf16_index(0).unwrap(); |
1765 | assert!(pos.is_document_start()); |
1766 | } |
1767 | |
1768 | { |
1769 | let pos = node.text_position_from_global_utf16_index(17).unwrap(); |
1770 | let mut range = pos.to_degenerate_range(); |
1771 | range.set_end(pos.forward_to_character_end()); |
1772 | assert_eq!(range.text(), " \u{a0}" ); |
1773 | } |
1774 | |
1775 | { |
1776 | let pos = node.text_position_from_global_utf16_index(18).unwrap(); |
1777 | let mut range = pos.to_degenerate_range(); |
1778 | range.set_end(pos.forward_to_character_end()); |
1779 | assert_eq!(range.text(), "l" ); |
1780 | } |
1781 | |
1782 | { |
1783 | let pos = node.text_position_from_global_utf16_index(37).unwrap(); |
1784 | let mut range = pos.to_degenerate_range(); |
1785 | range.set_end(pos.forward_to_character_end()); |
1786 | assert_eq!(range.text(), " " ); |
1787 | } |
1788 | |
1789 | { |
1790 | let pos = node.text_position_from_global_utf16_index(38).unwrap(); |
1791 | assert!(!pos.is_paragraph_start()); |
1792 | assert!(pos.is_line_start()); |
1793 | let mut range = pos.to_degenerate_range(); |
1794 | range.set_end(pos.forward_to_character_end()); |
1795 | assert_eq!(range.text(), "t" ); |
1796 | } |
1797 | |
1798 | { |
1799 | let pos = node.text_position_from_global_utf16_index(54).unwrap(); |
1800 | let mut range = pos.to_degenerate_range(); |
1801 | range.set_end(pos.forward_to_character_end()); |
1802 | assert_eq!(range.text(), " \n" ); |
1803 | } |
1804 | |
1805 | { |
1806 | let pos = node.text_position_from_global_utf16_index(55).unwrap(); |
1807 | assert!(pos.is_paragraph_start()); |
1808 | assert!(pos.is_line_start()); |
1809 | let mut range = pos.to_degenerate_range(); |
1810 | range.set_end(pos.forward_to_character_end()); |
1811 | assert_eq!(range.text(), "A" ); |
1812 | } |
1813 | |
1814 | { |
1815 | let pos = node.text_position_from_global_utf16_index(94).unwrap(); |
1816 | let mut range = pos.to_degenerate_range(); |
1817 | range.set_end(pos.forward_to_character_end()); |
1818 | assert_eq!(range.text(), " \u{1f60a}" ); |
1819 | } |
1820 | |
1821 | { |
1822 | let pos = node.text_position_from_global_utf16_index(95).unwrap(); |
1823 | let mut range = pos.to_degenerate_range(); |
1824 | range.set_end(pos.forward_to_character_end()); |
1825 | assert_eq!(range.text(), " \u{1f60a}" ); |
1826 | } |
1827 | |
1828 | { |
1829 | let pos = node.text_position_from_global_utf16_index(96).unwrap(); |
1830 | let mut range = pos.to_degenerate_range(); |
1831 | range.set_end(pos.forward_to_character_end()); |
1832 | assert_eq!(range.text(), " \n" ); |
1833 | } |
1834 | |
1835 | { |
1836 | let pos = node.text_position_from_global_utf16_index(97).unwrap(); |
1837 | assert!(pos.is_document_end()); |
1838 | } |
1839 | |
1840 | assert!(node.text_position_from_global_utf16_index(98).is_none()); |
1841 | } |
1842 | } |
1843 | |