1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/// Selection helpers for TextAreaState.
///
/// These are implementation details extracted to keep
/// the main module under the 1000-line limit.
use super::TextAreaState;
impl TextAreaState {
/// Returns true if there is an active text selection.
///
/// # Example
///
/// ```rust
/// use envision::component::{TextArea, TextAreaMessage, TextAreaState, Component};
///
/// let mut state = TextAreaState::new().with_value("hello");
/// assert!(!state.has_selection());
///
/// TextArea::update(&mut state, TextAreaMessage::SelectAll);
/// assert!(state.has_selection());
/// ```
pub fn has_selection(&self) -> bool {
match self.selection_anchor {
Some((r, c)) => r != self.cursor_row || c != self.cursor_col,
None => false,
}
}
/// Returns the ordered selection positions as `((start_row, start_col), (end_row, end_col))`.
///
/// # Example
///
/// ```rust
/// use envision::component::{TextArea, TextAreaMessage, TextAreaState, Component};
///
/// let mut state = TextAreaState::new().with_value("hello");
/// TextArea::update(&mut state, TextAreaMessage::SelectAll);
/// let positions = state.selection_positions();
/// assert_eq!(positions, Some(((0, 0), (0, 5))));
/// ```
pub fn selection_positions(&self) -> Option<((usize, usize), (usize, usize))> {
let (ar, ac) = self.selection_anchor?;
if ar == self.cursor_row && ac == self.cursor_col {
return None;
}
let a = (ar, ac);
let b = (self.cursor_row, self.cursor_col);
if a < b { Some((a, b)) } else { Some((b, a)) }
}
/// Returns the selected text, or None if no selection.
///
/// # Example
///
/// ```rust
/// use envision::component::{TextArea, TextAreaMessage, TextAreaState, Component};
///
/// let mut state = TextAreaState::new().with_value("hello");
/// assert_eq!(state.selected_text(), None);
///
/// TextArea::update(&mut state, TextAreaMessage::SelectAll);
/// assert_eq!(state.selected_text(), Some("hello".to_string()));
/// ```
pub fn selected_text(&self) -> Option<String> {
let ((sr, sc), (er, ec)) = self.selection_positions()?;
if sr == er {
Some(self.lines[sr][sc..ec].to_string())
} else {
let mut result = self.lines[sr][sc..].to_string();
for row in (sr + 1)..er {
result.push('\n');
result.push_str(&self.lines[row]);
}
result.push('\n');
result.push_str(&self.lines[er][..ec]);
Some(result)
}
}
/// Returns the internal clipboard contents.
///
/// # Example
///
/// ```rust
/// use envision::component::{TextArea, TextAreaMessage, TextAreaState, Component};
///
/// let mut state = TextAreaState::new().with_value("hello");
/// TextArea::update(&mut state, TextAreaMessage::SelectAll);
/// TextArea::update(&mut state, TextAreaMessage::Copy);
/// assert_eq!(state.clipboard(), "hello");
/// ```
pub fn clipboard(&self) -> &str {
&self.clipboard
}
pub(super) fn clear_selection(&mut self) {
self.selection_anchor = None;
}
pub(super) fn ensure_selection_anchor(&mut self) {
if self.selection_anchor.is_none() {
self.selection_anchor = Some((self.cursor_row, self.cursor_col));
}
}
/// Deletes selected text. Returns deleted text or None.
pub(super) fn delete_selection(&mut self) -> Option<String> {
let ((sr, sc), (er, ec)) = self.selection_positions()?;
let deleted = self.selected_text()?;
if sr == er {
self.lines[sr].drain(sc..ec);
} else {
let after = self.lines[er][ec..].to_string();
self.lines[sr].truncate(sc);
self.lines[sr].push_str(&after);
self.lines.drain((sr + 1)..=er);
}
self.cursor_row = sr;
self.cursor_col = sc;
self.selection_anchor = None;
Some(deleted)
}
}