1pub mod cursor;
2pub mod line;
3pub mod viewport;
4
5use crate::selection::Selection;
6use cursor::Cursor;
7use line::Line;
8use viewport::Viewport;
9
10pub struct Buffer {
12 pub lines: Vec<Line>,
13 pub viewport: Viewport,
14 pub cursor: Cursor,
15 pub selection: Option<Selection>,
16 preferred_col: usize,
17}
18
19impl Buffer {
20 pub fn new(lines: Vec<Line>) -> Self {
22 Self {
23 lines,
24 viewport: Viewport::default(),
25 cursor: Cursor::default(),
26 selection: None,
27 preferred_col: 0,
28 }
29 }
30
31 pub fn line_count(&self) -> usize {
33 self.lines.len()
34 }
35
36 pub fn get_line(&self, index: usize) -> Option<&Line> {
38 self.lines.get(index)
39 }
40
41 pub fn current_line(&self) -> Option<&Line> {
42 self.get_line(self.cursor.row)
43 }
44
45 pub fn visible_lines(&self) -> &[Line] {
47 let start = self.viewport.offset;
48 let end = (start + self.viewport.height).min(self.lines.len());
49 &self.lines[start..end]
50 }
51
52 pub fn scroll_down(&mut self, n: usize) {
54 let max_offset = self.lines.len().saturating_sub(self.viewport.height);
55 self.viewport.offset = (self.viewport.offset + n).min(max_offset);
56 }
57
58 pub fn scroll_up(&mut self, n: usize) {
60 self.viewport.offset = self.viewport.offset.saturating_sub(n);
61 }
62
63 pub fn cursor_down(&mut self, n: usize) {
65 let max_row = self.lines.len().saturating_sub(1);
66 self.cursor.row = (self.cursor.row + n).min(max_row);
67 self.clamp_cursor_col();
68 self.ensure_cursor_visible();
69 }
70
71 pub fn cursor_up(&mut self, n: usize) {
73 self.cursor.row = self.cursor.row.saturating_sub(n);
74 self.clamp_cursor_col();
75 self.ensure_cursor_visible();
76 }
77
78 pub fn cursor_right(&mut self, n: usize) {
80 if let Some(line) = self.lines.get(self.cursor.row) {
81 let max_col = line.display_width().saturating_sub(1);
82 self.cursor.col = (self.cursor.col + n).min(max_col);
83 self.preferred_col = self.cursor.col;
84 }
85 }
86
87 pub fn cursor_left(&mut self, n: usize) {
89 self.cursor.col = self.cursor.col.saturating_sub(n);
90 self.preferred_col = self.cursor.col;
91 }
92
93 pub fn cursor_top(&mut self) {
95 self.cursor.row = 0;
96 self.cursor.col = 0;
97 self.preferred_col = 0;
98 self.ensure_cursor_visible();
99 }
100
101 pub fn cursor_bottom(&mut self) {
103 self.cursor.row = self.lines.len().saturating_sub(1);
104 self.clamp_cursor_col();
105 self.ensure_cursor_visible();
106 }
107
108 pub fn half_page_down(&mut self) {
110 let half = self.viewport.height / 2;
111 self.cursor_down(half);
112 }
113
114 pub fn half_page_up(&mut self) {
116 let half = self.viewport.height / 2;
117 self.cursor_up(half);
118 }
119
120 pub fn page_down(&mut self) {
121 let height = self.viewport.height.saturating_sub(1).max(1);
122 self.cursor_down(height);
123 }
124
125 pub fn page_up(&mut self) {
126 let height = self.viewport.height.saturating_sub(1).max(1);
127 self.cursor_up(height);
128 }
129
130 pub fn cursor_line_start(&mut self) {
131 self.cursor.col = 0;
132 self.preferred_col = 0;
133 }
134
135 pub fn cursor_line_end(&mut self) {
136 self.cursor.col = self.max_col_for_row(self.cursor.row);
137 self.preferred_col = self.cursor.col;
138 }
139
140 pub fn cursor_first_nonblank(&mut self) {
141 self.cursor.col = self.first_nonblank_col(self.cursor.row);
142 self.preferred_col = self.cursor.col;
143 }
144
145 pub fn cursor_view_top(&mut self) {
146 self.cursor.row = self.viewport.offset.min(self.lines.len().saturating_sub(1));
147 self.clamp_cursor_col();
148 }
149
150 pub fn cursor_view_middle(&mut self) {
151 let visible_height = self.viewport.height.max(1);
152 let row = self.viewport.offset + visible_height / 2;
153 self.cursor.row = row.min(self.lines.len().saturating_sub(1));
154 self.clamp_cursor_col();
155 }
156
157 pub fn cursor_view_bottom(&mut self) {
158 let visible_height = self.viewport.height.max(1);
159 let row = self.viewport.offset + visible_height.saturating_sub(1);
160 self.cursor.row = row.min(self.lines.len().saturating_sub(1));
161 self.clamp_cursor_col();
162 }
163
164 pub fn set_cursor(&mut self, row: usize, col: usize) {
165 self.cursor.row = row.min(self.lines.len().saturating_sub(1));
166 self.cursor.col = self.clamp_col_for_row(self.cursor.row, col);
167 self.preferred_col = col;
168 self.ensure_cursor_visible();
169 }
170
171 fn ensure_cursor_visible(&mut self) {
173 if self.cursor.row < self.viewport.offset {
174 self.viewport.offset = self.cursor.row;
175 } else if self.cursor.row >= self.viewport.offset + self.viewport.height {
176 self.viewport.offset = self.cursor.row - self.viewport.height + 1;
177 }
178 }
179
180 pub fn resize(&mut self, width: usize, height: usize) {
182 self.viewport.width = width;
183 self.viewport.height = height;
184 }
185
186 pub fn ensure_cursor_visible_pub(&mut self) {
188 self.ensure_cursor_visible();
189 }
190
191 pub fn preferred_col(&self) -> usize {
192 self.preferred_col
193 }
194
195 fn clamp_cursor_col(&mut self) {
196 self.cursor.col = self.clamp_col_for_row(self.cursor.row, self.preferred_col);
197 }
198
199 fn clamp_col_for_row(&self, row: usize, col: usize) -> usize {
200 col.min(self.max_col_for_row(row))
201 }
202
203 fn max_col_for_row(&self, row: usize) -> usize {
204 self.lines
205 .get(row)
206 .map(|line| line.display_width().saturating_sub(1))
207 .unwrap_or(0)
208 }
209
210 fn first_nonblank_col(&self, row: usize) -> usize {
211 self.lines
212 .get(row)
213 .map(|line| {
214 let char_idx = line
215 .content
216 .chars()
217 .position(|ch| !ch.is_whitespace())
218 .unwrap_or_else(|| line.char_count());
219 line.display_col_for_char_index(char_idx)
220 })
221 .unwrap_or(0)
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 fn make_buffer(lines: &[&str]) -> Buffer {
230 let lines = lines
231 .iter()
232 .map(|content| Line {
233 content: (*content).to_string(),
234 display_widths: crate::parse::unicode::compute_display_widths(content),
235 styles: Vec::new(),
236 })
237 .collect();
238 Buffer::new(lines)
239 }
240
241 #[test]
242 fn vertical_motion_preserves_preferred_col() {
243 let mut buffer = make_buffer(&["abcd", "", "wxyz"]);
244 buffer.set_cursor(0, 3);
245 assert_eq!(buffer.cursor.col, 3);
246 assert_eq!(buffer.preferred_col(), 3);
247
248 buffer.cursor_down(1);
249 assert_eq!(buffer.cursor.row, 1);
250 assert_eq!(buffer.cursor.col, 0);
251 assert_eq!(buffer.preferred_col(), 3);
252
253 buffer.cursor_down(1);
254 assert_eq!(buffer.cursor.row, 2);
255 assert_eq!(buffer.cursor.col, 3);
256 assert_eq!(buffer.preferred_col(), 3);
257 }
258}