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 ..
220 }) => {
221 if (modifiers.control()
223 && matches!(key, keyboard::Key::Character(c) if c.as_str() == "c"))
224 || (modifiers.control()
225 && matches!(
226 key,
227 keyboard::Key::Named(keyboard::key::Named::Insert)
228 ))
229 {
230 return Some(Action::publish(Message::Copy).and_capture());
231 }
232
233 if modifiers.control()
235 && matches!(key, keyboard::Key::Character(z) if z.as_str() == "z")
236 {
237 return Some(Action::publish(Message::Undo).and_capture());
238 }
239
240 if modifiers.control()
242 && matches!(key, keyboard::Key::Character(y) if y.as_str() == "y")
243 {
244 return Some(Action::publish(Message::Redo).and_capture());
245 }
246
247 if (modifiers.control()
249 && matches!(key, keyboard::Key::Character(v) if v.as_str() == "v"))
250 || (modifiers.shift()
251 && matches!(
252 key,
253 keyboard::Key::Named(keyboard::key::Named::Insert)
254 ))
255 {
256 return Some(Action::publish(
258 Message::Paste(String::new()),
259 ));
260 }
261
262 if modifiers.control()
264 && matches!(
265 key,
266 keyboard::Key::Named(keyboard::key::Named::Home)
267 )
268 {
269 return Some(
270 Action::publish(Message::CtrlHome).and_capture(),
271 );
272 }
273
274 if modifiers.control()
276 && matches!(
277 key,
278 keyboard::Key::Named(keyboard::key::Named::End)
279 )
280 {
281 return Some(
282 Action::publish(Message::CtrlEnd).and_capture(),
283 );
284 }
285
286 if modifiers.shift()
288 && matches!(
289 key,
290 keyboard::Key::Named(keyboard::key::Named::Delete)
291 )
292 {
293 return Some(
294 Action::publish(Message::DeleteSelection).and_capture(),
295 );
296 }
297
298 let message = match key {
299 keyboard::Key::Character(c) if !modifiers.control() => {
300 c.chars().next().map(Message::CharacterInput)
301 }
302 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
303 Some(Message::Backspace)
304 }
305 keyboard::Key::Named(keyboard::key::Named::Delete) => {
306 Some(Message::Delete)
307 }
308 keyboard::Key::Named(keyboard::key::Named::Enter) => {
309 Some(Message::Enter)
310 }
311 keyboard::Key::Named(keyboard::key::Named::ArrowUp) => {
312 Some(Message::ArrowKey(
313 ArrowDirection::Up,
314 modifiers.shift(),
315 ))
316 }
317 keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
318 Some(Message::ArrowKey(
319 ArrowDirection::Down,
320 modifiers.shift(),
321 ))
322 }
323 keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
324 Some(Message::ArrowKey(
325 ArrowDirection::Left,
326 modifiers.shift(),
327 ))
328 }
329 keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
330 Some(Message::ArrowKey(
331 ArrowDirection::Right,
332 modifiers.shift(),
333 ))
334 }
335 keyboard::Key::Named(keyboard::key::Named::PageUp) => {
336 Some(Message::PageUp)
337 }
338 keyboard::Key::Named(keyboard::key::Named::PageDown) => {
339 Some(Message::PageDown)
340 }
341 keyboard::Key::Named(keyboard::key::Named::Home) => {
342 Some(Message::Home(modifiers.shift()))
343 }
344 keyboard::Key::Named(keyboard::key::Named::End) => {
345 Some(Message::End(modifiers.shift()))
346 }
347 _ => None,
348 };
349
350 message.map(|msg| Action::publish(msg).and_capture())
351 }
352 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
353 cursor.position_in(bounds).map(|position| {
354 Action::publish(Message::MouseClick(position))
356 })
357 }
358 Event::Mouse(mouse::Event::CursorMoved { .. }) => {
359 cursor.position_in(bounds).map(|position| {
361 Action::publish(Message::MouseDrag(position)).and_capture()
362 })
363 }
364 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
365 Some(Action::publish(Message::MouseRelease).and_capture())
366 }
367 _ => None,
368 }
369 }
370}