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 frame.fill_rectangle(
30 Point::ORIGIN,
31 bounds.size(),
32 self.style.background,
33 );
34
35 let total_lines = self.buffer.line_count();
38
39 frame.fill_rectangle(
41 Point::ORIGIN,
42 Size::new(GUTTER_WIDTH, bounds.height),
43 self.style.gutter_background,
44 );
45
46 let syntax_set = SyntaxSet::load_defaults_newlines();
48 let theme_set = ThemeSet::load_defaults();
49 let syntax_theme = &theme_set.themes["base16-ocean.dark"];
50
51 let syntax_ref = match self.syntax.as_str() {
52 "py" | "python" => syntax_set.find_syntax_by_extension("py"),
53 "lua" => syntax_set.find_syntax_by_extension("lua"),
54 "rs" | "rust" => syntax_set.find_syntax_by_extension("rs"),
55 "js" | "javascript" => syntax_set.find_syntax_by_extension("js"),
56 "html" | "htm" => syntax_set.find_syntax_by_extension("html"),
57 "xml" | "svg" => syntax_set.find_syntax_by_extension("xml"),
58 "css" => syntax_set.find_syntax_by_extension("css"),
59 "json" => syntax_set.find_syntax_by_extension("json"),
60 "md" | "markdown" => syntax_set.find_syntax_by_extension("md"),
61 _ => Some(syntax_set.find_syntax_plain_text()),
62 };
63
64 for line_idx in 0..total_lines {
66 let y = line_idx as f32 * LINE_HEIGHT;
67
68 let line_num_text = format!("{:>4}", line_idx + 1);
70 frame.fill_text(canvas::Text {
71 content: line_num_text,
72 position: Point::new(5.0, y + 2.0),
73 color: self.style.line_number_color,
74 size: FONT_SIZE.into(),
75 font: iced::Font::MONOSPACE,
76 ..canvas::Text::default()
77 });
78
79 if line_idx == self.cursor.0 {
81 frame.fill_rectangle(
82 Point::new(GUTTER_WIDTH, y),
83 Size::new(bounds.width - GUTTER_WIDTH, LINE_HEIGHT),
84 self.style.current_line_highlight,
85 );
86 }
87
88 let line_content = self.buffer.line(line_idx);
90
91 if let Some(syntax) = syntax_ref {
92 let mut highlighter =
93 HighlightLines::new(syntax, syntax_theme);
94 let ranges = highlighter
95 .highlight_line(line_content, &syntax_set)
96 .unwrap_or_else(|_| {
97 vec![(Style::default(), line_content)]
98 });
99
100 let mut x_offset = GUTTER_WIDTH + 5.0;
101 for (style, text) in ranges {
102 let color = Color::from_rgb(
103 f32::from(style.foreground.r) / 255.0,
104 f32::from(style.foreground.g) / 255.0,
105 f32::from(style.foreground.b) / 255.0,
106 );
107
108 frame.fill_text(canvas::Text {
109 content: text.to_string(),
110 position: Point::new(x_offset, y + 2.0),
111 color,
112 size: FONT_SIZE.into(),
113 font: iced::Font::MONOSPACE,
114 ..canvas::Text::default()
115 });
116
117 x_offset += text.len() as f32 * CHAR_WIDTH;
118 }
119 } else {
120 frame.fill_text(canvas::Text {
122 content: line_content.to_string(),
123 position: Point::new(GUTTER_WIDTH + 5.0, y + 2.0),
124 color: self.style.text_color,
125 size: FONT_SIZE.into(),
126 font: iced::Font::MONOSPACE,
127 ..canvas::Text::default()
128 });
129 }
130 }
131
132 if let Some((start, end)) = self.get_selection_range()
134 && start != end
135 {
136 let selection_color = Color { r: 0.3, g: 0.5, b: 0.8, a: 0.3 };
137
138 if start.0 == end.0 {
139 let y = start.0 as f32 * LINE_HEIGHT;
141 let x_start =
142 GUTTER_WIDTH + 5.0 + start.1 as f32 * CHAR_WIDTH;
143 let x_end = GUTTER_WIDTH + 5.0 + end.1 as f32 * CHAR_WIDTH;
144
145 frame.fill_rectangle(
146 Point::new(x_start, y + 2.0),
147 Size::new(x_end - x_start, LINE_HEIGHT - 4.0),
148 selection_color,
149 );
150 } else {
151 let y_start = start.0 as f32 * LINE_HEIGHT;
154 let x_start =
155 GUTTER_WIDTH + 5.0 + start.1 as f32 * CHAR_WIDTH;
156 let first_line_len = self.buffer.line_len(start.0);
157 let x_end_first =
158 GUTTER_WIDTH + 5.0 + first_line_len as f32 * CHAR_WIDTH;
159
160 frame.fill_rectangle(
161 Point::new(x_start, y_start + 2.0),
162 Size::new(x_end_first - x_start, LINE_HEIGHT - 4.0),
163 selection_color,
164 );
165
166 for line_idx in (start.0 + 1)..end.0 {
168 let y = line_idx as f32 * LINE_HEIGHT;
169 let line_len = self.buffer.line_len(line_idx);
170 let width = line_len as f32 * CHAR_WIDTH;
171
172 frame.fill_rectangle(
173 Point::new(GUTTER_WIDTH + 5.0, y + 2.0),
174 Size::new(width, LINE_HEIGHT - 4.0),
175 selection_color,
176 );
177 }
178
179 let y_end = end.0 as f32 * LINE_HEIGHT;
181 let x_end = GUTTER_WIDTH + 5.0 + end.1 as f32 * CHAR_WIDTH;
182
183 frame.fill_rectangle(
184 Point::new(GUTTER_WIDTH + 5.0, y_end + 2.0),
185 Size::new(
186 x_end - (GUTTER_WIDTH + 5.0),
187 LINE_HEIGHT - 4.0,
188 ),
189 selection_color,
190 );
191 }
192 }
193
194 if self.cursor_visible {
196 let cursor_x =
197 GUTTER_WIDTH + 5.0 + self.cursor.1 as f32 * CHAR_WIDTH;
198 let cursor_y = self.cursor.0 as f32 * LINE_HEIGHT;
199
200 frame.fill_rectangle(
201 Point::new(cursor_x, cursor_y + 2.0),
202 Size::new(2.0, LINE_HEIGHT - 4.0),
203 self.style.text_color,
204 );
205 }
206 });
207
208 vec![geometry]
209 }
210
211 fn update(
212 &self,
213 _state: &mut Self::State,
214 event: &Event,
215 bounds: Rectangle,
216 cursor: mouse::Cursor,
217 ) -> Option<Action<Message>> {
218 match event {
219 Event::Keyboard(keyboard::Event::KeyPressed {
220 key,
221 modifiers,
222 text,
223 ..
224 }) => {
225 if (modifiers.control()
227 && matches!(key, keyboard::Key::Character(c) if c.as_str() == "c"))
228 || (modifiers.control()
229 && matches!(
230 key,
231 keyboard::Key::Named(keyboard::key::Named::Insert)
232 ))
233 {
234 return Some(Action::publish(Message::Copy).and_capture());
235 }
236
237 if modifiers.control()
239 && matches!(key, keyboard::Key::Character(z) if z.as_str() == "z")
240 {
241 return Some(Action::publish(Message::Undo).and_capture());
242 }
243
244 if modifiers.control()
246 && matches!(key, keyboard::Key::Character(y) if y.as_str() == "y")
247 {
248 return Some(Action::publish(Message::Redo).and_capture());
249 }
250
251 if (modifiers.control()
253 && matches!(key, keyboard::Key::Character(v) if v.as_str() == "v"))
254 || (modifiers.shift()
255 && matches!(
256 key,
257 keyboard::Key::Named(keyboard::key::Named::Insert)
258 ))
259 {
260 return Some(Action::publish(
262 Message::Paste(String::new()),
263 ));
264 }
265
266 if modifiers.control()
268 && matches!(
269 key,
270 keyboard::Key::Named(keyboard::key::Named::Home)
271 )
272 {
273 return Some(
274 Action::publish(Message::CtrlHome).and_capture(),
275 );
276 }
277
278 if modifiers.control()
280 && matches!(
281 key,
282 keyboard::Key::Named(keyboard::key::Named::End)
283 )
284 {
285 return Some(
286 Action::publish(Message::CtrlEnd).and_capture(),
287 );
288 }
289
290 if modifiers.shift()
292 && matches!(
293 key,
294 keyboard::Key::Named(keyboard::key::Named::Delete)
295 )
296 {
297 return Some(
298 Action::publish(Message::DeleteSelection).and_capture(),
299 );
300 }
301
302 if let Some(text_content) = text
307 && !text_content.is_empty()
308 && !modifiers.control()
309 && !modifiers.alt()
310 {
311 if let Some(first_char) = text_content.chars().next()
314 && !first_char.is_control()
315 {
316 return Some(
317 Action::publish(Message::CharacterInput(
318 first_char,
319 ))
320 .and_capture(),
321 );
322 }
323 }
324
325 let message = match key {
328 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
329 Some(Message::Backspace)
330 }
331 keyboard::Key::Named(keyboard::key::Named::Delete) => {
332 Some(Message::Delete)
333 }
334 keyboard::Key::Named(keyboard::key::Named::Enter) => {
335 Some(Message::Enter)
336 }
337 keyboard::Key::Named(keyboard::key::Named::Tab) => {
338 Some(Message::Tab)
340 }
341 keyboard::Key::Named(keyboard::key::Named::ArrowUp) => {
342 Some(Message::ArrowKey(
343 ArrowDirection::Up,
344 modifiers.shift(),
345 ))
346 }
347 keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
348 Some(Message::ArrowKey(
349 ArrowDirection::Down,
350 modifiers.shift(),
351 ))
352 }
353 keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
354 Some(Message::ArrowKey(
355 ArrowDirection::Left,
356 modifiers.shift(),
357 ))
358 }
359 keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
360 Some(Message::ArrowKey(
361 ArrowDirection::Right,
362 modifiers.shift(),
363 ))
364 }
365 keyboard::Key::Named(keyboard::key::Named::PageUp) => {
366 Some(Message::PageUp)
367 }
368 keyboard::Key::Named(keyboard::key::Named::PageDown) => {
369 Some(Message::PageDown)
370 }
371 keyboard::Key::Named(keyboard::key::Named::Home) => {
372 Some(Message::Home(modifiers.shift()))
373 }
374 keyboard::Key::Named(keyboard::key::Named::End) => {
375 Some(Message::End(modifiers.shift()))
376 }
377 _ => {
380 if !modifiers.control()
381 && !modifiers.alt()
382 && let keyboard::Key::Character(c) = key
383 && !c.is_empty()
384 {
385 return c
386 .chars()
387 .next()
388 .map(Message::CharacterInput)
389 .map(|msg| Action::publish(msg).and_capture());
390 }
391 None
392 }
393 };
394
395 message.map(|msg| Action::publish(msg).and_capture())
396 }
397 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
398 cursor.position_in(bounds).map(|position| {
399 Action::publish(Message::MouseClick(position))
401 })
402 }
403 Event::Mouse(mouse::Event::CursorMoved { .. }) => {
404 cursor.position_in(bounds).map(|position| {
406 Action::publish(Message::MouseDrag(position)).and_capture()
407 })
408 }
409 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
410 Some(Action::publish(Message::MouseRelease).and_capture())
411 }
412 _ => None,
413 }
414 }
415}