iced_code_editor/canvas_editor/
mod.rs

1//! Canvas-based text editor widget for maximum performance.
2//!
3//! This module provides a custom Canvas widget that handles all text rendering
4//! and input directly, bypassing Iced's higher-level widgets for optimal speed.
5
6use iced::widget::Id;
7use iced::widget::canvas;
8use std::time::Instant;
9
10use crate::text_buffer::TextBuffer;
11use crate::theme::Style;
12pub use history::CommandHistory;
13
14// Re-export submodules
15mod canvas_impl;
16mod clipboard;
17pub mod command;
18mod cursor;
19pub mod history;
20mod selection;
21mod update;
22mod view;
23
24/// Canvas-based text editor constants
25pub(crate) const FONT_SIZE: f32 = 14.0;
26pub(crate) const LINE_HEIGHT: f32 = 20.0;
27pub(crate) const CHAR_WIDTH: f32 = 8.4; // Monospace character width
28pub(crate) const GUTTER_WIDTH: f32 = 60.0;
29pub(crate) const CURSOR_BLINK_INTERVAL: std::time::Duration =
30    std::time::Duration::from_millis(530);
31
32/// Canvas-based high-performance text editor.
33pub struct CodeEditor {
34    /// Text buffer
35    pub(crate) buffer: TextBuffer,
36    /// Cursor position (line, column)
37    pub(crate) cursor: (usize, usize),
38    /// Scroll offset in pixels
39    pub(crate) scroll_offset: f32,
40    /// Editor theme style
41    pub(crate) style: Style,
42    /// Syntax highlighting language
43    pub(crate) syntax: String,
44    /// Last cursor blink time
45    pub(crate) last_blink: Instant,
46    /// Cursor visible state
47    pub(crate) cursor_visible: bool,
48    /// Selection start (if any)
49    pub(crate) selection_start: Option<(usize, usize)>,
50    /// Selection end (if any) - cursor position during selection
51    pub(crate) selection_end: Option<(usize, usize)>,
52    /// Mouse is currently dragging for selection
53    pub(crate) is_dragging: bool,
54    /// Cache for canvas rendering
55    pub(crate) cache: canvas::Cache,
56    /// Scrollable ID for programmatic scrolling
57    pub(crate) scrollable_id: Id,
58    /// Current viewport scroll position (Y offset)
59    pub(crate) viewport_scroll: f32,
60    /// Viewport height (visible area)
61    pub(crate) viewport_height: f32,
62    /// Command history for undo/redo
63    pub(crate) history: CommandHistory,
64    /// Whether we're currently grouping commands (for smart undo)
65    pub(crate) is_grouping: bool,
66}
67
68/// Messages emitted by the code editor
69#[derive(Debug, Clone)]
70pub enum Message {
71    /// Character typed
72    CharacterInput(char),
73    /// Backspace pressed
74    Backspace,
75    /// Delete pressed
76    Delete,
77    /// Enter pressed
78    Enter,
79    /// Tab pressed (inserts 4 spaces)
80    Tab,
81    /// Arrow key pressed (direction, shift_pressed)
82    ArrowKey(ArrowDirection, bool),
83    /// Mouse clicked at position
84    MouseClick(iced::Point),
85    /// Mouse drag for selection
86    MouseDrag(iced::Point),
87    /// Mouse released
88    MouseRelease,
89    /// Copy selected text (Ctrl+C)
90    Copy,
91    /// Paste text from clipboard (Ctrl+V)
92    Paste(String),
93    /// Delete selected text (Shift+Delete)
94    DeleteSelection,
95    /// Request redraw for cursor blink
96    Tick,
97    /// Page Up pressed
98    PageUp,
99    /// Page Down pressed
100    PageDown,
101    /// Home key pressed (move to start of line, shift_pressed)
102    Home(bool),
103    /// End key pressed (move to end of line, shift_pressed)
104    End(bool),
105    /// Ctrl+Home pressed (move to start of document)
106    CtrlHome,
107    /// Ctrl+End pressed (move to end of document)
108    CtrlEnd,
109    /// Viewport scrolled - track scroll position
110    Scrolled(iced::widget::scrollable::Viewport),
111    /// Undo last operation (Ctrl+Z)
112    Undo,
113    /// Redo last undone operation (Ctrl+Y)
114    Redo,
115}
116
117/// Arrow key directions
118#[derive(Debug, Clone, Copy)]
119pub enum ArrowDirection {
120    Up,
121    Down,
122    Left,
123    Right,
124}
125
126impl CodeEditor {
127    /// Creates a new canvas-based text editor.
128    ///
129    /// # Arguments
130    ///
131    /// * `content` - Initial text content
132    /// * `syntax` - Syntax highlighting language (e.g., "py", "lua", "rs")
133    ///
134    /// # Returns
135    ///
136    /// A new `CodeEditor` instance
137    pub fn new(content: &str, syntax: &str) -> Self {
138        Self {
139            buffer: TextBuffer::new(content),
140            cursor: (0, 0),
141            scroll_offset: 0.0,
142            style: crate::theme::dark(&iced::Theme::Dark),
143            syntax: syntax.to_string(),
144            last_blink: Instant::now(),
145            cursor_visible: true,
146            selection_start: None,
147            selection_end: None,
148            is_dragging: false,
149            cache: canvas::Cache::default(),
150            scrollable_id: Id::unique(),
151            viewport_scroll: 0.0,
152            viewport_height: 600.0, // Default, will be updated
153            history: CommandHistory::new(100),
154            is_grouping: false,
155        }
156    }
157
158    /// Returns the current text content as a string.
159    ///
160    /// # Returns
161    ///
162    /// The complete text content of the editor
163    pub fn content(&self) -> String {
164        self.buffer.to_string()
165    }
166
167    /// Sets the theme style for the editor.
168    ///
169    /// # Arguments
170    ///
171    /// * `style` - The style to apply to the editor
172    ///
173    /// # Example
174    ///
175    /// ```
176    /// use iced_code_editor::{CodeEditor, theme};
177    ///
178    /// let mut editor = CodeEditor::new("fn main() {}", "rs");
179    /// editor.set_theme(theme::light(&iced::Theme::Light));
180    /// ```
181    pub fn set_theme(&mut self, style: Style) {
182        self.style = style;
183        self.cache.clear(); // Force redraw with new theme
184    }
185
186    /// Resets the cursor blink animation.
187    pub(crate) fn reset_cursor_blink(&mut self) {
188        self.last_blink = Instant::now();
189        self.cursor_visible = true;
190    }
191
192    /// Returns whether the editor has unsaved changes.
193    ///
194    /// # Returns
195    ///
196    /// `true` if there are unsaved modifications, `false` otherwise
197    pub fn is_modified(&self) -> bool {
198        self.history.is_modified()
199    }
200
201    /// Marks the current state as saved.
202    ///
203    /// Call this after successfully saving the file to reset the modified state.
204    pub fn mark_saved(&mut self) {
205        self.history.mark_saved();
206    }
207
208    /// Returns whether undo is available.
209    pub fn can_undo(&self) -> bool {
210        self.history.can_undo()
211    }
212
213    /// Returns whether redo is available.
214    pub fn can_redo(&self) -> bool {
215        self.history.can_redo()
216    }
217}