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