Răsfoiți Sursa

feat: Implement Emacs-style blinking block cursor with color inversion

- Add blinking timer with 500ms intervals matching Emacs behavior
- Replace line cursor with block cursor that covers entire character
- Implement color inversion: cursor background uses theme foreground color
- Character under cursor rendered with theme background color
- Cursor becomes visible immediately on keypress for responsive feel
- Proper timer cleanup in destructor and quit handler to prevent segfaults
- Update DEV_STATE.md with cursor implementation details

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Bernardo Magri 1 lună în urmă
părinte
comite
ba1bb2eb62
2 a modificat fișierele cu 56 adăugiri și 7 ștergeri
  1. 2 1
      DEV_STATE.md
  2. 54 6
      src/gtk_editor.cpp

+ 2 - 1
DEV_STATE.md

@@ -54,7 +54,7 @@ Lumacs/
 - ✅ **Phase 1-5**: Complete Emacs-like core functionality
 - ✅ **Phase 6 Core**: GTK4 frontend with text rendering
 - ✅ **Input System**: Full keyboard input with modifiers working
-- ✅ **Cursor System**: Fixed cursor positioning and text alignment
+- ✅ **Cursor System**: Emacs-style blinking block cursor with color inversion
 - ✅ **Text Editing**: Basic insertion, deletion, movement operations working
 - ✅ **Keybinding System**: Fixed conflicts, C-n, C-p, C-f, C-b, C-a, C-e, C-s, C-k working
 - ✅ **Clean Exit**: Fixed shutdown crashes and memory issues
@@ -73,6 +73,7 @@ Lumacs/
 - **GTK Threading**: All GTK operations must stay on main thread
 - **Memory Management**: Using RAII and smart pointers throughout C++ code
 - **Face System**: Themes are fully integrated with Pango text rendering
+- **Cursor Implementation**: Blinking timer with 500ms intervals, proper cleanup on exit
 - **Build System**: CMake-based with proper dependency management
 - **Testing**: Unit test framework in place for core components
 

+ 54 - 6
src/gtk_editor.cpp

@@ -25,6 +25,10 @@ class GtkEditor : public IEditorView {
 public:
     GtkEditor() : core_(nullptr) {}
     ~GtkEditor() override {
+        // Disconnect cursor timer first to prevent callbacks during destruction
+        if (cursor_timer_connection_.connected()) {
+            cursor_timer_connection_.disconnect();
+        }
         // Clear core pointer to prevent any callbacks during GTK cleanup
         core_ = nullptr;
         // Clear widget pointers - GTK manages their lifetime
@@ -54,6 +58,10 @@ public:
         }
 
         if (event == EditorEvent::Quit) {
+            // Disconnect timer before quitting to prevent segfault
+            if (cursor_timer_connection_.connected()) {
+                cursor_timer_connection_.disconnect();
+            }
             app_->quit(); // Quit the application gracefully
         }
     }
@@ -85,6 +93,10 @@ private:
     double char_width_ = 0;
     double line_height_ = 0;
     double ascent_ = 0;
+    
+    // Cursor blinking
+    bool cursor_visible_ = true;
+    sigc::connection cursor_timer_connection_;
 
 protected:
     void on_activate() {
@@ -110,6 +122,11 @@ protected:
         // Show window
         window_->present();
         drawing_area_->grab_focus();
+        
+        // Set up cursor blinking timer (500ms intervals like Emacs)
+        cursor_timer_connection_ = Glib::signal_timeout().connect(
+            sigc::mem_fun(*this, &GtkEditor::on_cursor_blink), 500
+        );
     }
 
     // Rendering
@@ -176,19 +193,47 @@ protected:
             layout->show_in_cairo_context(cr);
         }
 
-        // Render Cursor - Draw at same position as text
-        if (cursor.line >= static_cast<size_t>(start_line) && cursor.line < static_cast<size_t>(end_line)) {
+        // Render Cursor - Emacs-style blinking block cursor with color inversion
+        if (cursor_visible_ && cursor.line >= static_cast<size_t>(start_line) && cursor.line < static_cast<size_t>(end_line)) {
             double cursor_screen_x = cursor.column * char_width_;
             int screen_y = cursor.line - start_line;
             double cursor_y = screen_y * line_height_;
-
-            // Draw bright green vertical bar aligned with text
-            cr->set_source_rgb(0.0, 1.0, 0.0); // Bright green
-            cr->rectangle(cursor_screen_x, cursor_y, 2.0, line_height_);
+            
+            // Get the character under cursor for rendering with inverted colors
+            size_t buffer_line_idx = cursor.line;
+            const auto& cursor_line_text = buffer.line(buffer_line_idx);
+            char cursor_char = (cursor.column < cursor_line_text.length()) ? cursor_line_text[cursor.column] : ' ';
+            
+            // Draw block cursor background (inverted background color)
+            Color cursor_bg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255);
+            cr->set_source_rgb(cursor_bg.r / 255.0, cursor_bg.g / 255.0, cursor_bg.b / 255.0);
+            cr->rectangle(cursor_screen_x, cursor_y, char_width_, line_height_);
             cr->fill();
+            
+            // Draw the character with inverted color (background color as foreground)
+            if (cursor_char != '\0') {
+                Color cursor_fg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
+                cr->set_source_rgb(cursor_fg.r / 255.0, cursor_fg.g / 255.0, cursor_fg.b / 255.0);
+                
+                layout->set_text(std::string(1, cursor_char));
+                cr->move_to(cursor_screen_x, cursor_y);
+                layout->show_in_cairo_context(cr);
+            }
         }
     }
 
+    // Cursor blinking callback
+    bool on_cursor_blink() {
+        // Safety check - don't blink if core is destroyed or no drawing area
+        if (!core_ || !drawing_area_) {
+            return false; // Stop the timer
+        }
+        
+        cursor_visible_ = !cursor_visible_;
+        drawing_area_->queue_draw();
+        return true; // Continue timer
+    }
+
     std::string resolve_key(guint keyval, Gdk::ModifierType state) {
         // Handle modifier keys
         unsigned int state_uint = static_cast<unsigned int>(state);
@@ -234,6 +279,9 @@ protected:
         // Safety check - don't process keys if core is destroyed
         if (!core_) return false;
 
+        // Make cursor visible immediately when typing
+        cursor_visible_ = true;
+
         // 1. Resolve the base key name
         std::string key_name = resolve_key(keyval, state);
         if (key_name.empty()) return false;