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