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    /// Arrow key pressed (direction, shift_pressed)
80    ArrowKey(ArrowDirection, bool),
81    /// Mouse clicked at position
82    MouseClick(iced::Point),
83    /// Mouse drag for selection
84    MouseDrag(iced::Point),
85    /// Mouse released
86    MouseRelease,
87    /// Copy selected text (Ctrl+C)
88    Copy,
89    /// Paste text from clipboard (Ctrl+V)
90    Paste(String),
91    /// Delete selected text (Shift+Delete)
92    DeleteSelection,
93    /// Request redraw for cursor blink
94    Tick,
95    /// Page Up pressed
96    PageUp,
97    /// Page Down pressed
98    PageDown,
99    /// Home key pressed (move to start of line, shift_pressed)
100    Home(bool),
101    /// End key pressed (move to end of line, shift_pressed)
102    End(bool),
103    /// Ctrl+Home pressed (move to start of document)
104    CtrlHome,
105    /// Ctrl+End pressed (move to end of document)
106    CtrlEnd,
107    /// Viewport scrolled - track scroll position
108    Scrolled(iced::widget::scrollable::Viewport),
109    /// Undo last operation (Ctrl+Z)
110    Undo,
111    /// Redo last undone operation (Ctrl+Y)
112    Redo,
113}
114
115/// Arrow key directions
116#[derive(Debug, Clone, Copy)]
117pub enum ArrowDirection {
118    Up,
119    Down,
120    Left,
121    Right,
122}
123
124impl CodeEditor {
125    /// Creates a new canvas-based text editor.
126    ///
127    /// # Arguments
128    ///
129    /// * `content` - Initial text content
130    /// * `syntax` - Syntax highlighting language (e.g., "py", "lua", "rs")
131    ///
132    /// # Returns
133    ///
134    /// A new `CodeEditor` instance
135    pub fn new(content: &str, syntax: &str) -> Self {
136        Self {
137            buffer: TextBuffer::new(content),
138            cursor: (0, 0),
139            scroll_offset: 0.0,
140            style: crate::theme::dark(&iced::Theme::Dark),
141            syntax: syntax.to_string(),
142            last_blink: Instant::now(),
143            cursor_visible: true,
144            selection_start: None,
145            selection_end: None,
146            is_dragging: false,
147            cache: canvas::Cache::default(),
148            scrollable_id: Id::unique(),
149            viewport_scroll: 0.0,
150            viewport_height: 600.0, // Default, will be updated
151            history: CommandHistory::new(100),
152            is_grouping: false,
153        }
154    }
155
156    /// Returns the current text content as a string.
157    ///
158    /// # Returns
159    ///
160    /// The complete text content of the editor
161    pub fn content(&self) -> String {
162        self.buffer.to_string()
163    }
164
165    /// Sets the theme style for the editor.
166    ///
167    /// # Arguments
168    ///
169    /// * `style` - The style to apply to the editor
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// use iced_code_editor::{CodeEditor, theme};
175    ///
176    /// let mut editor = CodeEditor::new("fn main() {}", "rs");
177    /// editor.set_theme(theme::light(&iced::Theme::Light));
178    /// ```
179    pub fn set_theme(&mut self, style: Style) {
180        self.style = style;
181        self.cache.clear(); // Force redraw with new theme
182    }
183
184    /// Resets the cursor blink animation.
185    pub(crate) fn reset_cursor_blink(&mut self) {
186        self.last_blink = Instant::now();
187        self.cursor_visible = true;
188    }
189
190    /// Returns whether the editor has unsaved changes.
191    ///
192    /// # Returns
193    ///
194    /// `true` if there are unsaved modifications, `false` otherwise
195    pub fn is_modified(&self) -> bool {
196        self.history.is_modified()
197    }
198
199    /// Marks the current state as saved.
200    ///
201    /// Call this after successfully saving the file to reset the modified state.
202    pub fn mark_saved(&mut self) {
203        self.history.mark_saved();
204    }
205
206    /// Returns whether undo is available.
207    pub fn can_undo(&self) -> bool {
208        self.history.can_undo()
209    }
210
211    /// Returns whether redo is available.
212    pub fn can_redo(&self) -> bool {
213        self.history.can_redo()
214    }
215}