Prechádzať zdrojové kódy

refactor(gtk): Decouple GTK rendering into GtkRenderer

Bernardo Magri 1 mesiac pred
rodič
commit
a60aaa0c09
4 zmenil súbory, kde vykonal 564 pridanie a 656 odobranie
  1. 1 0
      documentation/PLAN.md
  2. 62 3
      include/lumacs/gtk_editor.hpp
  3. 33 653
      src/gtk_editor.cpp
  4. 468 0
      src/gtk_renderer.cpp

+ 1 - 0
documentation/PLAN.md

@@ -240,6 +240,7 @@ This phase focuses on improving the modularity, extensibility, and separation of
         *   Move `on_draw()`, `draw_window()`, `render_modeline_for_window()`, `render_minibuffer()`, and `render_modeline()` (if kept) methods into this new `GtkRenderer` class.
         *   Centralize font metrics calculation and caching within the `GtkRenderer`, performing it once or only when font settings change, rather than repeatedly.
         *   The `GtkEditor` would then own an instance of `GtkRenderer` and delegate drawing operations to it.
+    *   **Status Update:** All actions for Subtask A.2 have been completed. The `GtkRenderer` class has been implemented and `GtkEditor` now delegates rendering responsibilities to it.
 
 *   **Subtask A.3: Integrate with Modeline and Minibuffer Managers:**
     *   **Goal:** Ensure GTK frontend fully utilizes the centralized, UI-agnostic modeline and minibuffer logic.

+ 62 - 3
include/lumacs/gtk_editor.hpp

@@ -1,12 +1,71 @@
 #pragma once
 
 #include "lumacs/ui_interface.hpp"
+#include "lumacs/gtk_renderer.hpp"
+#include "lumacs/window.hpp"
+#include "lumacs/editor_core.hpp"
+#include "lumacs/face.hpp"
+
+#include <gtkmm.h>
+#include <pangomm.h>
 #include <memory>
+#include <vector>
+#include <map>
+
+// Forward declarations
+namespace lumacs {
+    class EditorCore;
+    class Window;
+}
+
+// Custom Gtk::ApplicationWindow to make constructor public
+class LumacsWindow : public Gtk::ApplicationWindow {
+public:
+    explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application);
+};
 
 namespace lumacs {
 
-/// @brief Create an instance of the GTK4-based editor view.
-/// @return Unique pointer to the editor view interface.
+class GtkEditor : public IEditorView {
+public:
+    GtkEditor();
+    ~GtkEditor() override;
+
+    void init() override;
+    void run() override;
+    void handle_editor_event(EditorEvent event) override;
+    void set_core(EditorCore* core) override;
+
+    void queue_redraw_all_windows(Gtk::Widget* widget);
+
+private:
+    EditorCore* core_;
+    std::shared_ptr<Window> cached_active_window_;
+
+    Glib::RefPtr<Gtk::Application> app_;
+    Gtk::Window* window_ = nullptr;
+    Gtk::DrawingArea* drawing_area_ = nullptr;
+    Gtk::Widget* content_widget_ = nullptr;
+    
+    bool cursor_visible_ = true;
+    sigc::connection cursor_timer_connection_;
+
+    std::unique_ptr<GtkRenderer> gtk_renderer_;
+
+protected:
+    void on_activate();
+    void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
+    bool on_cursor_blink();
+    void rebuild_layout();
+    Gtk::Widget* create_widget_for_layout_node(std::shared_ptr<LayoutNode> node);
+    
+    // Delegate these to GtkRenderer
+    std::optional<Position> resolve_screen_pos(std::shared_ptr<Window> window, double x, double y);
+
+    std::string resolve_key(guint keyval, Gdk::ModifierType state);
+    bool on_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
+};
+
 std::unique_ptr<IEditorView> create_gtk_editor();
 
-} // namespace lumacs
+} // namespace lumacs

+ 33 - 653
src/gtk_editor.cpp

@@ -4,6 +4,7 @@
 #include "lumacs/keybinding.hpp"
 #include "lumacs/command_system.hpp"
 #include "lumacs/minibuffer_manager.hpp" // Include for MinibufferManager and MinibufferMode
+#include "lumacs/gtk_renderer.hpp" // Include for GtkRenderer
 #include <iostream>
 #include <filesystem>
 #include <vector>
@@ -29,7 +30,7 @@ public:
 
 class GtkEditor : public IEditorView {
 public:
-    GtkEditor() : core_(nullptr) {}
+    GtkEditor() : core_(nullptr), drawing_area_(nullptr), content_widget_(nullptr) {}
     ~GtkEditor() override {
         // Disconnect cursor timer first to prevent callbacks during destruction
         if (cursor_timer_connection_.connected()) {
@@ -49,8 +50,10 @@ public:
         }
         
         // Clear widget pointers - GTK manages their lifetime
+        // drawing_area_ is now a raw pointer managed by Gtk::make_managed, so no delete needed
         drawing_area_ = nullptr;
         window_ = nullptr;
+        content_widget_ = nullptr; // Also managed by Gtk::make_managed
         // Let app_ RefPtr be destroyed naturally
     }
 
@@ -186,96 +189,18 @@ private:
     EditorCore* core_;
     std::shared_ptr<Window> cached_active_window_; // Cached to prevent focus jumping during redraws
 
-    void apply_face_attributes(Pango::AttrList& attr_list, const FaceAttributes& face, int start_index, int end_index) {
-        if (start_index >= end_index) return;
-
-        // Foreground
-        if (face.foreground) {
-            auto attr = Pango::Attribute::create_attr_foreground(
-                face.foreground->r * 257, face.foreground->g * 257, face.foreground->b * 257);
-            attr.set_start_index(start_index);
-            attr.set_end_index(end_index);
-            attr_list.insert(attr);
-        }
-        // Background
-        if (face.background) {
-            auto attr = Pango::Attribute::create_attr_background(
-                face.background->r * 257, face.background->g * 257, face.background->b * 257);
-            attr.set_start_index(start_index);
-            attr.set_end_index(end_index);
-            attr_list.insert(attr);
-        }
-        // Font Family
-        if (face.family) {
-            auto attr = Pango::Attribute::create_attr_family(*face.family);
-            attr.set_start_index(start_index);
-            attr.set_end_index(end_index);
-            attr_list.insert(attr);
-        }
-        // Weight
-        if (face.weight) {
-            Pango::Weight w = Pango::Weight::NORMAL;
-            if (*face.weight == FontWeight::Bold) w = Pango::Weight::BOLD;
-            else if (*face.weight == FontWeight::Light) w = Pango::Weight::LIGHT;
-            auto attr = Pango::Attribute::create_attr_weight(w);
-            attr.set_start_index(start_index);
-            attr.set_end_index(end_index);
-            attr_list.insert(attr);
-        }
-        // Slant/Style
-        if (face.slant) {
-            Pango::Style s = Pango::Style::NORMAL;
-            if (*face.slant == FontSlant::Italic) s = Pango::Style::ITALIC;
-            else if (*face.slant == FontSlant::Oblique) s = Pango::Style::OBLIQUE;
-            auto attr = Pango::Attribute::create_attr_style(s);
-            attr.set_start_index(start_index);
-            attr.set_end_index(end_index);
-            attr_list.insert(attr);
-        }
-        // Underline
-        if (face.underline && *face.underline) {
-            auto attr = Pango::Attribute::create_attr_underline(Pango::Underline::SINGLE);
-            attr.set_start_index(start_index);
-            attr.set_end_index(end_index);
-            attr_list.insert(attr);
-        }
-    }
-
     Glib::RefPtr<Gtk::Application> app_;
     Gtk::Window* window_ = nullptr; // Store window pointer for widget access only (not lifetime management)
-    // Member variables
     Gtk::DrawingArea* drawing_area_ = nullptr; // For single-window compatibility
     Gtk::Widget* content_widget_ = nullptr; // Will be either drawing_area_ or a split container
     
-    // Font caching
-    Pango::FontDescription font_desc_;
-    bool font_initialized_ = false;
-    double char_width_ = 0;
-    double line_height_ = 0;
-    double ascent_ = 0;
-    
-    // Render Caching
-    struct LineCache {
-        std::string text;
-        Glib::RefPtr<Pango::Layout> layout;
-    };
-    
-    struct WindowCache {
-        std::vector<LineCache> lines;
-    };
-    
-    std::map<Window*, WindowCache> render_cache_;
-    
-    // Layout padding
-    static constexpr double PADDING_LEFT = 8.0;
-    static constexpr double PADDING_TOP = 8.0;
-    static constexpr double PADDING_RIGHT = 8.0;
-    static constexpr double PADDING_BOTTOM = 8.0;
-    
     // Cursor blinking
     bool cursor_visible_ = true;
     sigc::connection cursor_timer_connection_;
 
+    // GtkRenderer instance
+    std::unique_ptr<GtkRenderer> gtk_renderer_;
+
 protected:
     void on_activate() {
         // Create main window and associate with the application
@@ -306,6 +231,11 @@ protected:
             drawing_area_->grab_focus();
         }
         
+        // Initialize GtkRenderer after drawing_area_ is set
+        if (core_ && drawing_area_) {
+            gtk_renderer_ = std::make_unique<GtkRenderer>(*core_, *drawing_area_);
+        }
+
         // 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
@@ -315,116 +245,9 @@ protected:
     // Rendering
     void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
         // Safety check - don't draw if core is null (during destruction)
-        if (!core_) return;
-
-        const auto cursor = core_->active_window()->cursor();
-
-        // Fill background
-        auto theme = core_->active_theme();
-        Color bg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
-        
-        cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
-        cr->paint();
-
-        // Create Pango layout
-        auto layout = Pango::Layout::create(drawing_area_->get_pango_context());
-        
-        // Font configuration
-        Pango::FontDescription font_desc("Monospace 12");
-        layout->set_font_description(font_desc);
+        if (!core_ || !gtk_renderer_) return;
 
-        // Get font metrics
-        Pango::FontMetrics metrics = layout->get_context()->get_metrics(font_desc);
-        line_height_ = (double)metrics.get_height() / PANGO_SCALE;
-        ascent_ = (double)metrics.get_ascent() / PANGO_SCALE;
-        
-        // Measure character width (for a single 'm' character)
-        layout->set_text("m");
-        Pango::Rectangle ink_rect, logical_rect;
-        layout->get_pixel_extents(ink_rect, logical_rect);
-        char_width_ = (double)logical_rect.get_width(); // Already in pixels, no PANGO_SCALE needed
-
-        // Update core's viewport size based on actual font metrics and padding
-        int content_width_px = width - static_cast<int>(PADDING_LEFT + PADDING_RIGHT);
-        int content_height_px = height - static_cast<int>(PADDING_TOP + PADDING_BOTTOM);
-
-        int visible_lines = static_cast<int>(content_height_px / line_height_);
-        int visible_cols = static_cast<int>(content_width_px / char_width_);
-        
-        // Reserve space for modeline and minibuffer at bottom
-        int editor_lines = std::max(0, visible_lines - 2); // Reserve lines for modeline and minibuffer
-        core_->set_viewport_size(visible_cols, editor_lines);
-
-        // Get default foreground color from theme
-        // auto theme = core_->active_theme(); // Redundant, theme already defined
-        Color fg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255); // Default to white
-        cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
-
-        // Render visible lines
-        const auto& buffer = core_->buffer();
-        auto [start_line, end_line] = core_->active_window()->visible_line_range();
-        int horizontal_offset = core_->active_window()->viewport().horizontal_offset;
-
-        for (int screen_y = 0; screen_y < editor_lines && start_line + screen_y < end_line; ++screen_y) {
-            size_t buffer_line_idx = start_line + screen_y;
-            const auto& line_text = buffer.line(buffer_line_idx);
-
-            // Apply horizontal scrolling - show only the visible portion of the line
-            std::string visible_text;
-            if (horizontal_offset < static_cast<int>(line_text.length())) {
-                visible_text = line_text.substr(horizontal_offset);
-            }
-            
-            layout->set_text(visible_text);
-
-            // Render text at proper position (Cairo expects top-left, not baseline)
-            double text_x = PADDING_LEFT;
-            double text_y = PADDING_TOP + screen_y * line_height_;
-            cr->move_to(text_x, text_y);
-            layout->show_in_cairo_context(cr);
-        }
-
-        // 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)) {
-            int screen_y = cursor.line - start_line;
-            double cursor_y = PADDING_TOP + screen_y * line_height_;
-            
-            // Get the line text and calculate exact cursor position using Pango text measurement
-            size_t buffer_line_idx = cursor.line;
-            const auto& cursor_line_text = buffer.line(buffer_line_idx);
-            
-            // Calculate the exact X position by measuring text up to cursor position
-            double cursor_screen_x = PADDING_LEFT + (static_cast<int>(cursor.column) - horizontal_offset) * char_width_;
-            
-            
-            // Only render cursor if it's visible horizontally
-            if (cursor_screen_x >= PADDING_LEFT && cursor_screen_x < (width - PADDING_RIGHT)) {
-                // Get the character under cursor for rendering with inverted colors
-                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' && cursor_char != ' ') {
-                    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);
-                }
-            }
-        }
-        
-        // Render Modeline above minibuffer
-        render_modeline(cr, width, height, layout);
-        
-        // Render Minibuffer at bottom of screen
-        render_minibuffer(cr, width, height, layout);
+        gtk_renderer_->on_draw(cr, width, height, cached_active_window_, cursor_visible_);
     }
 
     // Cursor blinking callback
@@ -465,9 +288,6 @@ protected:
         // Clear the drawing area reference since we're rebuilding
         drawing_area_ = nullptr;
 
-        // Clear render cache to prevent stale window pointers
-        render_cache_.clear();
-
         // Initialize cached active window to prevent focus jumping
         cached_active_window_ = core_->active_window();
 
@@ -490,8 +310,8 @@ protected:
             // Use a weak reference to the window to avoid crashes if the layout is rebuilt
             std::weak_ptr<Window> weak_window = node->window;
             drawing_area->set_draw_func([this, weak_window, drawing_area](const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
-                if (auto window = weak_window.lock()) {
-                    draw_window(cr, width, height, window, drawing_area);
+                if (auto window = weak_window.lock() && gtk_renderer_) {
+                    gtk_renderer_->draw_window(cr, width, height, window, drawing_area, cached_active_window_, cursor_visible_);
                 }
             });
             
@@ -528,11 +348,13 @@ protected:
                     drawing_area->grab_focus();
                     
                     // 2. Move Cursor
-                    if (auto pos = resolve_screen_pos(window, x, y)) {
-                        window->set_cursor(*pos);
-                        // Clear mark on simple click
-                        window->buffer().deactivate_mark();
-                        drawing_area->queue_draw();
+                    if (gtk_renderer_) {
+                        if (auto pos = gtk_renderer_->resolve_screen_pos(window, x, y)) {
+                            window->set_cursor(*pos);
+                            // Clear mark on simple click
+                            window->buffer().deactivate_mark();
+                            drawing_area->queue_draw();
+                        }
                     }
                 }
             });
@@ -544,11 +366,13 @@ protected:
             
             drag_controller->signal_drag_begin().connect([this, weak_window_drag, drawing_area](double x, double y) {
                 if (auto window = weak_window_drag.lock()) {
-                    if (auto pos = resolve_screen_pos(window, x, y)) {
-                        // Set mark at start of drag
-                        window->buffer().set_mark(*pos);
-                        window->set_cursor(*pos);
-                        drawing_area->queue_draw();
+                    if (gtk_renderer_) {
+                        if (auto pos = gtk_renderer_->resolve_screen_pos(window, x, y)) {
+                            // Set mark at start of drag
+                            window->buffer().set_mark(*pos);
+                            window->set_cursor(*pos);
+                            drawing_area->queue_draw();
+                        }
                     }
                 }
             });
@@ -556,11 +380,11 @@ protected:
             drag_controller->signal_drag_update().connect([this, weak_window_drag, drawing_area, drag_controller](double dx, double dy) {
                 if (auto window = weak_window_drag.lock()) {
                      double start_x, start_y;
-                     if (drag_controller->get_start_point(start_x, start_y)) {
+                     if (drag_controller->get_start_point(start_x, start_y) && gtk_renderer_) {
                          double current_x = start_x + dx;
                          double current_y = start_y + dy;
                          
-                         if (auto pos = resolve_screen_pos(window, current_x, current_y)) {
+                         if (auto pos = gtk_renderer_->resolve_screen_pos(window, current_x, current_y)) {
                              window->set_cursor(*pos);
                              drawing_area->queue_draw();
                          }
@@ -629,423 +453,7 @@ protected:
             return paned;
         }
     }
-
-    // Draw a specific window (factored out from on_draw)
-    void draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height, std::shared_ptr<Window> window, Gtk::DrawingArea* widget) {
-        if (!core_ || !window) return;
-
-        const auto cursor = window->cursor();
-        const auto& buffer = window->buffer();
-        auto theme = core_->active_theme();
-
-        // Fill background
-        Color bg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
-        cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
-        cr->paint();
-
-        // Ensure metrics are initialized
-        if (!font_initialized_) {
-             auto layout = widget->create_pango_layout("m");
-             font_desc_ = Pango::FontDescription("Monospace 12");
-             layout->set_font_description(font_desc_);
-             
-             Pango::FontMetrics metrics = layout->get_context()->get_metrics(font_desc_);
-             line_height_ = (double)metrics.get_height() / PANGO_SCALE;
-             ascent_ = (double)metrics.get_ascent() / PANGO_SCALE;
-             
-             Pango::Rectangle ink_rect, logical_rect;
-             layout->get_pixel_extents(ink_rect, logical_rect);
-             char_width_ = (double)logical_rect.get_width();
-             
-             font_initialized_ = true;
-        }
-
-        // Update window's viewport size based on actual font metrics and padding
-        int content_width_px = width - static_cast<int>(PADDING_LEFT + PADDING_RIGHT);
-        int content_height_px = height - static_cast<int>(PADDING_TOP + PADDING_BOTTOM);
-
-        int visible_lines = static_cast<int>(content_height_px / line_height_);
-        int visible_cols = static_cast<int>(content_width_px / char_width_);
-
-        // Reserve space for modeline (all windows) and minibuffer (main window only)
-        // Use cached active window to prevent focus jumping during async redraws
-        bool is_main_window = (window == cached_active_window_);
-        int editor_lines = is_main_window ? std::max(0, visible_lines - 2) : std::max(0, visible_lines - 1);
-        window->set_viewport_size(visible_cols, editor_lines);
-
-        // Region/Mark Calculation
-        std::optional<Range> selection_range;
-        if (buffer.has_active_mark() && buffer.mark()) {
-             selection_range = buffer.get_region(window->cursor());
-        }
-
-        // Get default foreground color from theme
-        Color fg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255);
-
-        // Render visible lines
-        auto [start_line, end_line] = window->visible_line_range();
-        int horizontal_offset = window->viewport().horizontal_offset;
-        
-        for (int screen_y = 0; screen_y < editor_lines; ++screen_y) {
-            if (start_line + screen_y >= end_line) break;
-            
-            size_t buffer_line_idx = start_line + screen_y;
-            if (buffer_line_idx >= buffer.line_count()) break;
-
-            const auto& line_text = buffer.line(buffer_line_idx);
-
-            // Apply horizontal scrolling
-            std::string visible_text;
-            if (horizontal_offset < static_cast<int>(line_text.length())) {
-                visible_text = line_text.substr(horizontal_offset);
-            }
-            
-            // Create Layout
-            auto layout = widget->create_pango_layout(visible_text);
-            layout->set_font_description(font_desc_);
-
-            // Create Attribute List
-            Pango::AttrList attr_list;
-
-            // 1. Apply Syntax Highlighting
-            const auto& styles = buffer.get_line_styles(buffer_line_idx);
-            for (const auto& style : styles) {
-                if (theme) {
-                    if (auto face = theme->get_face(style.attr.face_name)) {
-                         int start = static_cast<int>(style.range.start.column) - horizontal_offset;
-                         int end = static_cast<int>(style.range.end.column) - horizontal_offset;
-                         
-                         start = std::max(0, start);
-                         end = std::min(static_cast<int>(visible_text.length()), end);
-                         
-                         if (start < end) {
-                             apply_face_attributes(attr_list, *face, start, end);
-                         }
-                    }
-                }
-            }
-
-            // 2. Apply Region/Selection Highlight
-            if (selection_range) {
-                 if (buffer_line_idx >= selection_range->start.line && buffer_line_idx <= selection_range->end.line) {
-                     size_t sel_start_col = (buffer_line_idx == selection_range->start.line) ? selection_range->start.column : 0;
-                     size_t sel_end_col = (buffer_line_idx == selection_range->end.line) ? selection_range->end.column : line_text.length();
-                     
-                     int start = static_cast<int>(sel_start_col) - horizontal_offset;
-                     int end = static_cast<int>(sel_end_col) - horizontal_offset;
-                     
-                     start = std::max(0, start);
-                     end = std::min(static_cast<int>(visible_text.length()), end);
-                     
-                     if (start < end) {
-                         if (auto region_face = theme->get_face("region")) {
-                              apply_face_attributes(attr_list, *region_face, start, end);
-                         } else {
-                              // Fallback: Standard selection blue/gray
-                              auto attr = Pango::Attribute::create_attr_background(0x4444, 0x4444, 0x4444);
-                              attr.set_start_index(start);
-                              attr.set_end_index(end);
-                              attr_list.insert(attr);
-                         }
-                     }
-                 }
-            }
-
-            layout->set_attributes(attr_list);
-
-            // Render text at proper position
-            double text_x = PADDING_LEFT;
-            double text_y = PADDING_TOP + screen_y * line_height_;
-            cr->move_to(text_x, text_y);
-            
-            cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
-            layout->show_in_cairo_context(cr);
-
-            // Render Cursor
-            // Use cached active window to prevent focus jumping during async redraws
-            bool should_show_cursor = (window == cached_active_window_) && cursor_visible_;
-            if (should_show_cursor && buffer_line_idx == cursor.line) {
-                 int cursor_idx = static_cast<int>(cursor.column) - horizontal_offset;
-                 
-                 Pango::Rectangle pos;
-                 if (cursor_idx < 0) {
-                     // Out of view
-                 } else if (cursor_idx > static_cast<int>(visible_text.length())) {
-                      // Past end of line
-                      pos = layout->index_to_pos(visible_text.length()); 
-                      int diff = cursor_idx - visible_text.length();
-                      if (diff > 0) {
-                           pos.set_x(pos.get_x() + diff * char_width_ * PANGO_SCALE);
-                      }
-                 } else {
-                     pos = layout->index_to_pos(cursor_idx);
-                 }
-
-                 double cursor_screen_x = PADDING_LEFT + (pos.get_x() / (double)PANGO_SCALE);
-                 
-                 if (cursor_screen_x >= PADDING_LEFT && cursor_screen_x < (width - PADDING_RIGHT)) {
-                     // Determine cursor width
-                     double cur_width = char_width_;
-                     if (cursor_idx < static_cast<int>(visible_text.length())) {
-                          Pango::Rectangle next_pos;
-                          next_pos = layout->index_to_pos(cursor_idx + 1);
-                          cur_width = (next_pos.get_x() - pos.get_x()) / (double)PANGO_SCALE;
-                     }
-                     
-                     // Draw Cursor Block
-                     Color cursor_bg = fg;
-                     cr->set_source_rgb(cursor_bg.r / 255.0, cursor_bg.g / 255.0, cursor_bg.b / 255.0);
-                     cr->rectangle(cursor_screen_x, text_y, cur_width, line_height_);
-                     cr->fill();
-                     
-                     // Draw Character Inverted
-                     if (cursor_idx < static_cast<int>(visible_text.length())) {
-                         char cursor_char = visible_text[cursor_idx];
-                         cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
-                         
-                         auto cursor_layout = widget->create_pango_layout(std::string(1, cursor_char));
-                         cursor_layout->set_font_description(font_desc_);
-                         // We should ideally copy attributes here too, but it's complex.
-                         // Defaulting to base font is acceptable for the inverted character.
-                         
-                         cr->move_to(cursor_screen_x, text_y);
-                         cursor_layout->show_in_cairo_context(cr);
-                     }
-                 }
-            }
-        }
-
-        // Use a temporary layout for modeline/minibuffer as they are dynamic and not part of the main text grid
-        auto temp_layout = Pango::Layout::create(cr);
-        temp_layout->set_font_description(font_desc_);
-
-        render_modeline_for_window(cr, width, height, temp_layout, window);
-        
-        if (is_main_window) {
-            render_minibuffer(cr, width, height, temp_layout);
-        }
-    }
-
-
-    // Render modeline for a specific window
-    void render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
-                                   const Glib::RefPtr<Pango::Layout>& layout, std::shared_ptr<Window> window) {
-        if (!core_ || !window) return;
-
-        // Use cached active window to prevent focus jumping during async redraws
-        bool is_active = (window == cached_active_window_);
-        
-        // Calculate modeline position (second line from bottom)
-        double modeline_y = height - (2 * line_height_) - PADDING_BOTTOM;
-        double modeline_x = PADDING_LEFT;
-        
-        // Get theme colors
-        auto theme = core_->active_theme();
-        ThemeElement element = is_active ? ThemeElement::StatusLine : ThemeElement::StatusLineInactive;
-        
-        Color bg = theme ? theme->get_bg_color(element) : (is_active ? Color(60, 60, 60) : Color(40, 40, 40));
-        Color fg = theme ? theme->get_fg_color(element) : (is_active ? Color(220, 220, 220) : Color(160, 160, 160));
-        
-        // Draw modeline background
-        cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
-        cr->rectangle(0, modeline_y, width, line_height_);
-        cr->fill();
-        
-        // Build modeline content using ModelineManager
-        auto content = core_->modeline_manager().generate_content(window, is_active);
-        
-        double x_offset = modeline_x;
-        for (const auto& chunk : content) {
-            Color chunk_fg = fg;
-            
-            // Resolve chunk face if needed
-            if (theme && !chunk.face_name.empty()) {
-                 if (auto face = theme->get_face(chunk.face_name)) {
-                     if (face->foreground) chunk_fg = *face->foreground;
-                 }
-            }
-            
-            layout->set_text(chunk.text);
-            
-            // Apply attributes
-            Pango::AttrList attr_list;
-            if (theme && !chunk.face_name.empty()) {
-                 if (auto face = theme->get_face(chunk.face_name)) {
-                     apply_face_attributes(attr_list, *face, 0, chunk.text.length());
-                 }
-            }
-            layout->set_attributes(attr_list);
-
-            cr->set_source_rgb(chunk_fg.r / 255.0, chunk_fg.g / 255.0, chunk_fg.b / 255.0);
-            cr->move_to(x_offset, modeline_y);
-            layout->show_in_cairo_context(cr);
-            
-            // Advance
-            int w, h;
-            layout->get_pixel_size(w, h);
-            x_offset += w;
-        }
-    }
-
-    // Render the modeline above minibuffer
-    void render_modeline(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height, 
-                        const Glib::RefPtr<Pango::Layout>& layout) {
-        if (!core_) return;
-        
-        // Calculate modeline position (second line from bottom)
-        double modeline_y = height - (2 * line_height_) - PADDING_BOTTOM;
-        double modeline_x = PADDING_LEFT;
-        
-        // Get theme colors
-        auto theme = core_->active_theme();
-        Color bg = theme ? theme->get_bg_color(ThemeElement::StatusLine) : Color(40, 40, 40);
-        Color fg = theme ? theme->get_fg_color(ThemeElement::StatusLine) : Color(200, 200, 200);
-        
-        // Draw modeline background
-        cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
-        cr->rectangle(0, modeline_y, width, line_height_);
-        cr->fill();
-        
-        // Build modeline content using ModelineManager
-        auto content = core_->modeline_manager().generate_content(core_->active_window(), true);
-        
-        double x_offset = modeline_x;
-        for (const auto& chunk : content) {
-            Color chunk_fg = fg;
-            if (theme && !chunk.face_name.empty()) {
-                 if (auto face = theme->get_face(chunk.face_name)) {
-                     if (face->foreground) chunk_fg = *face->foreground;
-                 }
-            }
-            
-            layout->set_text(chunk.text);
-            
-            Pango::AttrList attr_list;
-            if (theme && !chunk.face_name.empty()) {
-                 if (auto face = theme->get_face(chunk.face_name)) {
-                     apply_face_attributes(attr_list, *face, 0, chunk.text.length());
-                 }
-            }
-            layout->set_attributes(attr_list);
-
-            cr->set_source_rgb(chunk_fg.r / 255.0, chunk_fg.g / 255.0, chunk_fg.b / 255.0);
-            cr->move_to(x_offset, modeline_y);
-            layout->show_in_cairo_context(cr);
-            
-            int w, h;
-            layout->get_pixel_size(w, h);
-            x_offset += w;
-        }
-    }
-
-    // Render the minibuffer at bottom of screen
-    void render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height, 
-                          const Glib::RefPtr<Pango::Layout>& layout) {
-        if (!core_) return;
-
-        // Only render if minibuffer is active or a message is set
-        if (!core_->minibuffer_manager().is_active() && core_->last_message().empty()) {
-            return;
-        }
-        
-        // Calculate minibuffer position (bottom line with padding)
-        double minibuffer_y = height - line_height_ - PADDING_BOTTOM;
-        double minibuffer_x = PADDING_LEFT;
-        
-        // Get theme colors
-        auto theme = core_->active_theme();
-        Color bg = theme ? theme->get_bg_color(ThemeElement::Background) : Color(0, 0, 0);
-        Color fg = theme ? theme->get_fg_color(ThemeElement::Normal) : Color(255, 255, 255);
-        
-        // Draw minibuffer background (slightly different shade)
-        cr->set_source_rgb(bg.r / 255.0 * 0.9, bg.g / 255.0 * 0.9, bg.b / 255.0 * 0.9);
-        cr->rectangle(0, minibuffer_y - 2, width, line_height_ + 4);
-        cr->fill();
-        
-        // Draw separator line above minibuffer
-        cr->set_source_rgb(fg.r / 255.0 * 0.5, fg.g / 255.0 * 0.5, fg.b / 255.0 * 0.5);
-        cr->set_line_width(1.0);
-        cr->move_to(0, minibuffer_y - 2);
-        cr->line_to(width, minibuffer_y - 2);
-        cr->stroke();
-        
-        // Prepare minibuffer text
-        std::string minibuffer_text;
-        std::string prompt_part;
-        std::string input_part;
-        
-        if (core_->minibuffer_manager().is_active()) {
-            prompt_part = core_->minibuffer_manager().get_prompt();
-            input_part = core_->minibuffer_manager().get_input_buffer();
-            minibuffer_text = prompt_part + input_part;
-        } else if (!core_->last_message().empty()) {
-            minibuffer_text = core_->last_message();
-        }
-        
-        // Render minibuffer text
-        if (!minibuffer_text.empty()) {
-            cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
-            layout->set_text(minibuffer_text);
-            
-            // Apply face attributes for prompt if active
-            if (core_->minibuffer_manager().is_active()) {
-                Pango::AttrList attr_list;
-                if (auto face = theme->get_face("minibuffer-prompt")) {
-                    apply_face_attributes(attr_list, *face, 0, prompt_part.length());
-                }
-                layout->set_attributes(attr_list);
-            }
-
-            cr->move_to(minibuffer_x, minibuffer_y);
-            layout->show_in_cairo_context(cr);
-        }
-        
-        // Render minibuffer cursor if active and visible
-        if (core_->minibuffer_manager().is_active() && cursor_visible_) {
-            // Calculate cursor position in minibuffer
-            layout->set_text(minibuffer_text); // Measure full text
-            Pango::Rectangle ink_rect, logical_rect;
-            layout->get_pixel_extents(ink_rect, logical_rect);
-            double cursor_x = minibuffer_x + logical_rect.get_width();
-            
-            // Draw minibuffer cursor
-            cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
-            cr->rectangle(cursor_x, minibuffer_y, 2.0, line_height_);
-            cr->fill();
-
-            // Render completion overlay if applicable
-            auto current_completion = core_->minibuffer_manager().get_current_completion();
-            if (current_completion && input_part != *current_completion) {
-                std::string completion_suffix = current_completion->substr(input_part.length());
-                if (!completion_suffix.empty()) {
-                    auto completion_layout = Pango::Layout::create(cr);
-                    completion_layout->set_font_description(font_desc_);
-                    completion_layout->set_text(minibuffer_text + completion_suffix);
-                    
-                    Pango::AttrList completion_attr_list;
-                    if (auto face = theme->get_face("minibuffer-completion")) { // Assuming a face for completion
-                        apply_face_attributes(completion_attr_list, *face, minibuffer_text.length(), completion_suffix.length());
-                    } else {
-                        // Fallback: dimmed foreground
-                        Color dim_fg = fg;
-                        dim_fg.r = static_cast<unsigned char>(dim_fg.r * 0.7);
-                        dim_fg.g = static_cast<unsigned char>(dim_fg.g * 0.7);
-                        dim_fg.b = static_cast<unsigned char>(dim_fg.b * 0.7);
-                        auto attr = Pango::Attribute::create_attr_foreground(dim_fg.r * 257, dim_fg.g * 257, dim_fg.b * 257);
-                        attr.set_start_index(minibuffer_text.length());
-                        attr.set_end_index(minibuffer_text.length() + completion_suffix.length());
-                        completion_attr_list.insert(attr);
-                    }
-                    completion_layout->set_attributes(completion_attr_list);
-
-                    cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
-                    cr->move_to(minibuffer_x, minibuffer_y);
-                    completion_layout->show_in_cairo_context(cr);
-                }
-            }
-        }
-    }
-
+    
     std::string resolve_key(guint keyval, Gdk::ModifierType state) {
         // Handle modifier keys
         unsigned int state_uint = static_cast<unsigned int>(state);
@@ -1078,8 +486,6 @@ protected:
                     } else if ((state_uint & static_cast<unsigned int>(Gdk::ModifierType::SHIFT_MASK)) != 0) { 
                         key_name = std::string(1, static_cast<char>(keyval - (GDK_KEY_a - GDK_KEY_A)));
                     }
-                } else if (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9) {
-                    key_name = std::string(1, static_cast<char>(keyval));
                 } else if (keyval >= 32 && keyval <= 126) { 
                     key_name = std::string(1, static_cast<char>(keyval));
                 }
@@ -1088,32 +494,6 @@ protected:
         return key_name;
     }
 
-    // Helper to convert screen coordinates to buffer position
-    std::optional<Position> resolve_screen_pos(std::shared_ptr<Window> window, double x, double y) {
-        if (!window || line_height_ <= 0 || char_width_ <= 0) return std::nullopt;
-
-        int row = static_cast<int>((y - PADDING_TOP) / line_height_);
-        int col = static_cast<int>((x - PADDING_LEFT) / char_width_);
-        
-        if (row < 0) row = 0;
-        if (col < 0) col = 0;
-        
-        auto viewport = window->viewport();
-        size_t target_line = viewport.scroll_offset + row;
-        size_t target_col = viewport.horizontal_offset + col;
-        
-        // Clamp to buffer bounds
-        if (target_line >= window->buffer().line_count()) {
-             target_line = window->buffer().line_count() - 1;
-        }
-        
-        // Clamp column to line length
-        size_t line_len = window->buffer().line(target_line).length();
-        if (target_col > line_len) target_col = line_len;
-        
-        return Position{target_line, target_col};
-    }
-
     // Input
     bool on_key_pressed(guint keyval, guint /*keycode*/, Gdk::ModifierType state) {
         // Safety check - don't process keys if core is destroyed

+ 468 - 0
src/gtk_renderer.cpp

@@ -0,0 +1,468 @@
+#include "lumacs/gtk_renderer.hpp"
+#include "lumacs/editor_core.hpp"
+#include "lumacs/minibuffer_manager.hpp" // For MinibufferManager and MinibufferMode
+#include <algorithm> // For std::max, std::min
+
+namespace lumacs {
+
+GtkRenderer::GtkRenderer(EditorCore& core, Gtk::DrawingArea& main_drawing_area)
+    : core_(core), main_drawing_area_(main_drawing_area) {
+    initialize_font_metrics(); // Initialize font metrics once during construction
+}
+
+void GtkRenderer::initialize_font_metrics() {
+    if (font_initialized_) return;
+
+    // Use the main_drawing_area_ to create a Pango layout for font metrics
+    auto layout = main_drawing_area_.create_pango_layout("m");
+    font_desc_ = Pango::FontDescription("Monospace 12");
+    layout->set_font_description(font_desc_);
+    
+    Pango::FontMetrics metrics = layout->get_context()->get_metrics(font_desc_);
+    line_height_ = (double)metrics.get_height() / PANGO_SCALE;
+    ascent_ = (double)metrics.get_ascent() / PANGO_SCALE;
+    
+    Pango::Rectangle ink_rect, logical_rect;
+    layout->get_pixel_extents(ink_rect, logical_rect);
+    char_width_ = (double)logical_rect.get_width();
+    
+    font_initialized_ = true;
+}
+
+void GtkRenderer::apply_face_attributes(Pango::AttrList& attr_list, const FaceAttributes& face, int start_index, int end_index) {
+    if (start_index >= end_index) return;
+
+    // Foreground
+    if (face.foreground) {
+        auto attr = Pango::Attribute::create_attr_foreground(
+            face.foreground->r * 257, face.foreground->g * 257, face.foreground->b * 257);
+        attr.set_start_index(start_index);
+        attr.set_end_index(end_index);
+        attr_list.insert(attr);
+    }
+    // Background
+    if (face.background) {
+        auto attr = Pango::Attribute::create_attr_background(
+            face.background->r * 257, face.background->g * 257, face.background->b * 257);
+        attr.set_start_index(start_index);
+        attr.set_end_index(end_index);
+        attr_list.insert(attr);
+    }
+    // Font Family
+    if (face.family) {
+        auto attr = Pango::Attribute::create_attr_family(*face.family);
+        attr.set_start_index(start_index);
+        attr.set_end_index(end_index);
+        attr_list.insert(attr);
+    }
+    // Weight
+    if (face.weight) {
+        Pango::Weight w = Pango::Weight::NORMAL;
+        if (*face.weight == FontWeight::Bold) w = Pango::Weight::BOLD;
+        else if (*face.weight == FontWeight::Light) w = Pango::Weight::LIGHT;
+        auto attr = Pango::Attribute::create_attr_weight(w);
+        attr.set_start_index(start_index);
+        attr.set_end_index(end_index);
+        attr_list.insert(attr);
+    }
+    // Slant/Style
+    if (face.slant) {
+        Pango::Style s = Pango::Style::NORMAL;
+        if (*face.slant == FontSlant::Italic) s = Pango::Style::ITALIC;
+        else if (*face.slant == FontSlant::Oblique) s = Pango::Style::OBLIQUE;
+        auto attr = Pango::Attribute::create_attr_style(s);
+        attr.set_start_index(start_index);
+        attr.set_end_index(end_index);
+        attr_list.insert(attr);
+    }
+    // Underline
+    if (face.underline && *face.underline) {
+        auto attr = Pango::Attribute::create_attr_underline(Pango::Underline::SINGLE);
+        attr.set_start_index(start_index);
+        attr.set_end_index(end_index);
+        attr_list.insert(attr);
+    }
+}
+
+std::optional<Position> GtkRenderer::resolve_screen_pos(std::shared_ptr<Window> window, double x, double y) {
+    if (!window || line_height_ <= 0 || char_width_ <= 0) return std::nullopt;
+
+    int row = static_cast<int>((y - PADDING_TOP) / line_height_);
+    int col = static_cast<int>((x - PADDING_LEFT) / char_width_);
+    
+    if (row < 0) row = 0;
+    if (col < 0) col = 0;
+    
+    auto viewport = window->viewport();
+    size_t target_line = viewport.scroll_offset + row;
+    size_t target_col = viewport.horizontal_offset + col;
+    
+    // Clamp to buffer bounds
+    if (target_line >= window->buffer().line_count()) {
+         target_line = window->buffer().line_count() - 1;
+    }
+    
+    // Clamp column to line length
+    size_t line_len = window->buffer().line(target_line).length();
+    if (target_col > line_len) target_col = line_len;
+    
+    return Position{target_line, target_col};
+}
+
+void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
+    // Safety check - don't draw if core has no active theme (during destruction)
+    if (!core_.active_theme()) return;
+
+    // Fill background of the entire drawing area
+    auto theme = core_.active_theme();
+    Color bg = theme->get_bg_color(ThemeElement::Background);
+    cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
+    cr->paint();
+
+    // Get the active window from core to render it
+    auto active_window = core_.active_window();
+    if (!active_window) return;
+
+    // Render the active window (main editor area)
+    draw_window(cr, width, height, active_window, &main_drawing_area_);
+
+    // Use a temporary layout for modeline/minibuffer as they are dynamic
+    auto temp_layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
+    temp_layout->set_font_description(font_desc_);
+
+    // Render modeline
+    render_modeline_for_window(cr, width, height, temp_layout, active_window);
+    
+    // Render minibuffer
+    render_minibuffer(cr, width, height, temp_layout);
+}
+
+void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                                std::shared_ptr<Window> window, Gtk::DrawingArea* widget) {
+    if (!core_.active_theme() || !window || !widget) return;
+
+    // Use a temporary layout that is properly initialized
+    auto layout = Pango::Layout::create(widget->get_pango_context());
+    layout->set_font_description(font_desc_);
+
+    const auto cursor = window->cursor();
+    const auto& buffer = window->buffer();
+    auto theme = core_.active_theme();
+
+    // Fill background
+    Color bg = theme->get_bg_color(ThemeElement::Background);
+    cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
+    cr->paint();
+
+    int content_width_px = width - static_cast<int>(PADDING_LEFT + PADDING_RIGHT);
+    int content_height_px = height - static_cast<int>(PADDING_TOP + PADDING_BOTTOM);
+
+    int visible_lines = static_cast<int>(content_height_px / line_height_);
+    int visible_cols = static_cast<int>(content_width_px / char_width_);
+
+    // Reserve space for modeline (1 line). Minibuffer is global and rendered separately.
+    int editor_lines = std::max(0, visible_lines - 1); 
+    window->set_viewport_size(visible_cols, editor_lines);
+
+    // Region/Mark Calculation
+    std::optional<Range> selection_range;
+    if (buffer.has_active_mark() && buffer.mark()) {
+         selection_range = buffer.get_region(window->cursor());
+    }
+
+    // Get default foreground color from theme
+    Color fg = theme->get_fg_color(ThemeElement::Normal);
+    cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+
+    // Render visible lines
+    auto [start_line, end_line] = window->visible_line_range();
+    int horizontal_offset = window->viewport().horizontal_offset;
+    
+    for (int screen_y = 0; screen_y < editor_lines && start_line + screen_y < end_line; ++screen_y) {
+        size_t buffer_line_idx = start_line + screen_y;
+        if (buffer_line_idx >= buffer.line_count()) break; // Safety check
+        const auto& line_text = buffer.line(buffer_line_idx);
+
+        // Apply horizontal scrolling
+        std::string visible_text;
+        if (horizontal_offset < static_cast<int>(line_text.length())) {
+            visible_text = line_text.substr(horizontal_offset);
+        }
+        
+        layout->set_text(visible_text);
+
+        // Create Attribute List
+        Pango::AttrList attr_list;
+
+        // 1. Apply Syntax Highlighting
+        const auto& styles = buffer.get_line_styles(buffer_line_idx);
+        for (const auto& style : styles) {
+            if (auto face = theme->get_face(style.attr.face_name)) {
+                 int start = static_cast<int>(style.range.start.column) - horizontal_offset;
+                 int end = static_cast<int>(style.range.end.column) - horizontal_offset;
+                 
+                 start = std::max(0, start);
+                 end = std::min(static_cast<int>(visible_text.length()), end);
+                 
+                 if (start < end) {
+                     apply_face_attributes(attr_list, *face, start, end);
+                 }
+            }
+        }
+
+        // 2. Apply Region/Selection Highlight
+        if (selection_range) {
+             if (buffer_line_idx >= selection_range->start.line && buffer_line_idx <= selection_range->end.line) {
+                 size_t sel_start_col = (buffer_line_idx == selection_range->start.line) ? selection_range->start.column : 0;
+                 size_t sel_end_col = (buffer_line_idx == selection_range->end.line) ? selection_range->end.column : line_text.length();
+                 
+                 int start = static_cast<int>(sel_start_col) - horizontal_offset;
+                 int end = static_cast<int>(sel_end_col) - horizontal_offset;
+                 
+                 start = std::max(0, start);
+                 end = std::min(static_cast<int>(visible_text.length()), end);
+                 
+                 if (start < end) {
+                     if (auto region_face = theme->get_face("region")) {
+                          apply_face_attributes(attr_list, *region_face, start, end);
+                     } else {
+                          // Fallback: Standard selection blue/gray
+                          auto attr = Pango::Attribute::create_attr_background(0x4444, 0x4444, 0x4444);
+                          attr.set_start_index(start);
+                          attr.set_end_index(end);
+                          attr_list.insert(attr);
+                     }
+                 }
+             }
+        }
+
+        layout->set_attributes(attr_list);
+
+        // Render text at proper position
+        double text_x = PADDING_LEFT;
+        double text_y = PADDING_TOP + screen_y * line_height_;
+        cr->move_to(text_x, text_y);
+        
+        cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+        layout->show_in_cairo_context(cr);
+
+        // Render Cursor (assuming `cursor_visible_` is managed by GtkEditor)
+        bool cursor_visible_ = true; // Placeholder, should be passed or queried
+        bool is_active_window = (window == core_.active_window());
+        if (is_active_window && cursor_visible_ && buffer_line_idx == cursor.line) {
+             int cursor_idx = static_cast<int>(cursor.column) - horizontal_offset;
+             
+             Pango::Rectangle pos;
+             if (cursor_idx < 0) {
+                 // Out of view
+             } else if (cursor_idx > static_cast<int>(visible_text.length())) {
+                  // Past end of line
+                  pos = layout->index_to_pos(visible_text.length()); 
+                  int diff = cursor_idx - static_cast<int>(visible_text.length());
+                  if (diff > 0) {
+                       pos.set_x(pos.get_x() + diff * char_width_ * PANGO_SCALE);
+                  }
+             } else {
+                 pos = layout->index_to_pos(cursor_idx);
+             }
+
+             double cursor_screen_x = PADDING_LEFT + (pos.get_x() / (double)PANGO_SCALE);
+             
+             if (cursor_screen_x >= PADDING_LEFT && cursor_screen_x < (width - PADDING_RIGHT)) {
+                 // Determine cursor width
+                 double cur_width = char_width_;
+                 if (cursor_idx < static_cast<int>(visible_text.length())) {
+                      Pango::Rectangle next_pos;
+                      next_pos = layout->index_to_pos(cursor_idx + 1);
+                      cur_width = (next_pos.get_x() - pos.get_x()) / (double)PANGO_SCALE;
+                 }
+                 
+                 // Draw Cursor Block
+                 Color cursor_bg = fg;
+                 cr->set_source_rgb(cursor_bg.r / 255.0, cursor_bg.g / 255.0, cursor_bg.b / 255.0);
+                 cr->rectangle(cursor_screen_x, text_y, cur_width, line_height_);
+                 cr->fill();
+                 
+                 // Draw Character Inverted
+                 if (cursor_idx < static_cast<int>(visible_text.length())) {
+                     char cursor_char = visible_text[cursor_idx];
+                     cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
+                     
+                     auto cursor_layout = widget->create_pango_layout(std::string(1, cursor_char));
+                     cursor_layout->set_font_description(font_desc_);
+                     
+                     cr->move_to(cursor_screen_x, text_y);
+                     cursor_layout->show_in_cairo_context(cr);
+                 }
+             }
+        }
+    }
+void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                                           const Glib::RefPtr<Pango::Layout>& layout, std::shared_ptr<Window> window) {
+    if (!core_.active_theme() || !window) return;
+
+    // The logic for is_active needs to be passed from GtkEditor
+    bool is_active = (window == core_.active_window()); // Placeholder
+    
+    // Calculate modeline position (second line from bottom)
+    double modeline_y = height - line_height_ - PADDING_BOTTOM; // Adjust height - modeline is on line above minibuffer
+    double modeline_x = PADDING_LEFT;
+    
+    // Get theme colors
+    auto theme = core_.active_theme();
+    ThemeElement element = is_active ? ThemeElement::StatusLine : ThemeElement::StatusLineInactive;
+    
+    Color bg = theme->get_bg_color(element);
+    Color fg = theme->get_fg_color(element);
+    
+    // Draw modeline background
+    cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
+    cr->rectangle(0, modeline_y, width, line_height_);
+    cr->fill();
+    
+    // Build modeline content using ModelineManager
+    auto content = core_.modeline_manager().generate_content(window, is_active);
+    
+    double x_offset = modeline_x;
+    for (const auto& chunk : content) {
+        Color chunk_fg = fg;
+        
+        // Resolve chunk face if needed
+        if (!chunk.face_name.empty()) {
+             if (auto face = theme->get_face(chunk.face_name)) {
+                 if (face->foreground) chunk_fg = *face->foreground;
+             }
+        }
+        
+        layout->set_text(chunk.text);
+        
+        // Apply attributes
+        Pango::AttrList attr_list;
+        if (!chunk.face_name.empty()) {
+             if (auto face = theme->get_face(chunk.face_name)) {
+                 apply_face_attributes(attr_list, *face, 0, static_cast<int>(chunk.text.length()));
+             }
+        }
+        layout->set_attributes(attr_list);
+
+        cr->set_source_rgb(chunk_fg.r / 255.0, chunk_fg.g / 255.0, chunk_fg.b / 255.0);
+        cr->move_to(x_offset, modeline_y);
+        layout->show_in_cairo_context(cr);
+        
+        // Advance
+        int w, h;
+        layout->get_pixel_size(w, h);
+        x_offset += w;
+    }
+}
+
+
+void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                                    const Glib::RefPtr<Pango::Layout>& layout) {
+    if (!core_.active_theme()) return;
+
+    // Only render if minibuffer is active or a message is set
+    if (!core_.minibuffer_manager().is_active() && core_.last_message().empty()) {
+        return;
+    }
+    
+    // Calculate minibuffer position (bottom line with padding)
+    double minibuffer_y = height - line_height_ - PADDING_BOTTOM;
+    double minibuffer_x = PADDING_LEFT;
+    
+    // Get theme colors
+    auto theme = core_.active_theme();
+    Color bg = theme->get_bg_color(ThemeElement::Background);
+    Color fg = theme->get_fg_color(ThemeElement::Normal);
+    
+    // Draw minibuffer background (slightly different shade)
+    cr->set_source_rgb(bg.r / 255.0 * 0.9, bg.g / 255.0 * 0.9, bg.b / 255.0 * 0.9);
+    cr->rectangle(0, minibuffer_y - 2, width, line_height_ + 4);
+    cr->fill();
+    
+    // Draw separator line above minibuffer
+    cr->set_source_rgb(fg.r / 255.0 * 0.5, fg.g / 255.0 * 0.5, fg.b / 255.0 * 0.5);
+    cr->set_line_width(1.0);
+    cr->move_to(0, minibuffer_y - 2);
+    cr->line_to(width, minibuffer_y - 2);
+    cr->stroke();
+    
+    // Prepare minibuffer text
+    std::string minibuffer_text;
+    std::string prompt_part;
+    std::string input_part;
+    
+    if (core_.minibuffer_manager().is_active()) {
+        prompt_part = core_.minibuffer_manager().get_prompt();
+        input_part = core_.minibuffer_manager().get_input_buffer();
+        minibuffer_text = prompt_part + input_part;
+    } else if (!core_.last_message().empty()) {
+        minibuffer_text = core_.last_message();
+    }
+    
+    // Render minibuffer text
+    if (!minibuffer_text.empty()) {
+        cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+        layout->set_text(minibuffer_text);
+        
+        // Apply face attributes for prompt if active
+        if (core_.minibuffer_manager().is_active()) {
+            Pango::AttrList attr_list;
+            if (auto face = theme->get_face("minibuffer-prompt")) {
+                apply_face_attributes(attr_list, *face, 0, static_cast<int>(prompt_part.length()));
+            }
+            layout->set_attributes(attr_list);
+        }
+
+        cr->move_to(minibuffer_x, minibuffer_y);
+        layout->show_in_cairo_context(cr);
+    }
+    
+    // Render minibuffer cursor if active and visible (assuming cursor_visible is managed by GtkEditor)
+    bool cursor_visible_ = true; // Placeholder
+    if (core_.minibuffer_manager().is_active() && cursor_visible_) {
+        // Calculate cursor position in minibuffer
+        layout->set_text(minibuffer_text); // Measure full text
+        Pango::Rectangle ink_rect, logical_rect;
+        layout->get_pixel_extents(ink_rect, logical_rect);
+        double cursor_x = minibuffer_x + logical_rect.get_width();
+        
+        // Draw minibuffer cursor
+        cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+        cr->rectangle(cursor_x, minibuffer_y, 2.0, line_height_);
+        cr->fill();
+
+        // Render completion overlay if applicable
+        auto current_completion = core_.minibuffer_manager().get_current_completion();
+        if (current_completion && input_part != *current_completion) {
+            std::string completion_suffix = current_completion->substr(input_part.length());
+            if (!completion_suffix.empty()) {
+                auto completion_layout = Pango::Layout::create(cr);
+                completion_layout->set_font_description(font_desc_);
+                completion_layout->set_text(minibuffer_text + completion_suffix);
+                
+                Pango::AttrList completion_attr_list;
+                if (auto face = theme->get_face("minibuffer-completion")) { // Assuming a face for completion
+                    apply_face_attributes(completion_attr_list, *face, static_cast<int>(minibuffer_text.length()), static_cast<int>(completion_suffix.length()));
+                } else {
+                    // Fallback: dimmed foreground
+                    Color dim_fg = fg;
+                    dim_fg.r = static_cast<unsigned char>(dim_fg.r * 0.7);
+                    dim_fg.g = static_cast<unsigned char>(dim_fg.g * 0.7);
+                    dim_fg.b = static_cast<unsigned char>(dim_fg.b * 0.7);
+                    auto attr = Pango::Attribute::create_attr_foreground(dim_fg.r * 257, dim_fg.g * 257, dim_fg.b * 257);
+                    attr.set_start_index(static_cast<int>(minibuffer_text.length()));
+                    attr.set_end_index(static_cast<int>(minibuffer_text.length() + completion_suffix.length()));
+                    completion_attr_list.insert(attr);
+                }
+                completion_layout->set_attributes(completion_attr_list);
+
+                cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+                cr->move_to(minibuffer_x, minibuffer_y);
+                completion_layout->show_in_cairo_context(cr);
+            }
+        }
+    }
+}
+
+} // namespace lumacs