ratatui_code_editor/
render.rs1use ratatui::{prelude::*, widgets::Widget};
2use crate::editor::Editor;
3use crate::code::{
4 RopeGraphemes, grapheme_width_and_chars_len, grapheme_width_and_bytes_len
5};
6
7impl Widget for &Editor {
22 fn render(self, area: Rect, buf: &mut Buffer) {
23 let code = self.code_ref();
24 let total_lines = code.len_lines();
25 let total_chars = code.len_chars();
26 let max_line_number = total_lines.max(1);
27 let line_number_digits = max_line_number.to_string().len().max(5);
28 let line_number_width = line_number_digits + 2;
29
30 let mut draw_y = area.top();
31
32 let line_number_style = Style::default().fg(Color::DarkGray);
33 let default_text_style = Style::default().fg(Color::White);
34
35 for line_idx in self.offset_y..total_lines {
37 if draw_y >= area.bottom() { break }
38
39 let line_number = format!("{:^width$}", line_idx + 1, width = line_number_digits);
40 buf.set_string(area.left(), draw_y, &line_number, line_number_style);
41
42 let line_len = code.line_len(line_idx);
43 let max_x = (area.width as usize).saturating_sub(line_number_width);
44
45 let start_col = self.offset_x.min(line_len);
46 let end_col = (start_col + max_x).min(line_len);
47
48 let line_start_char = code.line_to_char(line_idx);
49 let char_start = line_start_char + start_col;
50 let char_end = line_start_char + end_col;
51
52 let visible_chars = code.char_slice(char_start, char_end);
53
54 let displayed_line = visible_chars.to_string().replace("\t", &" ");
55
56 let text_x = area.left() + line_number_width as u16;
57 if text_x < area.left() + area.width && draw_y < area.top() + area.height {
58 buf.set_string(text_x, draw_y, &displayed_line, default_text_style);
59 }
60
61 draw_y += 1;
62 }
63
64 if code.is_highlight() {
66
67 for screen_y in 0..(area.height as usize) {
74 let line_idx = self.offset_y + screen_y;
75 if line_idx >= total_lines { break }
76
77 let line_len = code.line_len(line_idx);
78 let max_x = (area.width as usize).saturating_sub(line_number_width);
79
80 let line_start_char = code.line_to_char(line_idx);
81 let start_char = line_start_char + self.offset_x;
82 let visible_len = line_len.saturating_sub(self.offset_x);
83 let end = max_x.min(visible_len);
84 let end_char = start_char + end;
85
86 if start_char > total_chars || end_char > total_chars {
87 continue; }
89
90 let chars = code.char_slice(start_char, end_char);
91
92 let start_byte = code.char_to_byte(start_char);
93 let end_byte = code.char_to_byte(end_char);
94
95 let highlights = self.highlight_interval(
96 start_byte, end_byte, &self.theme
97 );
98
99 let mut x = 0;
100 let mut byte_idx_in_rope = start_byte;
101
102 for g in RopeGraphemes::new(&chars) {
103 let (g_width, g_bytes) = grapheme_width_and_bytes_len(g);
104
105 if x >= max_x { break; }
106
107 let start_x = area.left() + line_number_width as u16 + x as u16;
108 let draw_y = area.top() + screen_y as u16;
109
110 for dx in 0..g_width {
111 if x + dx >= max_x { break; }
112 let draw_x = start_x + dx as u16;
113 for &(start, end, s) in &highlights {
114 if start <= byte_idx_in_rope && byte_idx_in_rope < end {
115 buf[(draw_x, draw_y)].set_style(s);
116 break;
117 }
118 }
119 }
120
121 x = x.saturating_add(g_width);
122 byte_idx_in_rope += g_bytes;
123 }
124 }
125 }
126
127 if let Some(selection) = self.selection && !selection.is_empty() {
129 let start = selection.start.min(selection.end);
130 let end = selection.start.max(selection.end);
131
132 let start_line = code.char_to_line(start);
133 let end_line = code.char_to_line(end);
134
135 for line_idx in start_line..=end_line {
136 if line_idx < self.offset_y { continue }
137 if line_idx >= self.offset_y + area.height as usize { break }
138
139 let line_start_char = code.line_to_char(line_idx);
140 let line_len = code.line_len(line_idx);
141 let line_end_char = line_start_char + line_len;
142
143 let sel_start = start.max(line_start_char);
144 let sel_end = end.min(line_end_char);
145
146 let rel_start = sel_start - line_start_char;
147 let rel_end = sel_end - line_start_char;
148
149 let start_col = self.offset_x.min(line_len);
150 let max_text_width = (area.width as usize).saturating_sub(line_number_width);
151 let end_col = (start_col + max_text_width).min(line_len);
152
153 let char_slice_start = line_start_char + start_col;
154 let char_slice_end = line_start_char + end_col;
155
156 let visible_chars = code.char_slice(char_slice_start, char_slice_end);
157
158 let draw_y = area.top() + (line_idx - self.offset_y) as u16;
159 let mut visual_x: u16 = 0;
160 let mut char_col = start_col;
161
162 for g in RopeGraphemes::new(&visible_chars) {
163 let (g_width, g_chars) = grapheme_width_and_chars_len(g);
164
165 if char_col < rel_end && char_col + g_chars > rel_start {
166 let start_x = area.left() + line_number_width as u16 + visual_x;
167 for dx in 0..g_width as u16 {
168 let draw_x = start_x + dx;
169 if draw_x < area.right() && draw_y < area.bottom() {
170 buf[(draw_x, draw_y)].set_style(Style::default().bg(Color::DarkGray));
171 }
172 }
173 }
174
175 visual_x = visual_x.saturating_add(g_width as u16);
176 char_col += g_chars;
177 }
178 }
179 }
180
181 if let Some(ref marks) = self.marks {
183 for &(start, end, color) in marks {
184 if start >= end || end > total_chars { continue }
185
186 let start_line = code.char_to_line(start);
187 let end_line = code.char_to_line(end);
188
189 for line_idx in start_line..=end_line {
190 if line_idx < self.offset_y || line_idx >= self.offset_y + area.height as usize {
191 continue;
192 }
193
194 let line_start_char = code.line_to_char(line_idx);
195 let line_len = code.line_len(line_idx);
196 let line_end_char = line_start_char + line_len;
197
198 let highlight_start = start.max(line_start_char);
199 let highlight_end = end.min(line_end_char);
200
201 let rel_start = highlight_start - line_start_char;
202 let rel_end = highlight_end - line_start_char;
203
204 let start_col = self.offset_x.min(line_len);
205 let max_text_width = (area.width as usize).saturating_sub(line_number_width);
206 let end_col = (start_col + max_text_width).min(line_len);
207
208 let char_slice_start = line_start_char + start_col;
209 let char_slice_end = line_start_char + end_col;
210
211 let visible_chars = code.char_slice(char_slice_start, char_slice_end);
212
213 let draw_y = area.top() + (line_idx - self.offset_y) as u16;
214 let mut visual_x: u16 = 0;
215 let mut char_col = start_col;
216
217 for g in RopeGraphemes::new(&visible_chars) {
218 let (g_width, g_chars) = grapheme_width_and_chars_len(g);
219
220 if char_col < rel_end && char_col + g_chars > rel_start {
221 let start_x = area.left() + line_number_width as u16 + visual_x;
222 for dx in 0..g_width as u16 {
223 let draw_x = start_x + dx;
224 if draw_x < area.right() && draw_y < area.bottom() {
225 buf[(draw_x, draw_y)].set_bg(color);
226 }
227 }
228 }
229
230 visual_x = visual_x.saturating_add(g_width as u16);
231 char_col += g_chars;
232 }
233 }
234 }
235 }
236 }
237}