iced_code_editor/canvas_editor/
canvas_impl.rs1use iced::mouse;
4use iced::widget::canvas::{self, Geometry};
5use iced::{Color, Event, Point, Rectangle, Size, Theme, keyboard};
6use syntect::easy::HighlightLines;
7use syntect::highlighting::{Style, ThemeSet};
8use syntect::parsing::SyntaxSet;
9
10use super::{
11 ArrowDirection, CHAR_WIDTH, CodeEditor, FONT_SIZE, GUTTER_WIDTH,
12 LINE_HEIGHT, Message,
13};
14use iced::widget::canvas::Action;
15
16impl canvas::Program<Message> for CodeEditor {
17 type State = ();
18
19 fn draw(
20 &self,
21 _state: &Self::State,
22 renderer: &iced::Renderer,
23 _theme: &Theme,
24 bounds: Rectangle,
25 _cursor: mouse::Cursor,
26 ) -> Vec<Geometry> {
27 let geometry = self.cache.draw(renderer, bounds.size(), |frame| {
28 let total_lines = self.buffer.line_count();
33
34 let effective_viewport_height = if self.viewport_height > 0.0 {
39 self.viewport_height
40 } else {
41 bounds.height
42 };
43 let first_visible_line =
44 (self.viewport_scroll / LINE_HEIGHT).floor() as usize;
45 let visible_lines_count =
46 (effective_viewport_height / LINE_HEIGHT).ceil() as usize + 2;
47 let last_visible_line =
48 (first_visible_line + visible_lines_count).min(total_lines);
49
50 let syntax_set = SyntaxSet::load_defaults_newlines();
52 let theme_set = ThemeSet::load_defaults();
53 let syntax_theme = &theme_set.themes["base16-ocean.dark"];
54
55 let syntax_ref = match self.syntax.as_str() {
56 "py" | "python" => syntax_set.find_syntax_by_extension("py"),
57 "lua" => syntax_set.find_syntax_by_extension("lua"),
58 "rs" | "rust" => syntax_set.find_syntax_by_extension("rs"),
59 "js" | "javascript" => {
60 syntax_set.find_syntax_by_extension("js")
61 }
62 "html" | "htm" => syntax_set.find_syntax_by_extension("html"),
63 "xml" | "svg" => syntax_set.find_syntax_by_extension("xml"),
64 "css" => syntax_set.find_syntax_by_extension("css"),
65 "json" => syntax_set.find_syntax_by_extension("json"),
66 "md" | "markdown" => syntax_set.find_syntax_by_extension("md"),
67 _ => Some(syntax_set.find_syntax_plain_text()),
68 };
69
70 for line_idx in first_visible_line..last_visible_line {
72 let y = line_idx as f32 * LINE_HEIGHT;
73
74 let line_num_text = format!("{:>4}", line_idx + 1);
79 frame.fill_text(canvas::Text {
80 content: line_num_text,
81 position: Point::new(5.0, y + 2.0),
82 color: self.style.line_number_color,
83 size: FONT_SIZE.into(),
84 font: iced::Font::MONOSPACE,
85 ..canvas::Text::default()
86 });
87
88 if line_idx == self.cursor.0 {
90 frame.fill_rectangle(
91 Point::new(GUTTER_WIDTH, y),
92 Size::new(bounds.width - GUTTER_WIDTH, LINE_HEIGHT),
93 self.style.current_line_highlight,
94 );
95 }
96
97 let line_content = self.buffer.line(line_idx);
99
100 if let Some(syntax) = syntax_ref {
101 let mut highlighter =
102 HighlightLines::new(syntax, syntax_theme);
103 let ranges = highlighter
104 .highlight_line(line_content, &syntax_set)
105 .unwrap_or_else(|_| {
106 vec![(Style::default(), line_content)]
107 });
108
109 let mut x_offset = GUTTER_WIDTH + 5.0;
110 for (style, text) in ranges {
111 let color = Color::from_rgb(
112 f32::from(style.foreground.r) / 255.0,
113 f32::from(style.foreground.g) / 255.0,
114 f32::from(style.foreground.b) / 255.0,
115 );
116
117 frame.fill_text(canvas::Text {
118 content: text.to_string(),
119 position: Point::new(x_offset, y + 2.0),
120 color,
121 size: FONT_SIZE.into(),
122 font: iced::Font::MONOSPACE,
123 ..canvas::Text::default()
124 });
125
126 x_offset += text.len() as f32 * CHAR_WIDTH;
127 }
128 } else {
129 frame.fill_text(canvas::Text {
131 content: line_content.to_string(),
132 position: Point::new(GUTTER_WIDTH + 5.0, y + 2.0),
133 color: self.style.text_color,
134 size: FONT_SIZE.into(),
135 font: iced::Font::MONOSPACE,
136 ..canvas::Text::default()
137 });
138 }
139 }
140
141 if let Some((start, end)) = self.get_selection_range()
143 && start != end
144 {
145 let selection_color = Color { r: 0.3, g: 0.5, b: 0.8, a: 0.3 };
146
147 if start.0 == end.0 {
148 let y = start.0 as f32 * LINE_HEIGHT;
150 let x_start =
151 GUTTER_WIDTH + 5.0 + start.1 as f32 * CHAR_WIDTH;
152 let x_end = GUTTER_WIDTH + 5.0 + end.1 as f32 * CHAR_WIDTH;
153
154 frame.fill_rectangle(
155 Point::new(x_start, y + 2.0),
156 Size::new(x_end - x_start, LINE_HEIGHT - 4.0),
157 selection_color,
158 );
159 } else {
160 let y_start = start.0 as f32 * LINE_HEIGHT;
163 let x_start =
164 GUTTER_WIDTH + 5.0 + start.1 as f32 * CHAR_WIDTH;
165 let first_line_len = self.buffer.line_len(start.0);
166 let x_end_first =
167 GUTTER_WIDTH + 5.0 + first_line_len as f32 * CHAR_WIDTH;
168
169 frame.fill_rectangle(
170 Point::new(x_start, y_start + 2.0),
171 Size::new(x_end_first - x_start, LINE_HEIGHT - 4.0),
172 selection_color,
173 );
174
175 for line_idx in (start.0 + 1)..end.0 {
177 let y = line_idx as f32 * LINE_HEIGHT;
178 let line_len = self.buffer.line_len(line_idx);
179 let width = line_len as f32 * CHAR_WIDTH;
180
181 frame.fill_rectangle(
182 Point::new(GUTTER_WIDTH + 5.0, y + 2.0),
183 Size::new(width, LINE_HEIGHT - 4.0),
184 selection_color,
185 );
186 }
187
188 let y_end = end.0 as f32 * LINE_HEIGHT;
190 let x_end = GUTTER_WIDTH + 5.0 + end.1 as f32 * CHAR_WIDTH;
191
192 frame.fill_rectangle(
193 Point::new(GUTTER_WIDTH + 5.0, y_end + 2.0),
194 Size::new(
195 x_end - (GUTTER_WIDTH + 5.0),
196 LINE_HEIGHT - 4.0,
197 ),
198 selection_color,
199 );
200 }
201 }
202
203 if self.cursor_visible {
205 let cursor_x =
206 GUTTER_WIDTH + 5.0 + self.cursor.1 as f32 * CHAR_WIDTH;
207 let cursor_y = self.cursor.0 as f32 * LINE_HEIGHT;
208
209 frame.fill_rectangle(
210 Point::new(cursor_x, cursor_y + 2.0),
211 Size::new(2.0, LINE_HEIGHT - 4.0),
212 self.style.text_color,
213 );
214 }
215 });
216
217 vec![geometry]
218 }
219
220 fn update(
221 &self,
222 _state: &mut Self::State,
223 event: &Event,
224 bounds: Rectangle,
225 cursor: mouse::Cursor,
226 ) -> Option<Action<Message>> {
227 match event {
228 Event::Keyboard(keyboard::Event::KeyPressed {
229 key,
230 modifiers,
231 text,
232 ..
233 }) => {
234 if (modifiers.control()
236 && matches!(key, keyboard::Key::Character(c) if c.as_str() == "c"))
237 || (modifiers.control()
238 && matches!(
239 key,
240 keyboard::Key::Named(keyboard::key::Named::Insert)
241 ))
242 {
243 return Some(Action::publish(Message::Copy).and_capture());
244 }
245
246 if modifiers.control()
248 && matches!(key, keyboard::Key::Character(z) if z.as_str() == "z")
249 {
250 return Some(Action::publish(Message::Undo).and_capture());
251 }
252
253 if modifiers.control()
255 && matches!(key, keyboard::Key::Character(y) if y.as_str() == "y")
256 {
257 return Some(Action::publish(Message::Redo).and_capture());
258 }
259
260 if (modifiers.control()
262 && matches!(key, keyboard::Key::Character(v) if v.as_str() == "v"))
263 || (modifiers.shift()
264 && matches!(
265 key,
266 keyboard::Key::Named(keyboard::key::Named::Insert)
267 ))
268 {
269 return Some(Action::publish(
271 Message::Paste(String::new()),
272 ));
273 }
274
275 if modifiers.control()
277 && matches!(
278 key,
279 keyboard::Key::Named(keyboard::key::Named::Home)
280 )
281 {
282 return Some(
283 Action::publish(Message::CtrlHome).and_capture(),
284 );
285 }
286
287 if modifiers.control()
289 && matches!(
290 key,
291 keyboard::Key::Named(keyboard::key::Named::End)
292 )
293 {
294 return Some(
295 Action::publish(Message::CtrlEnd).and_capture(),
296 );
297 }
298
299 if modifiers.shift()
301 && matches!(
302 key,
303 keyboard::Key::Named(keyboard::key::Named::Delete)
304 )
305 {
306 return Some(
307 Action::publish(Message::DeleteSelection).and_capture(),
308 );
309 }
310
311 if let Some(text_content) = text
316 && !text_content.is_empty()
317 && !modifiers.control()
318 && !modifiers.alt()
319 {
320 if let Some(first_char) = text_content.chars().next()
323 && !first_char.is_control()
324 {
325 return Some(
326 Action::publish(Message::CharacterInput(
327 first_char,
328 ))
329 .and_capture(),
330 );
331 }
332 }
333
334 let message = match key {
337 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
338 Some(Message::Backspace)
339 }
340 keyboard::Key::Named(keyboard::key::Named::Delete) => {
341 Some(Message::Delete)
342 }
343 keyboard::Key::Named(keyboard::key::Named::Enter) => {
344 Some(Message::Enter)
345 }
346 keyboard::Key::Named(keyboard::key::Named::Tab) => {
347 Some(Message::Tab)
349 }
350 keyboard::Key::Named(keyboard::key::Named::ArrowUp) => {
351 Some(Message::ArrowKey(
352 ArrowDirection::Up,
353 modifiers.shift(),
354 ))
355 }
356 keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
357 Some(Message::ArrowKey(
358 ArrowDirection::Down,
359 modifiers.shift(),
360 ))
361 }
362 keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
363 Some(Message::ArrowKey(
364 ArrowDirection::Left,
365 modifiers.shift(),
366 ))
367 }
368 keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
369 Some(Message::ArrowKey(
370 ArrowDirection::Right,
371 modifiers.shift(),
372 ))
373 }
374 keyboard::Key::Named(keyboard::key::Named::PageUp) => {
375 Some(Message::PageUp)
376 }
377 keyboard::Key::Named(keyboard::key::Named::PageDown) => {
378 Some(Message::PageDown)
379 }
380 keyboard::Key::Named(keyboard::key::Named::Home) => {
381 Some(Message::Home(modifiers.shift()))
382 }
383 keyboard::Key::Named(keyboard::key::Named::End) => {
384 Some(Message::End(modifiers.shift()))
385 }
386 _ => {
389 if !modifiers.control()
390 && !modifiers.alt()
391 && let keyboard::Key::Character(c) = key
392 && !c.is_empty()
393 {
394 return c
395 .chars()
396 .next()
397 .map(Message::CharacterInput)
398 .map(|msg| Action::publish(msg).and_capture());
399 }
400 None
401 }
402 };
403
404 message.map(|msg| Action::publish(msg).and_capture())
405 }
406 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
407 cursor.position_in(bounds).map(|position| {
408 Action::publish(Message::MouseClick(position))
410 })
411 }
412 Event::Mouse(mouse::Event::CursorMoved { .. }) => {
413 cursor.position_in(bounds).map(|position| {
415 Action::publish(Message::MouseDrag(position)).and_capture()
416 })
417 }
418 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
419 Some(Action::publish(Message::MouseRelease).and_capture())
420 }
421 _ => None,
422 }
423 }
424}