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
6use accesskit::{
7 NodeId, Point, Rect, Role, TextDirection, TextPosition as WeakPosition, TextSelection,
8};
9use std::{cmp::Ordering, iter::FusedIterator};
10
11use crate::{FilterResult, Node, TreeState};
12
13#[derive(Clone, Copy)]
14pub(crate) struct InnerPosition<'a> {
15 pub(crate) node: Node<'a>,
16 pub(crate) character_index: usize,
17}
18
19impl<'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
175impl<'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
181impl<'a> Eq for InnerPosition<'a> {}
182
183#[derive(Clone, Copy)]
184pub struct Position<'a> {
185 root_node: Node<'a>,
186 pub(crate) inner: InnerPosition<'a>,
187}
188
189impl<'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
435impl<'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
441impl<'a> Eq for Position<'a> {}
442
443impl<'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
454pub enum AttributeValue<T> {
455 Single(T),
456 Mixed,
457}
458
459#[derive(Clone, Copy)]
460pub struct Range<'a> {
461 pub(crate) node: Node<'a>,
462 pub(crate) start: InnerPosition<'a>,
463 pub(crate) end: InnerPosition<'a>,
464}
465
466impl<'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
722impl<'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
728impl<'a> Eq for Range<'a> {}
729
730#[derive(Clone, Debug, PartialEq, Eq)]
731pub 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
739impl 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
766fn 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
774fn 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
814impl<'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)]
1029mod 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