Skip to main content

wedi_core/
cursor.rs

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,                // 邏輯行號 (0-based)
8    pub col: usize,                // 邏輯列號 (0-based)
9    pub visual_line_index: usize,  // 在當前邏輯行的第幾個視覺行 (0-based)
10    pub desired_visual_col: usize, // 期望的視覺列位置(用於上下移動)
11}
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            // 多行模式:在視覺行間移動
26            self.visual_line_index -= 1;
27            self.update_logical_col_from_visual(buffer, view);
28        } else if self.row > 0 {
29            // 單行模式或已在第一個視覺行:移動到上一邏輯行
30            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; // 單行模式永遠 = 0
36            }
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        // 移動到下一邏輯行
52        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            // 移動到上一行末尾
65            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            // 移動到下一行開頭
79            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    /// 移動到文件開頭
100    pub fn move_to_file_start(&mut self, _view: &View) {
101        // 設置到第一行行首,視覺狀態使用預設值
102        self.row = 0;
103        self.col = 0;
104        self.visual_line_index = 0;
105        self.desired_visual_col = 0;
106    }
107
108    /// 移動到文件末尾
109    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            // 移動到最後一行行尾,並同步視覺狀態
113            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        // 向上累積視覺行直到達到約一個螢幕
122        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        // 向下累積視覺行直到達到約一個螢幕
139        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    /// 獲取光標在文本中的絕對字符位置
158    pub fn char_position(&self, buffer: &RopeBuffer) -> usize {
159        buffer.line_to_char(self.row) + self.col
160    }
161
162    /// 設置光標位置並同步視覺狀態
163    /// 這是統一的光標位置設置方法,確保邏輯和視覺狀態一致
164    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    /// 重置到行首(用於換行等操作)
172    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    /// 從視覺座標更新邏輯列位置
179    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        // 確保不超出行長度
184        let line_len = self.line_len(buffer, self.row);
185        self.col = self.col.min(line_len);
186    }
187
188    /// 從邏輯座標更新視覺座標
189    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            // 找出光標在哪個視覺行
197            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    /// 同步期望視覺列位置
212    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            // 計算在當前視覺行內的列位置
218            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    /// 獲取指定行的長度(不包含換行符)
231    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}