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