redox_core/buffer/text_buffer/
selection.rs1use crate::buffer::{Pos, Selection, TextBuffer};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum VisualModeKind {
9 Char,
10 Line,
11 Block,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct VisualSelectionEditPlan {
20 pub delete_ranges: Vec<(Pos, Pos)>,
21 pub text: String,
22 pub mode: VisualModeKind,
23}
24
25impl TextBuffer {
26 pub fn line_span_char_range(
30 &self,
31 start_line: usize,
32 end_line_inclusive: usize,
33 ) -> std::ops::Range<usize> {
34 let start_line = self.clamp_line(start_line);
35 let end_line = self.clamp_line(end_line_inclusive.max(start_line));
36 let start = self.line_to_char(start_line);
37 let end = self.line_full_end_char(end_line);
38 start..end
39 }
40
41 pub fn line_span_pos_range(&self, start_line: usize, end_line_inclusive: usize) -> (Pos, Pos) {
43 let range = self.line_span_char_range(start_line, end_line_inclusive);
44 (self.char_to_pos(range.start), self.char_to_pos(range.end))
45 }
46
47 pub fn line_span_text(&self, start_line: usize, end_line_inclusive: usize) -> String {
51 let range = self.line_span_char_range(start_line, end_line_inclusive);
52 self.slice_chars(range.start, range.end)
53 }
54
55 pub fn line_span_text_linewise_register(
59 &self,
60 start_line: usize,
61 end_line_inclusive: usize,
62 ) -> String {
63 let mut text = self.line_span_text(start_line, end_line_inclusive);
64 if !text.ends_with('\n') {
65 text.push('\n');
66 }
67 text
68 }
69
70 pub fn visual_charwise_pos_range(&self, selection: Selection) -> (Pos, Pos) {
74 let (start, end_inclusive) = selection.ordered();
75 let end_char = self.pos_to_char(end_inclusive);
76 let end_exclusive = if end_char < self.len_chars() {
77 self.char_to_pos(end_char + 1)
78 } else {
79 end_inclusive
80 };
81 (start, end_exclusive)
82 }
83
84 pub fn visual_linewise_pos_range(&self, selection: Selection) -> (Pos, Pos) {
86 let (start, end_inclusive) = selection.ordered();
87 self.line_span_pos_range(start.line, end_inclusive.line)
88 }
89
90 pub fn visual_charwise_text(&self, selection: Selection) -> String {
92 let (start, end_exclusive) = self.visual_charwise_pos_range(selection);
93 self.slice_pos_range(start, end_exclusive)
94 }
95
96 pub fn visual_linewise_text(&self, selection: Selection) -> String {
98 let (start, end) = selection.ordered();
99 self.line_span_text_linewise_register(start.line, end.line)
100 }
101
102 pub fn visual_blockwise_pos_ranges(&self, selection: Selection) -> Vec<(Pos, Pos)> {
104 let (start, end) = selection.ordered();
105 let left = start.col.min(end.col);
106 let right_exclusive = start.col.max(end.col).saturating_add(1);
107 let mut ranges = Vec::new();
108
109 for line_idx in start.line..=end.line {
110 let line_len = self.line_len_chars(line_idx);
111 let range_start = left.min(line_len);
112 let range_end = right_exclusive.min(line_len);
113 if range_start < range_end {
114 ranges.push((
115 Pos::new(line_idx, range_start),
116 Pos::new(line_idx, range_end),
117 ));
118 }
119 }
120
121 ranges
122 }
123
124 pub fn visual_blockwise_delete_ranges(&self, selection: Selection) -> Vec<(Pos, Pos)> {
129 let (start, end) = selection.ordered();
130 let left = start.col.min(end.col);
131 let right_exclusive = start.col.max(end.col).saturating_add(1);
132 let mut ranges = Vec::new();
133
134 for line_idx in start.line..=end.line {
135 let line_len = self.line_len_chars(line_idx);
136 if left == 0 && right_exclusive >= line_len {
137 let full_line = self.line_span_pos_range(line_idx, line_idx);
138 if full_line.0 != full_line.1 {
139 ranges.push(full_line);
140 }
141 continue;
142 }
143
144 let range_start = left.min(line_len);
145 let range_end = right_exclusive.min(line_len);
146 if range_start < range_end {
147 ranges.push((
148 Pos::new(line_idx, range_start),
149 Pos::new(line_idx, range_end),
150 ));
151 }
152 }
153
154 ranges
155 }
156
157 pub fn visual_blockwise_text(&self, selection: Selection) -> String {
159 self.visual_blockwise_pos_ranges(selection)
160 .into_iter()
161 .map(|(start, end)| self.slice_pos_range(start, end))
162 .collect::<Vec<_>>()
163 .join("\n")
164 }
165
166 pub fn visual_selection_pos_ranges(
168 &self,
169 selection: Selection,
170 mode: VisualModeKind,
171 ) -> Vec<(Pos, Pos)> {
172 match mode {
173 VisualModeKind::Char => vec![self.visual_charwise_pos_range(selection)],
174 VisualModeKind::Line => vec![self.visual_linewise_pos_range(selection)],
175 VisualModeKind::Block => self.visual_blockwise_delete_ranges(selection),
176 }
177 }
178
179 pub fn visual_selection_text(&self, selection: Selection, mode: VisualModeKind) -> String {
181 match mode {
182 VisualModeKind::Char => self.visual_charwise_text(selection),
183 VisualModeKind::Line => self.visual_linewise_text(selection),
184 VisualModeKind::Block => self.visual_blockwise_text(selection),
185 }
186 }
187
188 pub fn visual_selection_edit_plan(
190 &self,
191 selection: Selection,
192 mode: VisualModeKind,
193 ) -> VisualSelectionEditPlan {
194 let delete_ranges = self.visual_selection_pos_ranges(selection, mode);
195 let text = self.visual_selection_text(selection, mode);
196 VisualSelectionEditPlan {
197 delete_ranges,
198 text,
199 mode,
200 }
201 }
202
203 pub fn visual_selection_char_range_on_line(
208 &self,
209 selection: Selection,
210 mode: VisualModeKind,
211 line_idx: usize,
212 ) -> Option<std::ops::Range<usize>> {
213 let line_len = self.line_len_chars(line_idx);
214 match mode {
215 VisualModeKind::Line => {
216 let (start_line, end_line) = selection.line_range();
217 if line_idx < start_line || line_idx > end_line {
218 return None;
219 }
220 Some(0..line_len)
221 }
222 VisualModeKind::Block => {
223 let (start, end) = selection.ordered();
224 if line_idx < start.line || line_idx > end.line {
225 return None;
226 }
227 let left = start.col.min(end.col).min(line_len);
228 let right = start.col.max(end.col).saturating_add(1).min(line_len);
229 if left < right {
230 Some(left..right)
231 } else {
232 None
233 }
234 }
235 VisualModeKind::Char => {
236 let (start, end) = selection.ordered();
237 if line_idx < start.line || line_idx > end.line {
238 return None;
239 }
240 if line_len == 0 {
241 return None;
242 }
243
244 let max_char = line_len.saturating_sub(1);
245 let sel_start = if line_idx == start.line {
246 start.col.min(max_char)
247 } else {
248 0
249 };
250 let sel_end_inclusive = if line_idx == end.line {
251 end.col.min(max_char)
252 } else {
253 max_char
254 };
255 if sel_start > sel_end_inclusive {
256 return None;
257 }
258
259 Some(sel_start..sel_end_inclusive.saturating_add(1))
260 }
261 }
262 }
263}