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