1use std::cmp::max;
18
19use crate::selection::{HorizPos, SelRegion, Selection};
20use crate::view::View;
21use crate::word_boundaries::WordCursor;
22use xi_rope::{Cursor, LinesMetric, Rope};
23
24#[derive(Debug, PartialEq, Clone, Copy)]
26pub enum Movement {
27 Left,
29 Right,
31 LeftWord,
33 RightWord,
35 LeftOfLine,
37 RightOfLine,
39 Up,
41 Down,
43 UpPage,
45 DownPage,
47 UpExactPosition,
49 DownExactPosition,
51 StartOfParagraph,
53 EndOfParagraph,
55 EndOfParagraphKill,
57 StartOfDocument,
59 EndOfDocument,
61}
62
63fn vertical_motion(
68 r: SelRegion,
69 view: &View,
70 text: &Rope,
71 line_delta: isize,
72 modify: bool,
73) -> (usize, Option<HorizPos>) {
74 let (col, line) = selection_position(r, view, text, line_delta < 0, modify);
75 let n_lines = view.line_of_offset(text, text.len());
76
77 if line_delta < 0 && (-line_delta as usize) > line {
80 return (0, Some(col));
81 }
82 let line = if line_delta < 0 {
83 line - (-line_delta as usize)
84 } else {
85 line.saturating_add(line_delta as usize)
86 };
87 if line > n_lines {
88 return (text.len(), Some(col));
89 }
90 let new_offset = view.line_col_to_offset(text, line, col);
91 (new_offset, Some(col))
92}
93
94fn vertical_motion_exact_pos(
97 r: SelRegion,
98 view: &View,
99 text: &Rope,
100 move_up: bool,
101 modify: bool,
102) -> (usize, Option<HorizPos>) {
103 let (col, init_line) = selection_position(r, view, text, move_up, modify);
104 let n_lines = view.line_of_offset(text, text.len());
105
106 let mut line_length = view.offset_of_line(text, init_line.saturating_add(1))
107 - view.offset_of_line(text, init_line);
108 if move_up && init_line == 0 {
109 return (view.line_col_to_offset(text, init_line, col), Some(col));
110 }
111 let mut line = if move_up { init_line - 1 } else { init_line.saturating_add(1) };
112
113 let col = if line_length < col { line_length - 1 } else { col };
115
116 loop {
117 line_length = view.offset_of_line(text, line + 1) - view.offset_of_line(text, line);
118
119 if line_length > col {
122 break;
123 }
124
125 if line >= n_lines || (line == 0 && move_up) {
127 line = init_line;
128 break;
129 }
130
131 line = if move_up { line - 1 } else { line.saturating_add(1) };
132 }
133
134 (view.line_col_to_offset(text, line, col), Some(col))
135}
136
137fn selection_position(
140 r: SelRegion,
141 view: &View,
142 text: &Rope,
143 move_up: bool,
144 modify: bool,
145) -> (HorizPos, usize) {
146 let active = if modify {
148 r.end
149 } else if move_up {
150 r.min()
151 } else {
152 r.max()
153 };
154 let col = if let Some(col) = r.horiz { col } else { view.offset_to_line_col(text, active).1 };
155 let line = view.line_of_offset(text, active);
156
157 (col, line)
158}
159
160const SCROLL_OVERLAP: isize = 2;
163
164fn scroll_height(view: &View) -> isize {
167 max(view.scroll_height() as isize - SCROLL_OVERLAP, 1)
168}
169
170pub fn region_movement(
172 m: Movement,
173 r: SelRegion,
174 view: &View,
175 text: &Rope,
176 modify: bool,
177) -> SelRegion {
178 let (offset, horiz) = match m {
179 Movement::Left => {
180 if r.is_caret() || modify {
181 if let Some(offset) = text.prev_grapheme_offset(r.end) {
182 (offset, None)
183 } else {
184 (0, r.horiz)
185 }
186 } else {
187 (r.min(), None)
188 }
189 }
190 Movement::Right => {
191 if r.is_caret() || modify {
192 if let Some(offset) = text.next_grapheme_offset(r.end) {
193 (offset, None)
194 } else {
195 (r.end, r.horiz)
196 }
197 } else {
198 (r.max(), None)
199 }
200 }
201 Movement::LeftWord => {
202 let mut word_cursor = WordCursor::new(text, r.end);
203 let offset = word_cursor.prev_boundary().unwrap_or(0);
204 (offset, None)
205 }
206 Movement::RightWord => {
207 let mut word_cursor = WordCursor::new(text, r.end);
208 let offset = word_cursor.next_boundary().unwrap_or_else(|| text.len());
209 (offset, None)
210 }
211 Movement::LeftOfLine => {
212 let line = view.line_of_offset(text, r.end);
213 let offset = view.offset_of_line(text, line);
214 (offset, None)
215 }
216 Movement::RightOfLine => {
217 let line = view.line_of_offset(text, r.end);
218 let mut offset = text.len();
219
220 let next_line_offset = view.offset_of_line(text, line + 1);
222 if line < view.line_of_offset(text, offset) {
223 if let Some(prev) = text.prev_grapheme_offset(next_line_offset) {
224 offset = prev;
225 }
226 }
227 (offset, None)
228 }
229 Movement::Up => vertical_motion(r, view, text, -1, modify),
230 Movement::Down => vertical_motion(r, view, text, 1, modify),
231 Movement::UpExactPosition => vertical_motion_exact_pos(r, view, text, true, modify),
232 Movement::DownExactPosition => vertical_motion_exact_pos(r, view, text, false, modify),
233 Movement::StartOfParagraph => {
234 let mut cursor = Cursor::new(&text, r.end);
236 let offset = cursor.prev::<LinesMetric>().unwrap_or(0);
237 (offset, None)
238 }
239 Movement::EndOfParagraph => {
240 let mut offset = r.end;
242 let mut cursor = Cursor::new(&text, offset);
243 if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
244 if cursor.is_boundary::<LinesMetric>() {
245 if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
246 offset = eol;
247 }
248 } else if cursor.pos() == text.len() {
249 offset = text.len();
250 }
251 (offset, None)
252 } else {
253 (text.len(), None)
255 }
256 }
257 Movement::EndOfParagraphKill => {
258 let mut offset = r.end;
260 let mut cursor = Cursor::new(&text, offset);
261 if let Some(next_para_offset) = cursor.next::<LinesMetric>() {
262 offset = next_para_offset;
263 if cursor.is_boundary::<LinesMetric>() {
264 if let Some(eol) = text.prev_grapheme_offset(next_para_offset) {
265 if eol != r.end {
266 offset = eol;
267 }
268 }
269 }
270 }
271 (offset, None)
272 }
273 Movement::UpPage => vertical_motion(r, view, text, -scroll_height(view), modify),
274 Movement::DownPage => vertical_motion(r, view, text, scroll_height(view), modify),
275 Movement::StartOfDocument => (0, None),
276 Movement::EndOfDocument => (text.len(), None),
277 };
278 SelRegion::new(if modify { r.start } else { offset }, offset).with_horiz(horiz)
279}
280
281pub fn selection_movement(
289 m: Movement,
290 s: &Selection,
291 view: &View,
292 text: &Rope,
293 modify: bool,
294) -> Selection {
295 let mut result = Selection::new();
296 for &r in s.iter() {
297 let new_region = region_movement(m, r, view, text, modify);
298 result.add_region(new_region);
299 }
300 result
301}