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 viewport height for the editor.
168    ///
169    /// This determines the minimum height of the canvas, ensuring proper
170    /// background rendering even when content is smaller than the viewport.
171    ///
172    /// # Arguments
173    ///
174    /// * `height` - The viewport height in pixels
175    ///
176    /// # Returns
177    ///
178    /// Self for method chaining
179    ///
180    /// # Example
181    ///
182    /// ```
183    /// use iced_code_editor::CodeEditor;
184    ///
185    /// let editor = CodeEditor::new("fn main() {}", "rs")
186    ///     .with_viewport_height(500.0);
187    /// ```
188    #[must_use]
189    pub fn with_viewport_height(mut self, height: f32) -> Self {
190        self.viewport_height = height;
191        self
192    }
193
194    /// Sets the theme style for the editor.
195    ///
196    /// # Arguments
197    ///
198    /// * `style` - The style to apply to the editor
199    ///
200    /// # Example
201    ///
202    /// ```
203    /// use iced_code_editor::{CodeEditor, theme};
204    ///
205    /// let mut editor = CodeEditor::new("fn main() {}", "rs");
206    /// editor.set_theme(theme::light(&iced::Theme::Light));
207    /// ```
208    pub fn set_theme(&mut self, style: Style) {
209        self.style = style;
210        self.cache.clear(); // Force redraw with new theme
211    }
212
213    /// Resets the cursor blink animation.
214    pub(crate) fn reset_cursor_blink(&mut self) {
215        self.last_blink = Instant::now();
216        self.cursor_visible = true;
217    }
218
219    /// Returns whether the editor has unsaved changes.
220    ///
221    /// # Returns
222    ///
223    /// `true` if there are unsaved modifications, `false` otherwise
224    pub fn is_modified(&self) -> bool {
225        self.history.is_modified()
226    }
227
228    /// Marks the current state as saved.
229    ///
230    /// Call this after successfully saving the file to reset the modified state.
231    pub fn mark_saved(&mut self) {
232        self.history.mark_saved();
233    }
234
235    /// Returns whether undo is available.
236    pub fn can_undo(&self) -> bool {
237        self.history.can_undo()
238    }
239
240    /// Returns whether redo is available.
241    pub fn can_redo(&self) -> bool {
242        self.history.can_redo()
243    }
244}