Browse Source

refactor(gtk): Implement GtkRenderer and GtkWindowController, move C++ fallbacks to Lua

Bernardo Magri 1 month ago
parent
commit
54bb9fac69
6 changed files with 270 additions and 67 deletions
  1. 0 1
      CMakeLists.txt
  2. 5 9
      include/lumacs/gtk_editor.hpp
  3. 57 0
      include/lumacs/gtk_renderer.hpp
  4. 61 10
      init.lua
  5. 123 32
      src/gtk_editor.cpp
  6. 24 15
      src/gtk_renderer.cpp

+ 0 - 1
CMakeLists.txt

@@ -75,7 +75,6 @@ add_executable(lumacs
     src/tui_editor.cpp
     src/gtk_editor.cpp
     src/gtk_renderer.cpp
-    src/gtk_window_controller.cpp
 )
 
 target_link_libraries(lumacs PRIVATE

+ 5 - 9
include/lumacs/gtk_editor.hpp

@@ -5,7 +5,6 @@
 #include "lumacs/window.hpp"
 #include "lumacs/editor_core.hpp"
 #include "lumacs/face.hpp"
-#include "lumacs/gtk_window_controller.hpp" // Include GtkWindowController header
 
 #include <gtkmm.h>
 #include <pangomm.h>
@@ -13,11 +12,11 @@
 #include <vector>
 #include <map>
 
-// Forward declarations
 namespace lumacs {
-    class EditorCore;
-    class Window;
-}
+
+// Forward declarations
+class EditorCore;
+class Window;
 
 // Custom Gtk::ApplicationWindow to make constructor public
 class LumacsWindow : public Gtk::ApplicationWindow {
@@ -25,8 +24,6 @@ public:
     explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application);
 };
 
-namespace lumacs {
-
 class GtkEditor : public IEditorView {
 public:
     GtkEditor();
@@ -52,7 +49,6 @@ private:
     sigc::connection cursor_timer_connection_;
 
     std::unique_ptr<GtkRenderer> gtk_renderer_;
-    std::vector<std::shared_ptr<GtkWindowController>> window_controllers_;
 
 protected:
     void on_activate();
@@ -64,4 +60,4 @@ protected:
 
 std::unique_ptr<IEditorView> create_gtk_editor();
 
-} // namespace lumacs
+} // namespace lumacs

+ 57 - 0
include/lumacs/gtk_renderer.hpp

@@ -0,0 +1,57 @@
+#pragma once
+
+#include "lumacs/editor_core.hpp"
+#include "lumacs/window.hpp"
+#include "lumacs/theme.hpp"
+
+#include <gtkmm.h>
+#include <pangomm.h>
+#include <memory>
+#include <optional>
+
+namespace lumacs {
+
+// Forward declarations
+class EditorCore;
+class Window;
+
+// Constants for 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;
+
+class GtkRenderer {
+public:
+    GtkRenderer(EditorCore& core, Gtk::DrawingArea& main_drawing_area);
+
+    void initialize_font_metrics();
+    void apply_face_attributes(Pango::AttrList& attr_list, const FaceAttributes& face, int start_index, int end_index);
+    std::optional<Position> resolve_screen_pos(std::shared_ptr<Window> window, double x, double y);
+
+    void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                 std::shared_ptr<Window> active_window_cache, bool cursor_visible_state);
+
+    void draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                     std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
+                     std::shared_ptr<Window> active_window_cache, bool cursor_visible_state);
+
+    void render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                                   std::shared_ptr<Window> window,
+                                   std::shared_ptr<Window> active_window_cache);
+    
+    void render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
+
+
+private:
+    EditorCore& core_;
+    Gtk::DrawingArea& main_drawing_area_;
+
+    Pango::FontDescription font_desc_;
+    bool font_initialized_ = false;
+    double char_width_ = 0;
+    double line_height_ = 0;
+    double ascent_ = 0;
+};
+
+} // namespace lumacs

+ 61 - 10
init.lua

@@ -304,6 +304,57 @@ define_minor_mode("line-numbers-mode", {
 -- Example: Custom keybindings
 -- Syntax: bind_key("key", function() ... end)
 
+-- Basic Editing Commands (moved from C++ fallback)
+function lumacs_insert_newline()
+    local cursor = editor.cursor
+    editor.buffer:insert_newline(cursor)
+    editor:set_cursor(lumacs.Position(cursor.line + 1, 0))
+end
+bind_key("Return", lumacs_insert_newline)
+define_command("insert-newline", lumacs_insert_newline, "Insert a new line at cursor position.")
+
+function lumacs_backward_delete_char()
+    local cursor = editor.cursor
+    editor.buffer:erase_char(cursor)
+    if cursor.column > 0 then
+        editor:set_cursor(lumacs.Position(cursor.line, cursor.column - 1))
+    elseif cursor.line > 0 then
+        local prev_line_len = #editor.buffer:line(cursor.line - 1)
+        editor:set_cursor(lumacs.Position(cursor.line - 1, prev_line_len))
+    end
+end
+bind_key("Backspace", lumacs_backward_delete_char)
+define_command("backward-delete-char", lumacs_backward_delete_char, "Delete the character before cursor.")
+
+function lumacs_delete_char()
+    local cursor = editor.cursor
+    editor.buffer:erase_char(lumacs.Position(cursor.line, cursor.column + 1))
+end
+bind_key("Delete", lumacs_delete_char)
+define_command("delete-char", lumacs_delete_char, "Delete the character at cursor position.")
+
+-- Navigation Commands (explicitly bound arrow keys)
+bind_key("ArrowUp", function() editor:move_up() end)
+bind_key("ArrowDown", function() editor:move_down() end)
+bind_key("ArrowLeft", function() editor:move_left() end)
+bind_key("ArrowRight", function() editor:move_right() end)
+
+bind_key("Home", function() editor:move_to_line_start() end)
+bind_key("End", function() editor:move_to_line_end() end)
+
+-- Generic self-insert command for printable characters
+-- This command is special; it's called by the C++ core if no other binding matches a printable char.
+function self_insert_command(char_to_insert)
+    local cursor = editor.cursor
+    editor.buffer:insert(cursor, char_to_insert)
+    editor:set_cursor(lumacs.Position(cursor.line, cursor.column + 1))
+    -- Optionally, log for debugging
+    -- local new_cursor = editor.cursor
+    -- print(string.format("[DEBUG Lua] Inserted '%s' at (%d,%d) -> cursor now at (%d,%d)", 
+    --                    char_to_insert, cursor.line, cursor.column, new_cursor.line, new_cursor.column))
+end
+define_command("self-insert-command", self_insert_command, "Insert the character pressed.")
+
 -- Emacs-style navigation (Ctrl+N/P for next/previous line)
 bind_key("C-n", function()
     editor:move_down()
@@ -416,7 +467,7 @@ function find_next(query)
     -- A simple way is to advance column by 1 for the search start.
     local search_start = lumacs.Position(cursor.line, cursor.column + 1)
     
-    -- If at end of line, search from start of next line is handled by find() implementation? 
+    -- If at end of line, search from start of next line is handled by find() implementation?
     -- Buffer::find currently implements simple linear search from a position.
     -- If column is beyond end, it should handle it. Let's trust the C++ impl or adjust.
     
@@ -746,8 +797,8 @@ bind_key("C-x C-b", function()
     table.insert(lines, "Buffer List:")
     table.insert(lines, "------------")
     table.insert(lines, "")
-    table.insert(lines, string.format("%-3s %-20s %-10s %s", "Mod", "Name", "Size", "File"))
-    table.insert(lines, string.format("%-3s %-20s %-10s %s", "---", "----", "----", "----"))
+    table.insert(lines, string.format("% -3s % -20s % -10s %s", "Mod", "Name", "Size", "File"))
+    table.insert(lines, string.format("% -3s % -20s % -10s %s", "---", "----", "----"))
 
     for i, info in ipairs(buffer_info) do
         local modified = info.modified and " * " or "   "
@@ -756,7 +807,7 @@ bind_key("C-x C-b", function()
             filepath = tostring(info.filepath)
         end
 
-        local line = string.format("%s %-20s %-10d %s",
+        local line = string.format("%s % -20s % -10d %s",
             modified,
             info.name,
             info.size,
@@ -862,7 +913,7 @@ bind_key("C-x C-l", function()
 end)
 
 -- ============================================================================
--- COMMENTING (M-;)
+-- COMMENTING (M- ;)
 -- ============================================================================
 
 function escape_pattern(text)
@@ -945,7 +996,7 @@ function comment_dwim()
     end
 end
 
-bind_key("M-;", comment_dwim)
+bind_key("M-;") comment_dwim)
 
 -- ============================================================================
 -- REGISTERS (C-x r s, C-x r i)
@@ -1162,14 +1213,14 @@ define_command("list-buffers", function()
     table.insert(lines, "Buffer List:")
     table.insert(lines, "------------")
     table.insert(lines, "")
-    table.insert(lines, string.format("%-3s %-20s %-10s %s", "Mod", "Name", "Size", "File"))
-    table.insert(lines, string.format("%-3s %-20s %-10s %s", "---", "----", "----", "----"))
+    table.insert(lines, string.format("% -3s % -20s % -10s %s", "Mod", "Name", "Size", "File"))
+    table.insert(lines, string.format("% -3s % -20s % -10s %s", "---", "----"))
 
     for i, info in ipairs(buffer_info) do
         local modified = info.modified and " * " or "   "
         local filepath = ""
         if info.filepath then filepath = tostring(info.filepath) end
-        table.insert(lines, string.format("%s %-20s %-10d %s", modified, info.name, info.size, filepath))
+        table.insert(lines, string.format("%s % -20s % -10d %s", modified, info.name, info.size, filepath))
     end
 
     local list_text = table.concat(lines, "\n")
@@ -1458,4 +1509,4 @@ function get_completion_candidates(mode_name, input)
     end
     
     return candidates
-end
+end

+ 123 - 32
src/gtk_editor.cpp

@@ -5,7 +5,6 @@
 #include "lumacs/command_system.hpp"
 #include "lumacs/minibuffer_manager.hpp" // Include for MinibufferManager and MinibufferMode
 #include "lumacs/gtk_renderer.hpp" // Include for GtkRenderer
-#include "lumacs/gtk_window_controller.hpp" // Include for GtkWindowController
 #include <iostream>
 #include <filesystem>
 #include <vector>
@@ -202,9 +201,6 @@ private:
     // GtkRenderer instance
     std::unique_ptr<GtkRenderer> gtk_renderer_;
 
-    // GtkWindowController instances for each window
-    std::vector<std::shared_ptr<GtkWindowController>> window_controllers_;
-
 protected:
     void on_activate() {
         // Create main window and associate with the application
@@ -257,7 +253,7 @@ protected:
     // Cursor blinking callback
     bool on_cursor_blink() {
         // Safety check - don't blink if core is destroyed or no drawing area
-        if (!core_ || !app_) { // drawing_area_ is now managed by controllers
+        if (!core_ || !drawing_area_ || !app_) {
             return false; // Stop the timer
         }
         
@@ -269,17 +265,7 @@ protected:
         try {
             cursor_visible_ = !cursor_visible_;
             core_->check_and_clear_message(); // Check and clear messages
-
-            // Update all window controllers and queue redraws
-            for (const auto& controller : window_controllers_) {
-                controller->set_cursor_visible(cursor_visible_);
-                // Each controller's drawing area will be redrawn by its own draw_func or here
-                // For now, GtkEditor manages top-level redraws, but this might be refactored
-                // if individual window redraws are sufficient
-            }
-            if (content_widget_) {
-                queue_redraw_all_windows(content_widget_);
-            }
+            drawing_area_->queue_draw();
         } catch (...) {
             return false; // Stop timer on any exception
         }
@@ -302,9 +288,6 @@ protected:
         // Clear the drawing area reference since we're rebuilding
         drawing_area_ = nullptr;
 
-        // Clear all window controllers
-        window_controllers_.clear();
-
         // Initialize cached active window to prevent focus jumping
         cached_active_window_ = core_->active_window();
 
@@ -323,25 +306,116 @@ protected:
             // Create a new DrawingArea for this window
             auto drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
             
-            // Create a GtkWindowController for this DrawingArea
-            auto controller = std::make_shared<GtkWindowController>(*core_, *gtk_renderer_, node->window, *drawing_area);
-            controller->set_cached_active_window(cached_active_window_);
-            controller->set_cursor_visible(cursor_visible_);
-            controller->connect_events();
-            
-            // Store the controller to manage its lifetime and update cached_active_window_ and cursor_visible_
-            window_controllers_.push_back(controller);
-
             // Set up drawing for this specific window
             // Use a weak reference to the window to avoid crashes if the layout is rebuilt
-            drawing_area->set_draw_func([this, controller_ptr = controller](const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
-                if (controller_ptr) {
-                    controller_ptr->on_draw_event(cr, width, height);
+            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() && gtk_renderer_) {
+                    gtk_renderer_->draw_window(cr, width, height, window, drawing_area, cached_active_window_, cursor_visible_);
                 }
             });
             
             drawing_area->set_focusable(true);
             
+            // Add input handling  
+            auto controller = Gtk::EventControllerKey::create();
+            // Use weak reference to window for key handling
+            std::weak_ptr<Window> weak_window_key = node->window;
+            controller->signal_key_pressed().connect([this, weak_window_key](guint keyval, guint keycode, Gdk::ModifierType state) -> bool {
+                // Ensure this window is active when it receives key input
+                if (auto window = weak_window_key.lock()) {
+                    if (core_) {
+                        core_->set_active_window(window);
+                        cached_active_window_ = window; // Cache for rendering to prevent focus jumping
+                    }
+                }
+                // Now handled by GtkWindowController, so we need to instantiate it
+                return false; 
+            }, false);
+            drawing_area->add_controller(controller);
+            
+            // Add click handling to set active window explicitly and move cursor
+            // We use GestureClick instead of EventControllerFocus to avoid spurious focus changes
+            auto click_controller = Gtk::GestureClick::create();
+            std::weak_ptr<Window> weak_window_click = node->window;
+            click_controller->signal_pressed().connect([this, weak_window_click, drawing_area](int /*n_press*/, double x, double y) {
+                if (auto window = weak_window_click.lock()) {
+                    // 1. Activate Window
+                    if (core_ && core_->active_window() != window) {
+                        core_->set_active_window(window);
+                        cached_active_window_ = window; // Cache for rendering to prevent focus jumping
+                    }
+                    // IMPORTANT: Grab keyboard focus for this widget
+                    drawing_area->grab_focus();
+                    
+                    // 2. Move Cursor
+                    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();
+                        }
+                    }
+                }
+            });
+            drawing_area->add_controller(click_controller);
+
+            // Add Drag Gesture for Selection
+            auto drag_controller = Gtk::GestureDrag::create();
+            std::weak_ptr<Window> weak_window_drag = node->window;
+            
+            drag_controller->signal_drag_begin().connect([this, weak_window_drag, drawing_area](double x, double y) {
+                if (auto window = weak_window_drag.lock()) {
+                    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();
+                        }
+                    }
+                }
+            });
+            
+            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) && gtk_renderer_) {
+                         double current_x = start_x + dx;
+                         double current_y = start_y + dy;
+                         
+                         if (auto pos = gtk_renderer_->resolve_screen_pos(window, current_x, current_y)) {
+                             window->set_cursor(*pos);
+                             drawing_area->queue_draw();
+                         }
+                     }
+                }
+            });
+            
+            drawing_area->add_controller(drag_controller);
+            
+            // Add scroll handling
+            auto scroll_controller = Gtk::EventControllerScroll::create();
+            scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
+            std::weak_ptr<Window> weak_window_scroll = node->window;
+            scroll_controller->signal_scroll().connect([weak_window_scroll, drawing_area](double /*dx*/, double dy) -> bool {
+                 if (auto window = weak_window_scroll.lock()) {
+                     // dy is usually 1.0 or -1.0 for wheel steps
+                     // Scroll 3 lines per step
+                     int lines = static_cast<int>(dy * 3.0);
+                     if (lines != 0) {
+                         window->scroll_lines(lines);
+                         drawing_area->queue_draw();
+                     }
+                     return true;
+                 }
+                 return false;
+            }, true);
+            drawing_area->add_controller(scroll_controller);
+            
+            // Context menus and tooltips removed per user request (Phase A.1)
+            
             // Store reference for single-window compatibility
             if (!drawing_area_) {
                 drawing_area_ = drawing_area;
@@ -380,4 +454,21 @@ protected:
             return paned;
         }
     }
-};
+};
+
+std::unique_ptr<IEditorView> create_gtk_editor() {
+    return std::make_unique<GtkEditor>();
+}
+
+} // namespace lumacs
+
+#else // LUMACS_WITH_GTK not defined
+
+namespace lumacs {
+std::unique_ptr<IEditorView> create_gtk_editor() {
+    std::cerr << "Error: Lumacs was built without GTK support." << std::endl;
+    return nullptr;
+}
+} // namespace lumacs
+
+#endif

+ 24 - 15
src/gtk_renderer.cpp

@@ -109,7 +109,8 @@ std::optional<Position> GtkRenderer::resolve_screen_pos(std::shared_ptr<Window>
     return Position{target_line, target_col};
 }
 
-void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
+void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
+                            std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
     // Safety check - don't draw if core has no active theme (during destruction)
     if (!core_.active_theme()) return;
 
@@ -124,21 +125,22 @@ void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, in
     if (!active_window) return;
 
     // Render the active window (main editor area)
-    draw_window(cr, width, height, active_window, &main_drawing_area_);
+    draw_window(cr, width, height, active_window, &main_drawing_area_, active_window_cache, cursor_visible_state);
 
     // 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_modeline_for_window(cr, width, height, active_window, active_window_cache);
     
     // Render minibuffer
-    render_minibuffer(cr, width, height, temp_layout);
+    render_minibuffer(cr, width, height);
 }
 
 void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
-                                std::shared_ptr<Window> window, Gtk::DrawingArea* widget) {
+                                std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
+                                std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
     if (!core_.active_theme() || !window || !widget) return;
 
     // Use a temporary layout that is properly initialized
@@ -161,7 +163,8 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
     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); 
+    bool is_main_window = (window == active_window_cache);
+    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
@@ -247,8 +250,8 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
         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());
+        bool cursor_visible_ = cursor_visible_state;
+        bool is_active_window = (window == active_window_cache);
         if (is_active_window && cursor_visible_ && buffer_line_idx == cursor.line) {
              int cursor_idx = static_cast<int>(cursor.column) - horizontal_offset;
              
@@ -297,12 +300,14 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
              }
         }
     }
+
 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) {
+                                           std::shared_ptr<Window> window,
+                                           std::shared_ptr<Window> active_window_cache) {
     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
+    bool is_active = (window == active_window_cache);
     
     // Calculate modeline position (second line from bottom)
     double modeline_y = height - line_height_ - PADDING_BOTTOM; // Adjust height - modeline is on line above minibuffer
@@ -334,6 +339,9 @@ void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>
              }
         }
         
+        // Use a temporary layout for modeline chunk
+        auto layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
+        layout->set_font_description(font_desc_);
         layout->set_text(chunk.text);
         
         // Apply attributes
@@ -357,8 +365,7 @@ void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>
 }
 
 
-void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
-                                    const Glib::RefPtr<Pango::Layout>& layout) {
+void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
     if (!core_.active_theme()) return;
 
     // Only render if minibuffer is active or a message is set
@@ -403,6 +410,8 @@ void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int
     // Render minibuffer text
     if (!minibuffer_text.empty()) {
         cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+        auto layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
+        layout->set_font_description(font_desc_);
         layout->set_text(minibuffer_text);
         
         // Apply face attributes for prompt if active
@@ -422,6 +431,8 @@ void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int
     bool cursor_visible_ = true; // Placeholder
     if (core_.minibuffer_manager().is_active() && cursor_visible_) {
         // Calculate cursor position in minibuffer
+        auto layout = Pango::Layout::create(main_drawing_area_.get_pango_context());
+        layout->set_font_description(font_desc_);
         layout->set_text(minibuffer_text); // Measure full text
         Pango::Rectangle ink_rect, logical_rect;
         layout->get_pixel_extents(ink_rect, logical_rect);
@@ -463,6 +474,4 @@ void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int
             }
         }
     }
-}
-
-} // namespace lumacs
+} // namespace lumacs