1use crate::buffer::RopeBuffer;
2use crate::utils::visual_width;
3use crate::view::View;
4
5#[derive(Debug, Clone, Copy)]
6pub struct Cursor {
7 pub row: usize, pub col: usize, pub visual_line_index: usize, pub desired_visual_col: usize, }
12
13impl Cursor {
14 pub fn new() -> Self {
15 Self {
16 row: 0,
17 col: 0,
18 visual_line_index: 0,
19 desired_visual_col: 0,
20 }
21 }
22
23 pub fn move_up(&mut self, buffer: &RopeBuffer, view: &View) {
24 if view.wrap_mode && self.visual_line_index > 0 {
25 self.visual_line_index -= 1;
27 self.update_logical_col_from_visual(buffer, view);
28 } else if self.row > 0 {
29 self.row -= 1;
31 if view.wrap_mode {
32 let visual_lines = view.calculate_visual_lines_for_row(buffer, self.row);
33 self.visual_line_index = visual_lines.len().saturating_sub(1);
34 } else {
35 self.visual_line_index = 0; }
37 self.update_logical_col_from_visual(buffer, view);
38 }
39 }
40
41 pub fn move_down(&mut self, buffer: &RopeBuffer, view: &View) {
42 if view.wrap_mode {
43 let visual_lines = view.calculate_visual_lines_for_row(buffer, self.row);
44 if self.visual_line_index + 1 < visual_lines.len() {
45 self.visual_line_index += 1;
46 self.update_logical_col_from_visual(buffer, view);
47 return;
48 }
49 }
50
51 if self.row + 1 < buffer.line_count() {
53 self.row += 1;
54 self.visual_line_index = 0;
55 self.update_logical_col_from_visual(buffer, view);
56 }
57 }
58
59 pub fn move_left(&mut self, buffer: &RopeBuffer, view: &View) {
60 if self.col > 0 {
61 self.col -= 1;
62 self.update_visual_from_logical(buffer, view);
63 } else if self.row > 0 {
64 self.row -= 1;
66 self.col = self.line_len(buffer, self.row);
67 self.update_visual_from_logical(buffer, view);
68 }
69 self.sync_desired_visual_col(buffer, view);
70 }
71
72 pub fn move_right(&mut self, buffer: &RopeBuffer, view: &View) {
73 let line_len = self.line_len(buffer, self.row);
74 if self.col < line_len {
75 self.col += 1;
76 self.update_visual_from_logical(buffer, view);
77 } else if self.row + 1 < buffer.line_count() {
78 self.row += 1;
80 self.col = 0;
81 self.visual_line_index = 0;
82 self.desired_visual_col = 0;
83 }
84 self.sync_desired_visual_col(buffer, view);
85 }
86
87 pub fn move_to_line_start(&mut self) {
88 self.col = 0;
89 self.visual_line_index = 0;
90 self.desired_visual_col = 0;
91 }
92
93 pub fn move_to_line_end(&mut self, buffer: &RopeBuffer, view: &View) {
94 self.col = self.line_len(buffer, self.row);
95 self.update_visual_from_logical(buffer, view);
96 self.sync_desired_visual_col(buffer, view);
97 }
98
99 pub fn move_to_file_start(&mut self, _view: &View) {
101 self.row = 0;
103 self.col = 0;
104 self.visual_line_index = 0;
105 self.desired_visual_col = 0;
106 }
107
108 pub fn move_to_file_end(&mut self, buffer: &RopeBuffer, view: &View) {
110 if buffer.line_count() > 0 {
111 self.row = buffer.line_count() - 1;
112 self.move_to_line_end(buffer, view);
114 }
115 }
116
117 pub fn move_page_up(&mut self, buffer: &RopeBuffer, view: &View, effective_rows: usize) {
118 let mut target_row = self.row;
119 let mut visual_count = 0;
120
121 while target_row > 0 && visual_count < effective_rows {
123 target_row -= 1;
124 let vlines = view.calculate_visual_lines_for_row(buffer, target_row);
125 visual_count += vlines.len();
126 }
127
128 self.row = target_row;
129 self.visual_line_index = 0;
130 self.update_logical_col_from_visual(buffer, view);
131 }
132
133 pub fn move_page_down(&mut self, buffer: &RopeBuffer, view: &View, effective_rows: usize) {
134 let max_row = buffer.line_count().saturating_sub(1);
135 let mut target_row = self.row;
136 let mut visual_count = 0;
137
138 while target_row < max_row && visual_count < effective_rows {
140 let vlines = view.calculate_visual_lines_for_row(buffer, target_row);
141 visual_count += vlines.len();
142 target_row += 1;
143 }
144
145 self.row = target_row.min(max_row);
146 self.visual_line_index = 0;
147 self.update_logical_col_from_visual(buffer, view);
148 }
149
150 #[allow(dead_code)]
151 pub fn move_to_line(&mut self, buffer: &RopeBuffer, view: &View, line: usize) {
152 self.row = line.min(buffer.line_count().saturating_sub(1));
153 self.visual_line_index = 0;
154 self.update_logical_col_from_visual(buffer, view);
155 }
156
157 pub fn char_position(&self, buffer: &RopeBuffer) -> usize {
159 buffer.line_to_char(self.row) + self.col
160 }
161
162 pub fn set_position(&mut self, buffer: &RopeBuffer, view: &View, row: usize, col: usize) {
165 self.row = row;
166 self.col = col;
167 self.update_visual_from_logical(buffer, view);
168 self.sync_desired_visual_col(buffer, view);
169 }
170
171 pub fn reset_to_line_start(&mut self) {
173 self.col = 0;
174 self.visual_line_index = 0;
175 self.desired_visual_col = 0;
176 }
177
178 fn update_logical_col_from_visual(&mut self, buffer: &RopeBuffer, view: &View) {
180 let visual_col = self.desired_visual_col;
181 self.col = view.visual_to_logical_col(buffer, self.row, self.visual_line_index, visual_col);
182
183 let line_len = self.line_len(buffer, self.row);
185 self.col = self.col.min(line_len);
186 }
187
188 fn update_visual_from_logical(&mut self, buffer: &RopeBuffer, view: &View) {
190 let visual_lines = view.calculate_visual_lines_for_row(buffer, self.row);
191
192 if let Some(line) = buffer.line(self.row) {
193 let line_str = line.to_string();
194 let visual_col = view.logical_col_to_visual_col(&line_str, self.col);
195
196 let mut accumulated = 0;
198 for (idx, vline) in visual_lines.iter().enumerate() {
199 let vline_len = visual_width(vline);
200 if visual_col < accumulated + vline_len || idx == visual_lines.len() - 1 {
201 self.visual_line_index = idx;
202 break;
203 }
204 accumulated += vline_len;
205 }
206 } else {
207 self.visual_line_index = 0;
208 }
209 }
210
211 fn sync_desired_visual_col(&mut self, buffer: &RopeBuffer, view: &View) {
213 if let Some(line) = buffer.line(self.row) {
214 let line_str = line.to_string();
215 let visual_col = view.logical_col_to_visual_col(&line_str, self.col);
216
217 let visual_lines = view.calculate_visual_lines_for_row(buffer, self.row);
219 let mut accumulated = 0;
220 for i in 0..self.visual_line_index {
221 if i < visual_lines.len() {
222 accumulated += visual_width(&visual_lines[i]);
223 }
224 }
225
226 self.desired_visual_col = visual_col - accumulated;
227 }
228 }
229
230 fn line_len(&self, buffer: &RopeBuffer, row: usize) -> usize {
232 if let Some(line) = buffer.line(row) {
233 let text = line.to_string();
234 let text = text.trim_end_matches(['\n', '\r']);
235 text.chars().count()
236 } else {
237 0
238 }
239 }
240}
241
242impl Default for Cursor {
243 fn default() -> Self {
244 Self::new()
245 }
246}