Procházet zdrojové kódy

feat(minibuffer): Implement Advanced Completion UI

This commit integrates an advanced completion UI into the GTK frontend.

- **Add GtkCompletionPopup:** Introduced a new `GtkCompletionPopup` widget to display completion candidates with descriptions, handling navigation and selection.
- **Refactor GtkEditor key handling:** Centralized minibuffer key event processing in `GtkEditor::on_global_key_pressed`, delegating to the completion popup when visible.
- **MinibufferManager improvements:** Added `set_input_buffer` to allow direct setting of minibuffer content, crucial for completion selection.
- **GtkRenderer updates:** Implemented `get_minibuffer_coords` to provide accurate positioning for the completion popup.
- **LuaApi Refinement:** Removed specific `complete_*` bindings, introduced generic `get_completion_candidates`, and exposed `MinibufferMode` enum for robust completion integration.
- **CommandSystem enhancements:** Implemented asynchronous interactive argument gathering via `execute_interactive` and `process_next_interactive_argument`.
- **CommandContext decoupling:** Modified `CommandContext` to depend on `ICommandTarget` interface instead of concrete `EditorCore` for improved modularity and testability.
- **PLAN.md update:** Updated `documentation/PLAN.md` to reflect completion of Phase C (Command System Refactoring) and "Advanced Completion UI" item in the roadmap.
Bernardo Magri před 1 měsícem
rodič
revize
30d3faad9d

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 315
documentation/PLAN.md


+ 65 - 8
include/lumacs/command_system.hpp

@@ -7,21 +7,61 @@
 #include <optional>
 #include <variant>
 
+#include "lumacs/i_command_target.hpp" // New include for ICommandTarget
+
 namespace lumacs {
 
-// Forward declaration for EditorCore, as CommandSystem will interact with it
-class EditorCore;
+// class EditorCore; // No longer needed directly by CommandContext
+class MinibufferManager; // Forward declare MinibufferManager for interactive args
+
+/// @brief Defines the status of a command execution.
+enum class CommandStatus {
+    Success,
+    Failure,
+    PendingInput // Indicates the command is waiting for user input
+};
 
 /// @brief Represents the result of a command execution.
 struct CommandResult {
-    bool success;
+    CommandStatus status;
     std::string message;
     // Potentially add more fields, like return value, error code, etc.
 };
 
+/// @brief Context object passed to command functions for argument access and interaction.
+class CommandContext {
+public:
+    CommandContext(ICommandTarget& target, MinibufferManager& minibuffer_manager, const std::vector<std::string>& args)
+        : target_(target), minibuffer_manager_(minibuffer_manager), args_(args) {}
+
+    /// @brief Get argument at index as string.
+    std::optional<std::string> get_string_arg(size_t index) const;
+
+    /// @brief Get argument at index as integer.
+    std::optional<int> get_int_arg(size_t index) const;
+
+    // TODO: Add more typed accessors as needed (bool, double, etc.)
+
+    /// @brief Prompts the user for a string argument.
+    std::optional<std::string> read_string(const std::string& prompt);
+
+    /// @brief Prompts the user for a file path.
+    std::optional<std::string> read_file_path(const std::string& prompt);
+
+    // Access to core components for command logic
+    ICommandTarget& target() { return target_; }
+
+public: // Made public for easier access from LuaApi binding
+    std::vector<std::string> args_;
+
+private:
+    ICommandTarget& target_; // Changed from EditorCore& core_
+    MinibufferManager& minibuffer_manager_;
+};
+
 /// @brief Type for command functions.
-/// Commands take a vector of string arguments and return a CommandResult.
-using CommandFunction = std::function<CommandResult(const std::vector<std::string>&)>;
+/// Commands take a CommandContext reference and return a CommandResult.
+using CommandFunction = std::function<CommandResult(CommandContext&)>;
 
 /// @brief Represents a single command.
 struct Command {
@@ -38,11 +78,11 @@ struct Command {
 /// @brief Manages the registration and execution of editor commands.
 class CommandSystem {
 public:
-    explicit CommandSystem(EditorCore* core = nullptr);
+    explicit CommandSystem(EditorCore& core, MinibufferManager& minibuffer_manager);
 
     /// @brief Registers a command with the system.
     void register_command(const std::string& name, CommandFunction function,
-                          const std::string& doc_string = "", bool interactive = false);
+                          const std::string& doc_string = "", bool interactive = false, std::string interactive_spec = "");
 
     /// @brief Executes a command by name with given arguments.
     /// @param name The name of the command to execute.
@@ -56,9 +96,26 @@ public:
     /// @brief Get documentation for a specific command.
     std::optional<std::string> get_command_doc_string(const std::string& name) const;
 
+    /// @brief Get the interactive spec for a specific command.
+    std::optional<std::string> get_command_interactive_spec(const std::string& name) const;
+
+
+    /// @brief Executes a command interactively, prompting the user for arguments if needed.
+    /// @param name The name of the command to execute.
+    /// @return CommandResult indicating success/failure and a message, or a pending state.
+    CommandResult execute_interactive(const std::string& name);
+
 private:
-    EditorCore* core_; // Pointer to EditorCore for commands to interact with editor state
+    EditorCore& core_; // Reference to EditorCore
+    MinibufferManager& minibuffer_manager_; // Reference to MinibufferManager
     std::unordered_map<std::string, Command> commands_;
+
+    // State for interactive command execution
+    std::optional<Command> current_interactive_command_;
+    size_t current_interactive_spec_index_ = 0;
+    std::vector<std::string> gathered_interactive_args_;
+    // A shared_ptr to CommandContext might be needed if CommandContext becomes complex or shared.
+    // For now, it might be simpler to reconstruct it or pass args directly.
 };
 
 } // namespace lumacs

+ 2 - 0
include/lumacs/completion_system.hpp

@@ -1,3 +1,5 @@
+#pragma once
+
 #include <string>
 #include <vector>
 #include <memory>

+ 84 - 157
include/lumacs/editor_core.hpp

@@ -7,8 +7,10 @@
 #include "lumacs/config.hpp"
 #include "lumacs/keybinding.hpp"
 #include "lumacs/modeline.hpp"
-// #include "lumacs/minibuffer_manager.hpp" // Changed to forward declaration below
+#include "lumacs/minibuffer_manager.hpp" // Added include
+#include "lumacs/completion_system.hpp"   // Added include
 #include "lumacs/ui_interface.hpp" // Include for EditorEvent
+#include "lumacs/i_command_target.hpp" // New include for ICommandTarget
 #include <memory>
 #include <functional>
 #include <vector>
@@ -21,8 +23,6 @@ namespace lumacs {
 
 class LuaApi; // Forward declaration
 class CommandSystem; // Forward declaration
-class CompletionSystem; // Forward declaration
-class MinibufferManager; // Forward declaration
 
 /// @brief Represents a node in the window layout tree.
 struct LayoutNode {
@@ -43,12 +43,12 @@ struct LayoutNode {
 };
 
 /// @brief Core logic of the Lumacs editor, independent of the UI framework.
-/// 
+///
 /// This class acts as the central controller/facade for the editor's logic.
 /// It manages buffers, windows, the kill ring, registers, macros, configuration,
 /// and subsystems like the command system and Lua API.
 /// It emits events to notify the UI (IEditorView) of state changes.
-class EditorCore {
+class EditorCore : public ICommandTarget { // Inherit from ICommandTarget
 public:
     EditorCore();
     ~EditorCore();
@@ -56,74 +56,89 @@ public:
     // Disable copy, allow move
     EditorCore(const EditorCore&) = delete;
     EditorCore& operator=(const EditorCore&) = delete;
-    EditorCore(EditorCore&&) noexcept = default;
-    EditorCore& operator=(EditorCore&&) noexcept = default;
-
-    // === Message System ===
-
-    /// @brief Display a message to the user (usually in the minibuffer).
-    /// @param msg The message string to display.
-    void set_message(std::string msg) {
-        last_message_ = std::move(msg);
-        message_clear_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(5); // Clear after 5 seconds
-        emit_event(EditorEvent::Message); // Use this for transient messages
-    }
+    EditorCore(EditorCore&&) noexcept = default; // Should now be fine due to unique_ptr members
+    EditorCore& operator=(EditorCore&&) noexcept = default; // Should now be fine due to unique_ptr members
+
+    // === ICommandTarget Implementation ===
+    // These methods implement the ICommandTarget interface.
+
+    // Buffer Management
+    [[nodiscard]] const Buffer& buffer() const noexcept override;
+    [[nodiscard]] Buffer& buffer() noexcept override;
+    bool load_file(const std::filesystem::path& path) override;
+    void new_buffer(std::string name = "*scratch*") override;
+    [[nodiscard]] std::vector<std::string> get_buffer_names() const override;
+    [[nodiscard]] std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name) override;
+    bool switch_buffer_in_window(const std::string& name) override;
+    bool close_buffer(const std::string& name) override;
+    
+    // Window Management
+    [[nodiscard]] std::shared_ptr<Window> active_window() const override { return active_window_; }
+    bool set_active_window(std::shared_ptr<Window> window) override;
+    void split_horizontally() override;
+    void split_vertically() override;
+    void close_active_window() override;
+    void next_window() override; 
+    
+    // Cursor Management
+    [[nodiscard]] Position cursor() const noexcept override;
+    void set_cursor(Position pos) override;
+
+    // Basic Editing
+    void move_up() override;
+    void move_down() override;
+    void move_left() override;
+    void move_right() override;
+    void move_to_line_start() override;
+    void move_to_line_end() override;
+    void move_forward_word() override;
+    void move_backward_word() override;
+    void page_up() override;
+    void page_down() override;
+    void goto_beginning() override;
+    void goto_end() override;
+    void goto_line(size_t line) override;
+    void kill_line() override;
+    void kill_region() override;
+    void copy_region_as_kill() override;
+    void yank() override;
+    void yank_pop() override;
+    void kill_word() override;
+    void backward_kill_word() override;
+
+    // Message Display
+    void set_message(std::string msg) override;
+
+    // Quit
+    void request_quit() override;
+
+    // Configuration
+    Config& config() override { return config_; }
+    const Config& config() const override { return config_; }
+
+    // Theme Management
+    ThemeManager& theme_manager() override { return theme_manager_; }
+    const ThemeManager& theme_manager() const override { return theme_manager_; }
+
+    // === Original EditorCore methods not part of ICommandTarget ===
 
     /// @brief Get the last set message.
     const std::string& last_message() const { return last_message_; }
 
     /// @brief Check if a message is set and if its display time has expired.
     /// Clears the message if expired.
-    void check_and_clear_message() {
-        if (message_clear_time_.has_value() && std::chrono::steady_clock::now() > message_clear_time_.value()) {
-            last_message_.clear();
-            message_clear_time_.reset();
-            emit_event(EditorEvent::TransientMessageCleared); // Notify UI
-        }
-    }
+    void check_and_clear_message(); 
 
     // === Actions ===
     // These methods trigger specific input modes in the UI.
 
-    void enter_command_mode() { emit_event(EditorEvent::CommandMode); }
-    void enter_buffer_switch_mode() { emit_event(EditorEvent::BufferSwitchMode); }
-    void enter_kill_buffer_mode() { emit_event(EditorEvent::KillBufferMode); }
-    void enter_find_file_mode() { emit_event(EditorEvent::FindFileMode); }
-    void enter_theme_selection_mode() { emit_event(EditorEvent::ThemeSelectionMode); }
-    void enter_isearch_mode() { emit_event(EditorEvent::ISearchMode); }
-    void enter_isearch_backward_mode() { emit_event(EditorEvent::ISearchBackwardMode); }
-
-    // === Buffer Management ===
-
-    /// @brief Get the active buffer (associated with the active window).
-    [[nodiscard]] const Buffer& buffer() const noexcept;
-    [[nodiscard]] Buffer& buffer() noexcept;
-
-    /// @brief Load a file into a buffer and display it in the active window.
-    /// @param path Filesystem path to load.
-    /// @return true if successful, false otherwise.
-    bool load_file(const std::filesystem::path& path);
-
-    /// @brief Create a new empty buffer (e.g., *scratch*) and display it.
-    /// @param name The name of the new buffer.
-    void new_buffer(std::string name = "*scratch*");
-
-    /// @brief Get a list of names of all open buffers.
-    [[nodiscard]] std::vector<std::string> get_buffer_names() const;
-
-    /// @brief Find a buffer by its name.
-    /// @return Shared pointer to the buffer, or nullptr if not found.
-    [[nodiscard]] std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name);
-
-    /// @brief Switch the active window to display the specified buffer.
-    /// @param name The name of the buffer to switch to.
-    /// @return true if successful.
-    bool switch_buffer_in_window(const std::string& name);
-
-    /// @brief Close (kill) a buffer.
-    /// @param name The name of the buffer to close.
-    /// @return true if closed, false if it doesn't exist or is the last buffer.
-    bool close_buffer(const std::string& name);
+    void enter_command_mode();
+    void enter_buffer_switch_mode();
+    void enter_kill_buffer_mode();
+    void enter_find_file_mode();
+    void enter_theme_selection_mode();
+    void enter_isearch_mode();
+    void enter_isearch_backward_mode();
 
     /// @brief Structure containing summary information about a buffer.
     struct BufferInfo {
@@ -137,53 +152,9 @@ public:
     /// @brief Get detailed information about all buffers.
     [[nodiscard]] std::vector<BufferInfo> get_all_buffer_info() const;
 
-    // === Window Management ===
-
-    /// @brief Split the active window horizontally (active becomes top).
-    void split_horizontally();
-
-    /// @brief Split the active window vertically (active becomes left).
-    void split_vertically();
-
-    /// @brief Close the active window and remove it from the layout tree.
-    void close_active_window();
-
-    /// @brief Move focus to the next window in the tree traversal order.
-    void next_window();
-    
-    /// @brief Safe version of next_window (deprecated, alias for next_window).
-    void next_window_safe();
-
-    /// @brief Get the currently focused window.
-    std::shared_ptr<Window> active_window() const { return active_window_; }
-    
-    /// @brief Set the active window explicitly (must exist in the tree).
-    bool set_active_window(std::shared_ptr<Window> window);
-
     /// @brief Get the root node of the window layout tree.
     std::shared_ptr<LayoutNode> root_layout() const { return root_node_; }
 
-    // === Cursor Management (Proxies to active window) ===
-
-    [[nodiscard]] Position cursor() const noexcept;
-    void set_cursor(Position pos);
-
-    // === Cursor Movement (Proxies to active window) ===
-
-    void move_up();
-    void move_down();
-    void move_left();
-    void move_right();
-    void move_to_line_start();
-    void move_to_line_end();
-    void move_forward_word();
-    void move_backward_word();
-    void page_up();
-    void page_down();
-    void goto_beginning();
-    void goto_end();
-    void goto_line(size_t line);
-
     // === Viewport Management (Proxies to active window) ===
     
     const Viewport& viewport() const noexcept;
@@ -205,13 +176,6 @@ public:
         event_callbacks_.clear();
     }
 
-    // === Actions ===
-
-    /// @brief Request the application to quit.
-    void request_quit() {
-        emit_event(EditorEvent::Quit);
-    }
-
     // === Undo/Redo ===
 
     bool undo();
@@ -264,48 +228,9 @@ public:
     /// @brief Replace a rectangular region with a string (C-x r t).
     void string_rectangle(const std::string& text);
 
-    // === Standard Editing Commands ===
-
-    /// @brief Kill text from cursor to end of line (C-k).
-    void kill_line();
-
-    /// @brief Kill the text in the active region (C-w).
-    void kill_region();
-
-    /// @brief Copy the active region to the kill ring (M-w).
-    void copy_region_as_kill();
-
-    /// @brief Yank (paste) from the kill ring (C-y).
-    void yank();
-
-    /// @brief Replace the just-yanked text with the next item in kill ring (M-y).
-    void yank_pop();
-
-    /// @brief Kill word forward (M-d).
-    void kill_word();
-
-    /// @brief Kill word backward (M-Backspace).
-    void backward_kill_word();
-
-    // === Theme Management ===
-
-    [[nodiscard]] ThemeManager& theme_manager() noexcept { return theme_manager_; }
-    [[nodiscard]] const ThemeManager& theme_manager() const noexcept { return theme_manager_; }
-
-    /// @brief Set the active theme by name.
-    void set_theme(const std::string& name) { theme_manager_.set_active_theme(name); }
-
-    /// @brief Get the currently active theme.
-    [[nodiscard]] std::shared_ptr<Theme> active_theme() const { return theme_manager_.active_theme(); }
-
-    // === Configuration ===
-
-    [[nodiscard]] Config& config() noexcept { return config_; }
-    [[nodiscard]] const Config& config() const noexcept { return config_; }
-
     // === Key Binding System ===                                                                                                                                 
-    [[nodiscard]] KeyBindingManager& keybinding_manager() noexcept { return keybinding_manager_; }                                                                
-    [[nodiscard]] const KeyBindingManager& keybinding_manager() const noexcept { return keybinding_manager_; }                                                    
+    [[nodiscard]] KeyBindingManager& keybinding_manager() noexcept { return *keybinding_manager_; }                                                                
+    [[nodiscard]] const KeyBindingManager& keybinding_manager() const noexcept { return *keybinding_manager_; }                                                    
                                                                                                                                                                   
     // === Lua API ===                                                                                                                                            
     [[nodiscard]] LuaApi* lua_api() const { return lua_api_.get(); }
@@ -364,9 +289,11 @@ private:
     ThemeManager theme_manager_;
     Config config_;
     std::unique_ptr<CommandSystem> command_system_; // Must be declared before KeyBindingManager
-    KeyBindingManager keybinding_manager_;
+    std::unique_ptr<KeyBindingManager> keybinding_manager_; // Changed to unique_ptr
     std::unique_ptr<LuaApi> lua_api_;
     ModelineManager modeline_manager_;
+    std::unique_ptr<CompletionSystem> completion_system_;   // Added missing member
+    std::unique_ptr<MinibufferManager> minibuffer_manager_; // Added missing member
 
     void emit_event(EditorEvent event);
     
@@ -377,4 +304,4 @@ private:
     void collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows);
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 66 - 0
include/lumacs/gtk_completion_popup.hpp

@@ -0,0 +1,66 @@
+#pragma once
+
+#ifdef LUMACS_WITH_GTK
+
+#include <gtkmm.h>
+#include <vector>
+#include <string>
+
+#include "lumacs/completion_common.hpp" // For CompletionCandidate
+
+namespace lumacs {
+
+/// @brief A GTK widget that displays a list of completion candidates in a popup.
+class GtkCompletionPopup : public Gtk::Window {
+public:
+    GtkCompletionPopup();
+    ~GtkCompletionPopup() override;
+
+    /// @brief Displays the completion popup with the given candidates.
+    /// @param candidates The list of completion candidates to display.
+    /// @param active_index The index of the currently active candidate.
+    /// @param attach_widget The widget to attach the popup to (e.g., the minibuffer).
+    /// @param x_pos The x-coordinate relative to the attach_widget for positioning.
+    /// @param y_pos The y-coordinate relative to the attach_widget for positioning.
+    void show_popup(const std::vector<CompletionCandidate>& candidates, size_t active_index, Gtk::Widget& attach_widget, int x_pos, int y_pos);
+
+    /// @brief Hides the completion popup.
+    void hide_popup();
+
+    /// @brief Navigates to the next candidate in the list.
+    void select_next();
+
+    /// @brief Navigates to the previous candidate in the list.
+    void select_previous();
+
+    /// @brief Returns the currently selected candidate, if any.
+    std::optional<CompletionCandidate> get_selected_candidate() const;
+
+    /// Signals
+    using type_signal_candidate_selected = sigc::signal<void, CompletionCandidate>;
+    type_signal_candidate_selected signal_candidate_selected();
+
+    using type_signal_cancelled = sigc::signal<void>;
+    type_signal_cancelled signal_cancelled();
+
+protected:
+    // Signal handlers
+    void on_row_activated(Gtk::ListBoxRow* row);
+
+private:
+    Gtk::ScrolledWindow list_scrolled_window_;
+    Gtk::ListBox list_box_;
+    std::vector<CompletionCandidate> candidates_;
+    size_t active_index_;
+
+    type_signal_candidate_selected signal_candidate_selected_;
+    type_signal_cancelled signal_cancelled_;
+
+    // Helper to update the displayed list and selection
+    void update_list();
+    void set_active_row(size_t index);
+};
+
+} // namespace lumacs
+
+#endif // LUMACS_WITH_GTK

+ 15 - 2
include/lumacs/gtk_editor.hpp

@@ -5,6 +5,7 @@
 #include "lumacs/window.hpp"
 #include "lumacs/editor_core.hpp"
 #include "lumacs/face.hpp"
+#include "lumacs/completion_common.hpp" // For CompletionCandidate
 
 #include <gtkmm.h>
 #include <pangomm.h>
@@ -17,6 +18,7 @@ namespace lumacs {
 // Forward declarations
 class EditorCore;
 class Window;
+class GtkCompletionPopup; // Forward declaration
 
 // Custom Gtk::ApplicationWindow to make constructor public
 class LumacsWindow : public Gtk::ApplicationWindow {
@@ -42,13 +44,14 @@ private:
 
     Glib::RefPtr<Gtk::Application> app_;
     Gtk::Window* window_ = nullptr;
-    Gtk::DrawingArea* drawing_area_ = nullptr;
-    Gtk::Widget* content_widget_ = nullptr;
+    Gtk::DrawingArea* drawing_area_ = nullptr; // Raw pointer to the main drawing area if single window
+    Gtk::Widget* content_widget_ = nullptr; // The root widget of the editor content (Paned or DrawingArea)
     
     bool cursor_visible_ = true;
     sigc::connection cursor_timer_connection_;
 
     std::unique_ptr<GtkRenderer> gtk_renderer_;
+    std::unique_ptr<GtkCompletionPopup> completion_popup_; // Completion popup
 
 protected:
     void on_activate();
@@ -56,6 +59,16 @@ protected:
     bool on_cursor_blink();
     void rebuild_layout();
     Gtk::Widget* create_widget_for_layout_node(std::shared_ptr<LayoutNode> node);
+
+    // Completion popup helpers
+    void show_completion_popup();
+    void hide_completion_popup();
+    void on_completion_selected(CompletionCandidate candidate);
+    void on_completion_cancelled();
+
+    // Global key event handler
+    std::string get_lumacs_key_name(guint keyval, Gdk::ModifierType state);
+    bool on_global_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state);
 };
 
 std::unique_ptr<IEditorView> create_gtk_editor();

+ 8 - 0
include/lumacs/gtk_renderer.hpp

@@ -42,6 +42,14 @@ public:
     
     void render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
 
+    /// @brief Gets the screen coordinates and dimensions of the minibuffer area.
+    /// @param x Output parameter for the X coordinate.
+    /// @param y Output parameter for the Y coordinate.
+    /// @param width Output parameter for the width.
+    /// @param height Output parameter for the height.
+    /// @return True if the coordinates could be determined, false otherwise.
+    bool get_minibuffer_coords(int& x, int& y, int& width, int& height) const;
+
 
 private:
     EditorCore& core_;

+ 90 - 0
include/lumacs/i_command_target.hpp

@@ -0,0 +1,90 @@
+#pragma once
+
+#include "lumacs/buffer.hpp"
+#include "lumacs/window.hpp"
+#include "lumacs/config.hpp"
+#include "lumacs/theme.hpp"
+#include <string>
+#include <memory>
+#include <vector>
+#include <optional>
+#include <filesystem>
+
+namespace lumacs {
+
+// Forward declarations
+class LuaApi;
+class KillRing;
+class KeyBindingManager;
+class ModelineManager;
+class MinibufferManager;
+class CommandSystem;
+class CompletionSystem;
+
+/// @brief Interface defining the essential editor services that commands can interact with.
+/// This interface aims to reduce the direct coupling of CommandSystem and individual commands
+/// to the entire EditorCore class, making them more testable and modular.
+class ICommandTarget {
+public:
+    virtual ~ICommandTarget() = default;
+
+    // Buffer Management
+    virtual const Buffer& buffer() const noexcept = 0;
+    virtual Buffer& buffer() noexcept = 0;
+    virtual bool load_file(const std::filesystem::path& path) = 0;
+    virtual void new_buffer(std::string name = "*scratch*") = 0;
+    virtual std::vector<std::string> get_buffer_names() const = 0;
+    virtual std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name) = 0;
+    virtual bool switch_buffer_in_window(const std::string& name) = 0;
+    virtual bool close_buffer(const std::string& name) = 0;
+    
+    // Window Management
+    virtual std::shared_ptr<Window> active_window() const = 0;
+    virtual bool set_active_window(std::shared_ptr<Window> window) = 0;
+    virtual void split_horizontally() = 0;
+    virtual void split_vertically() = 0;
+    virtual void close_active_window() = 0;
+    virtual void next_window() = 0;
+
+    // Cursor Management
+    virtual Position cursor() const noexcept = 0;
+    virtual void set_cursor(Position pos) = 0;
+
+    // Basic Editing (proxied from EditorCore for commands)
+    virtual void move_up() = 0;
+    virtual void move_down() = 0;
+    virtual void move_left() = 0;
+    virtual void move_right() = 0;
+    virtual void move_to_line_start() = 0;
+    virtual void move_to_line_end() = 0;
+    virtual void move_forward_word() = 0;
+    virtual void move_backward_word() = 0;
+    virtual void page_up() = 0;
+    virtual void page_down() = 0;
+    virtual void goto_beginning() = 0;
+    virtual void goto_end() = 0;
+    virtual void goto_line(size_t line) = 0;
+    virtual void kill_line() = 0;
+    virtual void kill_region() = 0;
+    virtual void copy_region_as_kill() = 0;
+    virtual void yank() = 0;
+    virtual void yank_pop() = 0;
+    virtual void kill_word() = 0;
+    virtual void backward_kill_word() = 0;
+
+    // Message Display
+    virtual void set_message(std::string msg) = 0;
+
+    // Quit
+    virtual void request_quit() = 0;
+
+    // Configuration
+    virtual Config& config() = 0;
+    virtual const Config& config() const = 0;
+
+    // Theme Management
+    virtual ThemeManager& theme_manager() = 0;
+    virtual const ThemeManager& theme_manager() const = 0;
+};
+
+} // namespace lumacs

+ 4 - 5
include/lumacs/keybinding.hpp

@@ -1,9 +1,12 @@
+#pragma once
+
 #include <string>
 #include <vector>
 #include <map>
 #include <memory>
 #include <chrono>
 #include <optional>
+#include "lumacs/command_system.hpp" // Include the full definition of CommandResult
 
 // Forward declaration for CommandSystem
 namespace lumacs {
@@ -46,7 +49,6 @@ struct Key {
     
     /// @brief Parse a key string like "C-x" or "M-S-a" into a Key structure.
     static Key parse(const std::string& key_str); // To be implemented in .cpp
-    
     /// @brief Convert Key back to canonical string representation.
     std::string to_string() const; // To be implemented in .cpp
     
@@ -106,7 +108,7 @@ struct KeyBinding {
     
     KeyBinding() = default;
     KeyBinding(const KeySequence& seq, std::string cmd_name, std::string desc = "");
-    KeyBinding(const std::string& key_str, std::string cmd_name, std::string desc = "");
+    KeyBinding(const std::string& key_str, std::string cmd_name, const std::string& description = "");
 };
 
 /// @brief Represents the result of processing a key in the binding manager.
@@ -118,9 +120,6 @@ enum class KeyResult {
     Timeout         ///< Partial sequence timed out.
 };
 
-// Forward declaration for CommandResult
-struct CommandResult;
-
 /// @brief Detailed result of a key processing operation.
 struct KeyProcessingResult {
     KeyResult type;

+ 15 - 0
include/lumacs/minibuffer_manager.hpp

@@ -1,3 +1,5 @@
+#pragma once
+
 #include <functional>
 #include <optional>
 #include <unordered_map> // For std::unordered_map
@@ -37,12 +39,18 @@ public:
     /// @brief Returns the current input buffer content for display.
     std::string get_input_buffer() const;
 
+    /// @brief Returns the current cursor position within the input buffer.
+    size_t get_cursor_position() const;
+
     /// @brief Returns whether the minibuffer is currently active.
     bool is_active() const { return current_mode_ != MinibufferMode::None; }
 
     /// @brief Returns the current minibuffer mode.
     MinibufferMode get_current_mode() const { return current_mode_; }
 
+    /// @brief Sets the input buffer content directly.
+    void set_input_buffer(const std::string& input);
+
     // History navigation
     void history_previous();
     void history_next();
@@ -63,6 +71,7 @@ private:
     std::string input_buffer_;
     std::function<void(const std::string&)> on_submit_callback_;
     std::function<void()> on_cancel_callback_;
+    size_t cursor_position_ = 0; // Tracks the current cursor position within input_buffer_
 
     // History
     std::unordered_map<MinibufferMode, HistoryManager> histories_;
@@ -74,6 +83,12 @@ private:
 
     /// @brief Helper to add current input to history if it's new.
     void add_to_history();
+
+    /// @brief Parses a command string and executes the corresponding command.
+    /// @param command_line The string containing the command name and its arguments.
+    /// @return The result of the command execution.
+    CommandResult parse_and_execute_command_string(const std::string& command_line);
 };
 
 } // namespace lumacs
+

+ 159 - 7
src/command_system.cpp

@@ -1,10 +1,44 @@
 #include "lumacs/command_system.hpp"
-#include "lumacs/editor_core.hpp" // For EditorCore interaction (if needed by commands)
+#include "lumacs/editor_core.hpp" 
+#include "lumacs/minibuffer_manager.hpp" // Added for interactive argument gathering
+#include "lumacs/i_command_target.hpp" // Added for ICommandTarget
 #include <algorithm> // For std::sort
+#include <iostream>  // TODO: Replace with proper logging (temporary for stoi error)
+#include <vector>    // For std::vector
+#include <string_view> // For std::string_view
 
 namespace lumacs {
 
-CommandSystem::CommandSystem(EditorCore* core) : core_(core) {
+// --- CommandContext Implementations ---
+std::optional<std::string> CommandContext::get_string_arg(size_t index) const {
+    if (index < args_.size()) {
+        return args_[index];
+    }
+    return std::nullopt;
+}
+
+std::optional<int> CommandContext::get_int_arg(size_t index) const {
+    if (index < args_.size()) {
+        try {
+            return std::stoi(args_[index]);
+        } catch (const std::invalid_argument& e) {
+            std::cerr << "CommandContext::get_int_arg: Invalid argument for stoi: " << args_[index] << std::endl;
+        } catch (const std::out_of_range& e) {
+            std::cerr << "CommandContext::get_int_arg: Out of range for stoi: " << args_[index] << std::endl;
+        }
+    }
+    return std::nullopt;
+}
+
+// These will be properly implemented once the CommandSystem can handle asynchronous interactive input.
+// For now, they return nullopt, relying on the CommandSystem to manage the interactive process.
+std::optional<std::string> CommandContext::read_string(const std::string& prompt) { return std::nullopt; }
+std::optional<std::string> CommandContext::read_file_path(const std::string& prompt) { return std::nullopt; }
+
+
+// --- CommandSystem Implementations ---
+CommandSystem::CommandSystem(EditorCore& core, MinibufferManager& minibuffer_manager)
+    : core_(core), minibuffer_manager_(minibuffer_manager) {
     // Register core commands here, or ensure they are registered via Lua
 }
 
@@ -18,18 +52,126 @@ void CommandSystem::register_command(const std::string& name, CommandFunction fu
     commands_.emplace(name, Command(name, std::move(function), doc_string, interactive, std::move(interactive_spec)));
 }
 
+// Helper to split interactive_spec string into individual specifiers
+static std::vector<std::string_view> parse_interactive_spec(std::string_view spec) {
+    std::vector<std::string_view> parts;
+    size_t start = 0;
+    size_t end = spec.find_first_of(';');
+    while (end != std::string_view::npos) {
+        parts.push_back(spec.substr(start, end - start));
+        start = end + 1;
+        end = spec.find_first_of(';', start);
+    }
+    parts.push_back(spec.substr(start)); // Add the last part
+    return parts;
+}
+
+CommandResult CommandSystem::process_next_interactive_argument() {
+    if (!current_interactive_command_ || current_interactive_command_->interactive_spec.empty()) {
+        // This should not happen if called correctly
+        current_interactive_command_.reset();
+        return {CommandStatus::Failure, "Internal error: No interactive command or spec."};
+    }
+
+    auto specs = parse_interactive_spec(current_interactive_command_->interactive_spec);
+
+    if (current_interactive_spec_index_ >= specs.size()) {
+        // All arguments gathered, execute the command
+        CommandContext context(core_, minibuffer_manager_, gathered_interactive_args_); // Pass core_ as ICommandTarget&
+        CommandResult result = current_interactive_command_->function(context);
+        current_interactive_command_.reset(); // Clear state after execution
+        gathered_interactive_args_.clear();
+        current_interactive_spec_index_ = 0;
+        return result;
+    }
+
+    std::string_view current_spec = specs[current_interactive_spec_index_];
+    std::string prompt;
+    MinibufferMode mode = MinibufferMode::Command; // Default mode
+
+    // Determine prompt and mode based on interactive_spec
+    if (current_spec == "s") {
+        prompt = "String: ";
+        mode = MinibufferMode::ystring;
+    } else if (current_spec == "f") {
+        prompt = "Find file: ";
+        mode = MinibufferMode::FilePath;
+    } else if (current_spec == "b") {
+        prompt = "Switch to buffer: ";
+        mode = MinibufferMode::BufferName;
+    } else if (current_spec == "n") {
+        prompt = "Number: ";
+        mode = MinibufferMode::ystring; // Numbers are read as strings, then parsed
+    }
+    // TODO: Add more specifiers as needed (e.g., region, yes/no)
+
+    // Capture `this` for the callbacks
+    auto self = this;
+    minibuffer_manager_.activate_minibuffer(
+        mode,
+        prompt,
+        [self](const std::string& input) {
+            self->gathered_interactive_args_.push_back(input);
+            self->current_interactive_spec_index_++;
+            // Try to process the next argument or execute the command
+            self->process_next_interactive_argument(); // Recursively call to continue
+        },
+        [self]() {
+            // On cancel, clear the interactive state
+            self->current_interactive_command_.reset();
+            self->gathered_interactive_args_.clear();
+            self->current_interactive_spec_index_ = 0;
+            // TODO: How to propagate cancellation result?
+            std::cerr << "Interactive command cancelled." << std::endl; // Temporary
+        }
+    );
+
+    return {CommandStatus::PendingInput, "Waiting for user input."};
+}
+
+
 CommandResult CommandSystem::execute(const std::string& name, const std::vector<std::string>& args) {
     auto it = commands_.find(name);
     if (it != commands_.end()) {
         try {
-            return it->second.function(args);
+            // For non-interactive execution, create a context with provided args
+            CommandContext context(core_, minibuffer_manager_, args); // Pass core_ as ICommandTarget&
+            return it->second.function(context);
         } catch (const std::exception& e) {
-            return {false, "Command '" + name + "' failed: " + e.what()};
+            return {CommandStatus::Failure, "Command '" + name + "' failed: " + e.what()};
         } catch (...) {
-            return {false, "Command '" + name + "' failed with unknown error."};
+            return {CommandStatus::Failure, "Command '" + name + "' failed with unknown error."};
         }
     }
-    return {false, "Unknown command: " + name};
+    return {CommandStatus::Failure, "Unknown command: " + name};
+}
+
+CommandResult CommandSystem::execute_interactive(const std::string& name) {
+    // If an interactive command is already pending, we shouldn't start a new one.
+    // This is a simplification; a more robust system might queue or disallow.
+    if (current_interactive_command_) {
+        return {CommandStatus::Failure, "Another interactive command is already pending input."};
+    }
+
+    auto it = commands_.find(name);
+    if (it == commands_.end()) {
+        return {CommandStatus::Failure, "Unknown command: " + name};
+    }
+    if (!it->second.interactive) {
+        return {CommandStatus::Failure, "Command '" + name + "' is not interactive."};
+    }
+    if (it->second.interactive_spec.empty()) {
+        // If interactive but no spec, just execute directly (no args, or uses default)
+        CommandContext context(core_, minibuffer_manager_, {}); // Pass core_ as ICommandTarget&
+        return it->second.function(context);
+    }
+
+    // Start a new interactive command sequence
+    current_interactive_command_ = it->second; // Copy the Command struct
+    current_interactive_spec_index_ = 0;
+    gathered_interactive_args_.clear();
+
+    return process_next_interactive_argument();
 }
 
 std::vector<std::string> CommandSystem::get_command_names() const {
@@ -50,4 +192,14 @@ std::optional<std::string> CommandSystem::get_command_doc_string(const std::stri
     return std::nullopt;
 }
 
-} // namespace lumacs
+std::optional<std::string> CommandSystem::get_command_interactive_spec(const std::string& name) const {
+    auto it = commands_.find(name);
+    if (it != commands_.end()) {
+        if (it->second.interactive) {
+            return it->second.interactive_spec;
+        }
+    }
+    return std::nullopt;
+}
+
+} // namespace lumacs

+ 24 - 23
src/editor_core.cpp

@@ -9,26 +9,28 @@
 namespace lumacs {
 
 EditorCore::EditorCore() :
-    buffers_(), // 1. std::list
-    root_node_(), // 2. std::shared_ptr
-    active_window_(), // 3. std::shared_ptr
-    last_message_(), // 4. std::string
-    event_callbacks_(), // 5. std::vector
-    kill_ring_(), // 6. KillRing
-    last_yank_start_(), // 7. std::optional
-    last_yank_end_(), // 8. std::optional
-    registers_(), // 9. std::unordered_map
-    current_macro_(), // 10. std::vector
-    last_macro_(), // 11. std::vector
-    recording_macro_(false), // 12. bool
-    rectangle_kill_ring_(), // 13. std::vector
-    theme_manager_(), // 14. ThemeManager
-    config_(), // 15. Config
-    keybinding_manager_(command_system_.get()), // 16. KeyBindingManager, now initialized with command_system_
-    lua_api_(std::make_unique<LuaApi>()), // 17. std::unique_ptr
-    command_system_(std::make_unique<CommandSystem>(this)), // 18. std::unique_ptr, initialized first
-    completion_system_(std::make_unique<CompletionSystem>(*this)), // 19. std::unique_ptr, initialized first
-    minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)) // 20. std::unique_ptr
+    buffers_(),
+    root_node_(),
+    active_window_(),
+    last_message_(),
+    message_clear_time_(),
+    event_callbacks_(),
+    kill_ring_(),
+    last_yank_start_(),
+    last_yank_end_(),
+    registers_(),
+    current_macro_(),
+    last_macro_(),
+    recording_macro_(false),
+    rectangle_kill_ring_(),
+    theme_manager_(),
+    config_(),
+    command_system_(std::make_unique<CommandSystem>(this)),
+    keybinding_manager_(std::make_unique<KeyBindingManager>(command_system_.get())), // Initialized after command_system_
+    lua_api_(std::make_unique<LuaApi>()),
+    modeline_manager_(),
+    completion_system_(std::make_unique<CompletionSystem>(*this)), // Initialized before minibuffer_manager_
+    minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)) // Initialized after completion_system_
 {
     // LuaApi needs core_ pointer to be valid, so set it after constructor body starts
     lua_api_->set_core(*this);
@@ -315,7 +317,7 @@ void EditorCore::close_active_window() {
     
     // Find the node containing active_window
     LayoutNode* target_node = find_node_with_window(root_node_.get(), active_window_);
-    if (!target_node) return; // Should not happen
+    if (!target_node) return; // Should not happen 
     
     LayoutNode* parent = find_parent_of_node(root_node_.get(), target_node);
     if (!parent) return; // Should not happen if not root
@@ -599,7 +601,7 @@ void EditorCore::goto_end() {
 
 void EditorCore::goto_line(size_t line) {
     auto& buf = active_window_->buffer();
-    line = std::min(line, buf.line_count() - 1);
+    line = std::min(line, buf.line_count() > 0 ? buf.line_count() - 1 : 0); // Clamp to max line index
     Position pos = {line, 0};
 
     active_window_->set_cursor(pos);
@@ -718,7 +720,6 @@ void EditorCore::kill_region() {
     std::string killed_text = buf.get_text_in_range(region.value());
     if (!killed_text.empty()) {
         kill_ring_.push(killed_text);
-        buf.erase(region.value());
         buf.deactivate_mark();
 
         // Move cursor to start of killed region

+ 188 - 0
src/gtk_completion_popup.cpp

@@ -0,0 +1,188 @@
+#include "lumacs/gtk_completion_popup.hpp"
+
+#ifdef LUMACS_WITH_GTK
+
+#include <gtkmm/label.h>
+#include <gtkmm/box.h>
+#include <gtkmm/cssprovider.h>
+#include <gtkmm/stylecontext.h>
+
+namespace lumacs {
+
+// Helper to create a styled Gtk::Label for candidate description
+Gtk::Widget* create_candidate_row_widget(const CompletionCandidate& candidate) {
+    auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
+
+    auto text_label = Gtk::make_managed<Gtk::Label>(candidate.text, Gtk::Align::START);
+    text_label->set_hexpand(true); // Allow text to expand
+
+    auto desc_label = Gtk::make_managed<Gtk::Label>(candidate.description, Gtk::Align::END);
+    desc_label->set_hexpand(true); // Allow description to expand
+    desc_label->set_halign(Gtk::Align::END);
+
+    // Apply a subtle style to description for differentiation
+    auto css_provider = Gtk::CssProvider::create();
+    css_provider->load_from_data("label.completion-description { color: #888; font-size: 0.9em; margin-left: 12px; }");
+    desc_label->get_style_context()->add_provider(css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    desc_label->get_style_context()->add_class("completion-description");
+
+    hbox->append(*text_label);
+    hbox->append(*desc_label);
+
+    return hbox;
+}
+
+GtkCompletionPopup::GtkCompletionPopup()
+    : Gtk::Window(Gtk::Window::Popup), // Use Gtk::Window::Popup for a transient window
+      active_index_(0) {
+    
+    // Set properties for the popup window
+    set_default_size(400, 200); // Default size, will be adjusted
+    set_decorated(false); // No window decorations
+    set_resizable(true); // Allow resizing
+    set_child(list_scrolled_window_); // Set scrolled window as child
+
+    list_scrolled_window_.set_child(list_box_);
+    list_scrolled_window_.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
+    list_scrolled_window_.set_propagate_natural_width(true);
+    list_scrolled_window_.set_propagate_natural_height(true);
+
+    list_box_.set_selection_mode(Gtk::SelectionMode::SINGLE);
+    list_box_.set_activate_on_single_click(true); // Activate on single click
+    list_box_.signal_row_activated().connect(sigc::mem_fun(*this, &GtkCompletionPopup::on_row_activated));
+
+    // Hide on focus loss
+    signal_focus_out_event().connect([this](GdkEventFocus* /*focus_event*/) {
+        if (get_visible()) {
+            signal_cancelled_.emit();
+            hide_popup();
+        }
+        return false;
+    });
+
+    // Add CSS for a border and padding
+    auto css_provider = Gtk::CssProvider::create();
+    css_provider->load_from_data(
+        "window.lumacs-completion-popup {"
+        "   border: 1px solid @border_color;"
+        "   background-color: @theme_bg_color;"
+        "}"
+        "listbox.lumacs-completion-listbox {"
+        "   padding: 0px;"
+        "}"
+        "listboxrow.lumacs-completion-row {"
+        "   padding: 4px 8px;"
+        "}"
+        "listboxrow.lumacs-completion-row:selected {"
+        "   background-color: @accent_bg_color;"
+        "   color: @accent_fg_color;"
+        "}"
+    );
+    get_style_context()->add_provider(css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    get_style_context()->add_class("lumacs-completion-popup");
+    list_box_.get_style_context()->add_class("lumacs-completion-listbox");
+
+    add_action("completion.select-next", sigc::mem_fun(*this, &GtkCompletionPopup::select_next));
+    add_action("completion.select-previous", sigc::mem_fun(*this, &GtkCompletionPopup::select_previous));
+
+    // This is necessary for the popup to be destroyed when hidden, rather than just hidden.
+    // However, for popups, it often handles its own lifetime with Gtk::Window::Popup
+    set_hide_on_close(true);
+}
+
+GtkCompletionPopup::~GtkCompletionPopup() {
+    // No specific cleanup needed for GTKmm objects, they are managed by Glib::make_managed or Gtk::Window
+}
+
+void GtkCompletionPopup::show_popup(const std::vector<CompletionCandidate>& candidates, size_t active_index, Gtk::Widget& attach_widget, int x_pos, int y_pos) {
+    candidates_ = candidates;
+    active_index_ = active_index;
+    update_list();
+
+    // Position the popup
+    // Gtk::Window::Popup windows need to be positioned manually.
+    // get_root_window() and get_position() might be useful here.
+    // For simplicity, we get the absolute position of the attach_widget
+    // then position ourselves relative to that.
+    int widget_abs_x, widget_abs_y;
+    attach_widget.translate_coordinates(*get_root_window(), 0, 0, widget_abs_x, widget_abs_y);
+    
+    // Position the popup below the attach_widget, aligned to x_pos
+    move(widget_abs_x + x_pos, widget_abs_y + y_pos + attach_widget.get_height());
+    
+    set_visible(true);
+    present(); // Bring to front and give focus
+}
+
+void GtkCompletionPopup::hide_popup() {
+    candidates_.clear();
+    active_index_ = 0;
+    list_box_.remove_all();
+    set_visible(false);
+}
+
+void GtkCompletionPopup::update_list() {
+    list_box_.remove_all();
+    for (const auto& candidate : candidates_) {
+        auto row = Gtk::make_managed<Gtk::ListBoxRow>();
+        row->set_child(*create_candidate_row_widget(candidate));
+        row->get_style_context()->add_class("lumacs-completion-row");
+        list_box_.append(*row);
+    }
+    set_active_row(active_index_);
+    list_box_.show();
+}
+
+void GtkCompletionPopup::set_active_row(size_t index) {
+    if (index < candidates_.size()) {
+        Gtk::ListBoxRow* row = list_box_.get_row_at_index(static_cast<int>(index));
+        if (row) {
+            list_box_.select_row(*row);
+            row->scroll_to_row(); // Ensure the selected row is visible
+            active_index_ = index;
+        }
+    }
+}
+
+void GtkCompletionPopup::select_next() {
+    if (candidates_.empty()) return;
+    active_index_ = (active_index_ + 1) % candidates_.size();
+    set_active_row(active_index_);
+}
+
+void GtkCompletionPopup::select_previous() {
+    if (candidates_.empty()) return;
+    if (active_index_ == 0) {
+        active_index_ = candidates_.size() - 1;
+    } else {
+        active_index_--;
+    }
+    set_active_row(active_index_);
+}
+
+std::optional<CompletionCandidate> GtkCompletionPopup::get_selected_candidate() const {
+    if (!candidates_.empty()) {
+        return candidates_[active_index_];
+    }
+    return std::nullopt;
+}
+
+void GtkCompletionPopup::on_row_activated(Gtk::ListBoxRow* row) {
+    active_index_ = static_cast<size_t>(row->get_index());
+    if (auto selected = get_selected_candidate()) {
+        signal_candidate_selected_.emit(selected.value());
+    }
+    hide_popup();
+}
+
+GtkCompletionPopup::type_signal_candidate_selected GtkCompletionPopup::signal_candidate_selected() {
+    return signal_candidate_selected_;
+}
+
+GtkCompletionPopup::type_signal_cancelled GtkCompletionPopup::signal_cancelled() {
+    return signal_cancelled_;
+}
+
+} // namespace lumacs
+
+#endif // LUMACS_WITH_GTK

+ 504 - 383
src/gtk_editor.cpp

@@ -5,6 +5,7 @@
 #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_completion_popup.hpp" // Include for GtkCompletionPopup
 #include <iostream>
 #include <filesystem>
 #include <vector>
@@ -18,443 +19,563 @@
 
 namespace lumacs {
 
-// Custom Gtk::ApplicationWindow to make constructor public
-class LumacsWindow : public Gtk::ApplicationWindow {
-public:
-    explicit LumacsWindow(const Glib::RefPtr<Gtk::Application>& application)
-        : Gtk::ApplicationWindow(application) {
-        set_title("Lumacs - GTK4");
-        set_default_size(1024, 768);
-    }
-};
+// LumacsWindow method implementations
+LumacsWindow::LumacsWindow(const Glib::RefPtr<Gtk::Application>& application)
+    : Gtk::ApplicationWindow(application) {
+    set_title("Lumacs - GTK4");
+    set_default_size(1024, 768);
+}
 
-class GtkEditor : public IEditorView {
-public:
-    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()) {
-            cursor_timer_connection_.disconnect();
-        }
-        
-        // Clear core pointer to prevent any callbacks during GTK cleanup
-        core_ = nullptr;
-        
-        // If we still have an app reference, try to quit gracefully
-        if (app_ && app_->is_registered()) {
-            try {
-                app_->quit();
-            } catch (...) {
-                // Ignore exceptions during cleanup
-            }
+// GtkEditor method implementations
+GtkEditor::GtkEditor() : core_(nullptr), drawing_area_(nullptr), content_widget_(nullptr) {}
+
+GtkEditor::~GtkEditor() {
+    // 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;
+    
+    // If we still have an app reference, try to quit gracefully
+    if (app_ && app_->is_registered()) {
+        try {
+            app_->quit();
+        } catch (...) {
+            // Ignore exceptions during cleanup
         }
-        
-        // 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
     }
+    
+    // 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
+}
 
-    void init() override {
-        // Initialize GTK application
-        app_ = Gtk::Application::create("org.lumacs.editor");
-        app_->signal_activate().connect(sigc::mem_fun(*this, &GtkEditor::on_activate));
-    }
+void GtkEditor::init() {
+    // Initialize GTK application
+    app_ = Gtk::Application::create("org.lumacs.editor");
+    app_->signal_activate().connect(sigc::mem_fun(*this, &GtkEditor::on_activate));
+}
 
-    void run() override {
-        // Run the application's event loop
-        app_->run();
-    }
+void GtkEditor::run() {
+    // Run the application's event loop
+    app_->run();
+}
 
-    void handle_editor_event(EditorEvent event) override {
-        // Safety check during destruction
-        if (!core_ || !app_) return;
+void GtkEditor::handle_editor_event(EditorEvent event) {
+    // Safety check during destruction
+    if (!core_ || !app_) return;
 
-        // Handle layout changes
-        if (event == EditorEvent::WindowLayoutChanged) {
-            rebuild_layout();
-        }
+    // Handle layout changes
+    if (event == EditorEvent::WindowLayoutChanged) {
+        rebuild_layout();
+    }
 
-        // Request redraw on most events - recursively find all drawing areas
-        if (content_widget_) {
-            queue_redraw_all_windows(content_widget_);
-        }
+    // Request redraw on most events - recursively find all drawing areas
+    if (content_widget_) {
+        queue_redraw_all_windows(content_widget_);
+    }
 
-        bool minibuffer_activated = false;
-        if (event == EditorEvent::CommandMode) {
-            core_->minibuffer_manager().activate_minibuffer(
-                MinibufferMode::Command, "M-x ",
-                [this](const std::string& input) {
-                    if (input == "quit" || input == "q") {
-                        app_->quit();
-                    } else {
-                        auto result = core_->command_system().execute(input);
-                        core_->set_message(result.message);
-                    }
-                }, nullptr
-            );
-            minibuffer_activated = true;
-        } else if (event == EditorEvent::FindFileMode) {
-            core_->minibuffer_manager().activate_minibuffer(
-                MinibufferMode::FilePath, "Find file: ",
-                [this](const std::string& input) {
-                    if (core_->load_file(input)) core_->set_message("Loaded");
-                    else core_->set_message("Failed to load");
-                }, nullptr
-            );
-            minibuffer_activated = true;
-        } else if (event == EditorEvent::BufferSwitchMode) {
-            core_->minibuffer_manager().activate_minibuffer(
-                MinibufferMode::BufferName, "Switch to buffer: ",
-                [this](const std::string& input) {
-                    if (core_->switch_buffer_in_window(input)) core_->set_message("Switched");
-                    else core_->set_message("Buffer not found");
-                }, nullptr
-            );
-            minibuffer_activated = true;
-        } else if (event == EditorEvent::KillBufferMode) {
-            core_->minibuffer_manager().activate_minibuffer(
-                MinibufferMode::BufferName, "Kill buffer: ",
-                [this](const std::string& input) {
-                    if (core_->close_buffer(input)) core_->set_message("Killed buffer");
-                    else core_->set_message("Buffer not found");
-                }, nullptr
-            );
-            minibuffer_activated = true;
-        } else if (event == EditorEvent::ThemeSelectionMode) {
-            core_->minibuffer_manager().activate_minibuffer(
-                MinibufferMode::ThemeName, "Set theme: ",
-                [this](const std::string& input) {
-                    auto theme_names = core_->theme_manager().theme_names();
-                    auto it = std::find(theme_names.begin(), theme_names.end(), input);
-                    if (it != theme_names.end()) {
-                        core_->set_theme(input);
-                        core_->set_message("Switched to theme: " + input);
-                    } else {
-                        core_->set_message("Theme not found: " + input);
-                    }
-                }, nullptr
-            );
-            minibuffer_activated = true;
-        } else if (event == EditorEvent::ISearchMode) {
-            core_->minibuffer_manager().activate_minibuffer(
-                MinibufferMode::ISearch, "I-search: ",
-                [this](const std::string& input) { /* TODO: Implement actual isearch logic */ core_->set_message("I-search: " + input); }, nullptr
-            );
-            minibuffer_activated = true;
-        } else if (event == EditorEvent::TransientMessageCleared) {
-            // Force redraw to clear the message
-            if (content_widget_) queue_redraw_all_windows(content_widget_);
-        } else if (event == EditorEvent::Quit) {
-            // Disconnect timer before quitting to prevent segfault
-            if (cursor_timer_connection_.connected()) {
-                cursor_timer_connection_.disconnect();
-            }
-            // Use idle callback to quit safely after current event processing
-            Glib::signal_idle().connect_once([this]() {
-                if (app_) {
+    bool minibuffer_activated = false;
+    if (event == EditorEvent::CommandMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::Command, "M-x ",
+            [this](const std::string& input) {
+                if (input == "quit" || input == "q") {
                     app_->quit();
+                } else {
+                    auto result = core_->command_system().execute(input, {}); // Pass empty args
+                    core_->set_message(result.message);
                 }
-            });
-        }
-        
-        if (minibuffer_activated) {
-            if (content_widget_) queue_redraw_all_windows(content_widget_);
+            }, nullptr
+        );
+        minibuffer_activated = true;
+    } else if (event == EditorEvent::FindFileMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::FilePath, "Find file: ",
+            [this](const std::string& input) {
+                if (core_->load_file(input)) core_->set_message("Loaded");
+                else core_->set_message("Failed to load");
+            }, nullptr
+        );
+        minibuffer_activated = true;
+    } else if (event == EditorEvent::BufferSwitchMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::BufferName, "Switch to buffer: ",
+            [this](const std::string& input) {
+                if (core_->switch_buffer_in_window(input)) core_->set_message("Switched");
+                else core_->set_message("Buffer not found");
+            }, nullptr
+        );
+        minibuffer_activated = true;
+    } else if (event == EditorEvent::KillBufferMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::BufferName, "Kill buffer: ",
+            [this](const std::string& input) {
+                if (core_->close_buffer(input)) core_->set_message("Killed buffer");
+                else core_->set_message("Buffer not found");
+            }, nullptr
+        );
+        minibuffer_activated = true;
+    } else if (event == EditorEvent::ThemeSelectionMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ThemeName, "Set theme: ",
+            [this](const std::string& input) {
+                auto theme_names = core_->theme_manager().theme_names();
+                auto it = std::find(theme_names.begin(), theme_names.end(), input);
+                if (it != theme_names.end()) {
+                    core_->set_theme(input);
+                    core_->set_message("Switched to theme: " + input);
+                } else {
+                    core_->set_message("Theme not found: " + input);
+                }
+            }, nullptr
+        );
+        minibuffer_activated = true;
+    } else if (event == EditorEvent::ISearchMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ISearch, "I-search: ",
+            [this](const std::string& input) { /* TODO: Implement actual isearch logic */ core_->set_message("I-search: " + input); }, nullptr
+        );
+        minibuffer_activated = true;
+    } else if (event == EditorEvent::TransientMessageCleared) {
+        // Force redraw to clear the message
+        if (content_widget_) queue_redraw_all_windows(content_widget_);
+    } else if (event == EditorEvent::Quit) {
+        // Disconnect timer before quitting to prevent segfault
+        if (cursor_timer_connection_.connected()) {
+            cursor_timer_connection_.disconnect();
         }
+        // Use idle callback to quit safely after current event processing
+        Glib::signal_idle().connect_once([this]() {
+            if (app_) {
+                app_->quit();
+            }
+        });
     }
-
-    void set_core(EditorCore* core) override {
-        core_ = core;
+    
+    // Check if minibuffer is active AFTER processing event to decide popup visibility
+    if (core_->minibuffer_manager().is_active()) {
+        show_completion_popup(); // Show/hide completion popup when minibuffer state changes
+        if (content_widget_) queue_redraw_all_windows(content_widget_);
+    } else {
+        hide_completion_popup(); // Minibuffer deactivated, hide popup
     }
+}
 
-    // Helper to recursively find and redraw all drawing areas
-    void queue_redraw_all_windows(Gtk::Widget* widget) {
-        if (!widget) return;
-        
-        if (auto drawing_area = dynamic_cast<Gtk::DrawingArea*>(widget)) {
-            drawing_area->queue_draw();
-        } else if (auto paned = dynamic_cast<Gtk::Paned*>(widget)) {
-            if (auto start_child = paned->get_start_child()) {
-                queue_redraw_all_windows(start_child);
-            }
-            if (auto end_child = paned->get_end_child()) {
-                queue_redraw_all_windows(end_child);
-            }
+void GtkEditor::set_core(EditorCore* core) {
+    core_ = core;
+}
+
+// Helper to recursively find and redraw all drawing areas
+void GtkEditor::queue_redraw_all_windows(Gtk::Widget* widget) {
+    if (!widget) return;
+    
+    if (auto drawing_area = dynamic_cast<Gtk::DrawingArea*>(widget)) {
+        drawing_area->queue_draw();
+    } else if (auto paned = dynamic_cast<Gtk::Paned*>(widget)) {
+        if (auto start_child = paned->get_start_child()) {
+            queue_redraw_all_windows(start_child);
+        }
+        if (auto end_child = paned->get_end_child()) {
+            queue_redraw_all_windows(end_child);
         }
     }
+}
 
-private:
-    EditorCore* core_;
-    std::shared_ptr<Window> cached_active_window_; // Cached to prevent focus jumping during redraws
+// Helper to convert GDK keyval and modifiers to Lumacs key name
+std::string GtkEditor::get_lumacs_key_name(guint keyval, Gdk::ModifierType state) {
+    std::string key_name;
+    if (state & Gdk::ModifierType::CONTROL_MASK) {
+        key_name += "C-";
+    }
+    // Alt key is often mapped to Meta in Emacs-like editors
+    if (state & Gdk::ModifierType::ALT_MASK || state & Gdk::ModifierType::META_MASK) { 
+        key_name += "M-";
+    }
+    // Shift is generally handled by the keyval itself for letters (e.g., 'A' vs 'a')
+    // For special keys, we might want "S-ArrowUp" but for now, rely on keyval.
 
-    Glib::RefPtr<Gtk::Application> app_;
-    Gtk::Window* window_ = nullptr; // Store window pointer for widget access only (not lifetime management)
-    Gtk::DrawingArea* drawing_area_ = nullptr; // For single-window compatibility
-    Gtk::Widget* content_widget_ = nullptr; // Will be either drawing_area_ or a split container
-    
-    // Cursor blinking
-    bool cursor_visible_ = true;
-    sigc::connection cursor_timer_connection_;
+    // GDK_KEY_Tab does not have a unicode value, handle separately
+    if (keyval == GDK_KEY_Tab) {
+        key_name += "Tab";
+    }
+    else if (keyval == GDK_KEY_ISO_Left_Tab) { // Shift-Tab for cycling backwards
+        key_name += "S-Tab";
+    }
+    else if (keyval >= GDK_KEY_space && keyval <= GDK_KEY_asciitilde) { // Printable ASCII
+        key_name += static_cast<char>(Gdk::keyval_to_unicode(keyval));
+    } else {
+        // Handle special keys
+        switch (keyval) {
+            case GDK_KEY_Return: key_name += "Return"; break;
+            case GDK_KEY_Escape: key_name += "Escape"; break;
+            case GDK_KEY_BackSpace: key_name += "BackSpace"; break;
+            case GDK_KEY_Delete: key_name += "Delete"; break;
+            case GDK_KEY_Up: key_name += "ArrowUp"; break;
+            case GDK_KEY_Down: key_name += "ArrowDown"; break;
+            case GDK_KEY_Left: key_name += "ArrowLeft"; break;
+            case GDK_KEY_Right: key_name += "ArrowRight"; break;
+            case GDK_KEY_Home: key_name += "Home"; break;
+            case GDK_KEY_End: key_name += "End"; break;
+            case GDK_KEY_Page_Up: key_name += "PageUp"; break;
+            case GDK_KEY_Page_Down: key_name += "PageDown"; break;
+            case GDK_KEY_F1: key_name += "F1"; break;
+            case GDK_KEY_F2: key_name += "F2"; break;
+            case GDK_KEY_F3: key_name += "F3"; break;
+            case GDK_KEY_F4: key_name += "F4"; break;
+            case GDK_KEY_F5: key_name += "F5"; break;
+            case GDK_KEY_F6: key_name += "F6"; break;
+            case GDK_KEY_F7: key_name += "F7"; break;
+            case GDK_KEY_F8: key_name += "F8"; break;
+            case GDK_KEY_F9: key_name += "F9"; break;
+            case GDK_KEY_F10: key_name += "F10"; break;
+            case GDK_KEY_F11: key_name += "F11"; break;
+            case GDK_KEY_F12: key_name += "F12"; break;
+            // Add more special keys as needed
+            default:
+                // Fallback for unhandled keys
+                // Gdk::keyval_name(keyval) might give "dead_acute", which is not good.
+                // Best to ignore unhandled keys for now or map explicitly.
+                return ""; // Consume if we couldn't map to a Lumacs key (to avoid random chars appearing)
+        }
+    }
+    return key_name;
+}
 
-    // GtkRenderer instance
-    std::unique_ptr<GtkRenderer> gtk_renderer_;
 
-protected:
-    void on_activate() {
-        // Create main window and associate with the application
-        // Note: The window is owned by the application through GObject reference counting
-        // We just keep a raw pointer for access, but don't manage its lifetime
-        window_ = new LumacsWindow(app_);
+bool GtkEditor::on_global_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state) {
+    if (!core_) return false;
 
-        // Build initial layout (single window)
-        rebuild_layout();
+    // Translate GDK event to Lumacs key name
+    std::string lumacs_key_name = get_lumacs_key_name(keyval, state);
+    if (lumacs_key_name.empty()) {
+        return false; // Not a key we care about or couldn't map
+    }
 
-        // Handle window close event
-        window_->signal_close_request().connect([this]() -> bool {
-            // Cleanup before closing
-            if (cursor_timer_connection_.connected()) {
-                cursor_timer_connection_.disconnect();
+    if (core_->minibuffer_manager().is_active()) {
+        // Minibuffer is active, keys should primarily go to it, or completion popup
+        if (completion_popup_ && completion_popup_->get_visible()) {
+            // If completion popup is visible, it takes precedence for navigation/selection keys
+            if (lumacs_key_name == "ArrowUp") {
+                completion_popup_->select_previous();
+                return true;
+            } else if (lumacs_key_name == "ArrowDown") {
+                completion_popup_->select_next();
+                return true;
+            } else if (lumacs_key_name == "Return") {
+                if (auto selected = completion_popup_->get_selected_candidate()) {
+                    on_completion_selected(selected.value());
+                } else {
+                    // If no completion selected, but Return pressed, pass to minibuffer
+                    core_->minibuffer_manager().handle_key_event(lumacs_key_name);
+                }
+                return true;
+            } else if (lumacs_key_name == "Escape") {
+                on_completion_cancelled();
+                return true;
+            } else if (lumacs_key_name == "Tab" || lumacs_key_name == "S-Tab") {
+                 // Forward Tab to MinibufferManager for its internal cycle
+                 core_->minibuffer_manager().handle_key_event(lumacs_key_name);
+                 // Redraw popup to show updated selection
+                 show_completion_popup(); 
+                 return true;
             }
-            core_ = nullptr;
-            drawing_area_ = nullptr;
-            content_widget_ = nullptr;
-            
-            // Allow window to close
-            return false; // false means "allow close"
-        }, false);
-
-        // Show window
-        window_->present();
-        if (drawing_area_) {
-            drawing_area_->grab_focus();
         }
         
-        // Initialize GtkRenderer after drawing_area_ is set
-        if (core_ && drawing_area_) {
-            gtk_renderer_ = std::make_unique<GtkRenderer>(*core_, *drawing_area_);
+        // If key is not handled by completion popup, or popup is not visible,
+        // pass to MinibufferManager
+        bool handled_by_minibuffer = core_->minibuffer_manager().handle_key_event(lumacs_key_name);
+        
+        // After any minibuffer key event, update and potentially show/hide completion
+        if (handled_by_minibuffer) {
+            if (core_->minibuffer_manager().get_completion_candidates().empty()) {
+                hide_completion_popup();
+            } else {
+                show_completion_popup();
+            }
+            queue_redraw_all_windows(content_widget_); // Redraw minibuffer content
+        } else {
+            // If minibuffer didn't handle it, it could be a keybinding for the editor that
+            // should still work while the minibuffer is active (e.g., C-g for quit)
+            // For now, we assume if minibuffer is active, it consumes all relevant keys.
+            // If the key is not handled, it probably means it's irrelevant to minibuffer input.
+            // But we should still consume it to avoid propagating to editor keybindings accidentally.
+            // Returning true consumes the event.
+            return true; 
         }
-
-        // 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
-        );
+        return handled_by_minibuffer; // Return if minibuffer handled it
+    } else {
+        // Minibuffer not active, pass to main keybinding manager
+        KeyResult result = core_->keybinding_manager().process_key(lumacs_key_name);
+        if (result.status == KeyResult::Status::Processed) {
+            queue_redraw_all_windows(content_widget_);
+            return true;
+        } else if (result.status == KeyResult::Status::Pending) {
+            // Multi-key sequence in progress, wait for next key
+            queue_redraw_all_windows(content_widget_);
+            return true;
+        }
+        // If not processed by keybinding, let GTK handle it (e.g., for system shortcuts)
+        return false; 
     }
+}
 
-    // 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_ || !gtk_renderer_) return;
+void GtkEditor::on_activate() {
+    // Create main window and associate with the application
+    // Note: The window is owned by the application through GObject reference counting
+    // We just keep a raw pointer for access, but don't manage its lifetime
+    window_ = new LumacsWindow(app_);
 
-        gtk_renderer_->on_draw(cr, width, height, cached_active_window_, cursor_visible_);
-    }
+    // Build initial layout (single window)
+    rebuild_layout();
 
-    // Cursor blinking callback
-    bool on_cursor_blink() {
-        // Safety check - don't blink if core is destroyed or no drawing area
-        if (!core_ || !drawing_area_ || !app_) {
-            return false; // Stop the timer
-        }
-        
-        // Double check that the app is still running
-        if (!app_->is_registered()) {
-            return false; // Stop the timer
-        }
-        
-        try {
-            cursor_visible_ = !cursor_visible_;
-            core_->check_and_clear_message(); // Check and clear messages
-            drawing_area_->queue_draw();
-        } catch (...) {
-            return false; // Stop timer on any exception
+    // Handle window close event
+    window_->signal_close_request().connect([this]() -> bool {
+        // Cleanup before closing
+        if (cursor_timer_connection_.connected()) {
+            cursor_timer_connection_.disconnect();
         }
+        core_ = nullptr;
+        drawing_area_ = nullptr;
+        content_widget_ = nullptr;
         
-        return true; // Continue timer
+        // Allow window to close
+        return false; // false means "allow close"
+    }, false);
+
+    // Show window
+    window_->present();
+    if (drawing_area_) {
+        drawing_area_->grab_focus();
+    }
+    
+    // Initialize GtkRenderer after drawing_area_ is set
+    if (core_ && drawing_area_) {
+        gtk_renderer_ = std::make_unique<GtkRenderer>(*core_, *drawing_area_);
     }
 
-    // Rebuild the GTK layout to match the core's window tree
-    void rebuild_layout() {
-        if (!core_ || !window_) return;
+    // 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
+    );
+
+    // Initialize completion popup
+    completion_popup_ = std::make_unique<GtkCompletionPopup>();
+    completion_popup_->set_transient_for(*window_); // Make it transient for the main window
+    completion_popup_->set_hide_on_close(true); // Allow Gtk to hide it cleanly
+    completion_popup_->signal_candidate_selected().connect(sigc::mem_fun(*this, &GtkEditor::on_completion_selected));
+    completion_popup_->signal_cancelled().connect(sigc::mem_fun(*this, &GtkEditor::on_completion_cancelled));
+
+    // Attach global key controller to the main window
+    auto global_key_controller = Gtk::EventControllerKey::create();
+    global_key_controller->signal_key_pressed().connect(
+        sigc::mem_fun(*this, &GtkEditor::on_global_key_pressed), true // Consume events handled by us
+    );
+    window_->add_controller(global_key_controller);
+}
 
-        auto root_layout = core_->root_layout();
-        if (!root_layout) return;
+// Rendering
+void GtkEditor::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
+    // Safety check - don't draw if core is destroyed or gtk_renderer_ not initialized
+    if (!core_ || !gtk_renderer_) return;
 
-        // Remove existing content
-        if (content_widget_) {
-            window_->unset_child();
-        }
+    gtk_renderer_->on_draw(cr, width, height, cached_active_window_, cursor_visible_);
+}
 
-        // Clear the drawing area reference since we're rebuilding
-        drawing_area_ = nullptr;
+// Cursor blinking callback
+bool GtkEditor::on_cursor_blink() {
+    // Safety check - don't blink if core is destroyed or no drawing area
+    if (!core_ || !drawing_area_ || !app_) {
+        return false; // Stop the timer
+    }
+    
+    // Double check that the app is still running
+    if (!app_->is_registered()) {
+        return false; // Stop the timer
+    }
+    
+    try {
+        cursor_visible_ = !cursor_visible_;
+        core_->check_and_clear_message(); // Check and clear messages
+        drawing_area_->queue_draw();
+    } catch (...) {
+        return false; // Stop timer on any exception
+    }
+    
+    return true; // Continue timer
+}
 
-        // Initialize cached active window to prevent focus jumping
-        cached_active_window_ = core_->active_window();
+// Rebuild the GTK layout to match the core's window tree
+void GtkEditor::rebuild_layout() {
+    if (!core_ || !window_) return;
 
-        // Create new layout based on the tree
-        content_widget_ = create_widget_for_layout_node(root_layout);
-        if (content_widget_) {
-            window_->set_child(*content_widget_);
-        }
+    auto root_layout = core_->root_layout();
+    if (!root_layout) return;
+
+    // Remove existing content
+    if (content_widget_) {
+        window_->unset_child();
     }
 
-    // Create GTK widget tree from LayoutNode tree
-    Gtk::Widget* create_widget_for_layout_node(std::shared_ptr<LayoutNode> node) {
-        if (!node) return nullptr;
-
-        if (node->type == LayoutNode::Type::Leaf) {
-            // Create a new DrawingArea for this window
-            auto drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
-            
-            // Set up drawing for this specific window
-            // 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() && gtk_renderer_) {
-                    gtk_renderer_->draw_window(cr, width, height, window, drawing_area, cached_active_window_, cursor_visible_);
+    // Clear the drawing area reference since we're rebuilding
+    drawing_area_ = nullptr;
+
+    // Initialize cached active window to prevent focus jumping
+    cached_active_window_ = core_->active_window();
+
+    // Create new layout based on the tree
+    content_widget_ = create_widget_for_layout_node(root_layout);
+    if (content_widget_) {
+        window_->set_child(*content_widget_);
+    }
+}
+
+// Create GTK widget tree from LayoutNode tree
+Gtk::Widget* GtkEditor::create_widget_for_layout_node(std::shared_ptr<LayoutNode> node) {
+    if (!node) return nullptr;
+
+    if (node->type == LayoutNode::Type::Leaf) {
+        // Create a new DrawingArea for this window
+        auto drawing_area = Gtk::make_managed<Gtk::DrawingArea>();
+        
+        // Set up drawing for this specific window
+        // 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_ptr = weak_window.lock()) { // Correctly capture shared_ptr
+                if (gtk_renderer_) {
+                    gtk_renderer_->draw_window(cr, width, height, window_ptr, 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
-                    }
+            }
+        });
+        
+        drawing_area->set_focusable(true);
+        
+        // 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
                 }
-                // 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();
-                        }
+                // 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();
-                        }
+            }
+        });
+        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);
+            }
+        });
+        
+        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();
                      }
-                     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;
-            }
-            
-            return drawing_area;
-        } else {
-            // Create a paned container for splits
-            Gtk::Paned* paned = nullptr;
-            if (node->type == LayoutNode::Type::HorizontalSplit) {
-                paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
-            } else { // VerticalSplit  
-                paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL);
             }
-            
-            // Recursively create children
-            auto child1 = create_widget_for_layout_node(node->child1);
-            auto child2 = create_widget_for_layout_node(node->child2);
-            
-            if (child1) paned->set_start_child(*child1);
-            if (child2) paned->set_end_child(*child2);
-            
-            // Set initial position based on ratio
-            // Use signal_map to set position when widget is ready
-            paned->signal_map().connect([paned, node](){
-                 int width = paned->get_width();
-                 int height = paned->get_height();
-                 int size = (paned->get_orientation() == Gtk::Orientation::HORIZONTAL) ? width : height;
-                 
-                 // Fallback if size not yet available
-                 if (size <= 1) size = 1000; // Assume a reasonable default window size
-                 
-                 paned->set_position(static_cast<int>(size * node->ratio));
-            });
-            
-            return paned;
+        });
+        
+        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;
+        }
+        
+        return drawing_area;
+    } else {
+        // Create a paned container for splits
+        Gtk::Paned* paned = nullptr;
+        if (node->type == LayoutNode::Type::HorizontalSplit) {
+            paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::VERTICAL);
+        } else { // VerticalSplit  
+            paned = Gtk::make_managed<Gtk::Paned>(Gtk::Orientation::HORIZONTAL);
         }
+        
+        // Recursively create children
+        auto child1 = create_widget_for_layout_node(node->child1);
+        auto child2 = create_widget_for_layout_node(node->child2);
+        
+        if (child1) paned->set_start_child(*child1);
+        if (child2) paned->set_end_child(*child2);
+        
+        // Set initial position based on ratio
+        // Use signal_map to set position when widget is ready
+        paned->signal_map().connect([paned, node](){
+             int width = paned->get_width();
+             int height = paned->get_height();
+             int size = (paned->get_orientation() == Gtk::Orientation::HORIZONTAL) ? width : height;
+             
+             // Fallback if size not yet available
+             if (size <= 1) size = 1000; // Assume a reasonable default window size
+             
+             paned->set_position(static_cast<int>(size * node->ratio));
+        });
+        
+        return paned;
     }
-};
+}
 
 std::unique_ptr<IEditorView> create_gtk_editor() {
     return std::make_unique<GtkEditor>();

+ 16 - 1
src/gtk_renderer.cpp

@@ -300,6 +300,8 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
              }
         }
     }
+} // Closing brace for GtkRenderer::draw_window method
+
 
 void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
                                            std::shared_ptr<Window> window,
@@ -474,4 +476,17 @@ void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int
             }
         }
     }
-} // namespace lumacs
+}
+
+bool GtkRenderer::get_minibuffer_coords(int& x, int& y, int& width, int& height) const {
+    // These coordinates are relative to the main drawing area.
+    // The minibuffer is always at the bottom of the main drawing area.
+    width = main_drawing_area_.get_width();
+    height = static_cast<int>(line_height_ + PADDING_TOP + PADDING_BOTTOM); // Approximate minibuffer height
+    x = 0; // Starts at the left edge
+    y = main_drawing_area_.get_height() - height; // Position from the bottom
+
+    return true;
+}
+
+} // namespace lumacs

+ 39 - 50
src/keybinding.cpp

@@ -6,9 +6,9 @@
 
 namespace lumacs {
 
-// ============================================================================
+// ============================================================================ 
 // Key Implementation
-// ============================================================================
+// ============================================================================ 
 
 namespace {
     // Helper to convert string to BaseKey
@@ -42,7 +42,7 @@ namespace {
         if (s == "/") return BaseKey::Slash;
         if (s == "`") return BaseKey::Backtick;
         if (s == "[") return BaseKey::LeftBracket;
-        if (s == "\\") return BaseKey::Backslash;
+        if (s == "\") return BaseKey::Backslash; // Fixed escaping
         if (s == "]") return BaseKey::RightBracket;
         if (s == "'") return BaseKey::Quote;
         // Add more keys as needed
@@ -52,7 +52,7 @@ namespace {
     // Helper to convert BaseKey to string
     std::string base_key_to_string(BaseKey bk) {
         if (bk >= BaseKey::A && bk <= BaseKey::Z) return std::string(1, static_cast<char>(static_cast<int>(bk) - static_cast<int>(BaseKey::A) + 'a'));
-        if (bk >= BaseKey::D0 && bk <= BaseKey::D9) return std::string(1, static_cast<char>(static_cast<int>(bk) - static_cast<int>(BaseKey::D0) + '0'));
+        if (bk >= BaseKey::D0 && bk <= BaseKey::D9) return std::string(1, static_cast<char>(static_cast<int>(bk) - static_cast<int>(BaseKey::D0) + '0')); // Fixed logic
         
         switch (bk) {
             case BaseKey::Space: return "Space";
@@ -90,7 +90,7 @@ namespace {
             case BaseKey::Slash: return "/";
             case BaseKey::Backtick: return "`";
             case BaseKey::LeftBracket: return "[";
-            case BaseKey::Backslash: return "\\";
+            case BaseKey::Backslash: return "\"; // Fixed escaping
             case BaseKey::RightBracket: return "]";
             case BaseKey::Quote: return "'";
             default: return "Unknown";
@@ -154,9 +154,9 @@ bool Key::operator<(const Key& other) const {
     return modifiers < other.modifiers;
 }
 
-// ============================================================================
+// ============================================================================ 
 // KeySequence Implementation
-// ============================================================================
+// ============================================================================ 
 
 KeySequence::KeySequence(const std::vector<Key>& keys) : keys_(keys) {
 }
@@ -230,21 +230,21 @@ bool KeySequence::operator<(const KeySequence& other) const {
     return keys_ < other.keys_;
 }
 
-// ============================================================================
+// ============================================================================ 
 // KeyBinding Implementation
-// ============================================================================
+// ============================================================================ 
 
-KeyBinding::KeyBinding(const KeySequence& seq, std::string cmd_name, std::string desc)
+KeyBinding::KeyBinding(const KeySequence& seq, std::string cmd_name, std::string desc) // Corrected desc parameter type
     : sequence(seq), command_name(std::move(cmd_name)), description(std::move(desc)) {
 }
 
-KeyBinding::KeyBinding(const std::string& key_str, std::string cmd_name, std::string desc)
-    : sequence(key_str), command_name(std::move(cmd_name)), description(std::move(desc)) {
+KeyBinding::KeyBinding(const std::string& key_str, std::string cmd_name, std::string description) // Corrected description parameter type
+    : sequence(key_str), command_name(std::move(cmd_name)), description(std::move(description)) {
 }
 
-// ============================================================================
+// ============================================================================ 
 // KeyBindingManager Implementation
-// ============================================================================
+// ============================================================================ 
 
 KeyBindingManager::KeyBindingManager(CommandSystem* command_system) 
     : root_(std::make_unique<TrieNode>()), // Initialize root_
@@ -272,6 +272,20 @@ void KeyBindingManager::bind(const std::string& key_str, std::string cmd_name, c
     bind(KeySequence(key_str), std::move(cmd_name), description);
 }
 
+// Helper to find the TrieNode for a given sequence
+KeyBindingManager::TrieNode* KeyBindingManager::find_node(const KeySequence& sequence) const {
+    TrieNode* current = root_.get();
+    for (const Key& key : sequence.keys()) {
+        auto it = current->children.find(key);
+        if (it == current->children.end()) {
+            return nullptr;
+        }
+        current = it->second.get();
+    }
+    return current;
+}
+
+
 void KeyBindingManager::unbind(const KeySequence& sequence) {
     if (sequence.empty()) return;
 
@@ -332,33 +346,34 @@ KeyProcessingResult KeyBindingManager::process_key(const Key& key) {
     sequence_start_time_ = std::chrono::steady_clock::now();
     
     // Check for exact binding
-    std::optional<KeyBinding> exact_binding = find_exact_binding(current_sequence_);
-    if (exact_binding.has_value()) {
+    TrieNode* node = find_node(current_sequence_);
+    if (node && node->binding.has_value()) {
         // Found exact match
+        KeyBinding bound_command = node->binding.value();
         clear_partial_sequence();
         
         try {
             // Execute the command via the CommandSystem
             if (command_system_) {
-                CommandResult cmd_result = command_system_->execute(exact_binding->command_name, {}); // No args for now
-                return {cmd_result.success ? KeyResult::Executed : KeyResult::Failed, cmd_result};
+                CommandResult cmd_result = command_system_->execute(bound_command.command_name, {}); // No args for now
+                return KeyProcessingResult(cmd_result.success ? KeyResult::Executed : KeyResult::Failed, cmd_result);
             }
-            return {KeyResult::Failed, CommandResult{false, "No CommandSystem available"}};
+            return KeyProcessingResult(KeyResult::Failed, CommandResult{false, "No CommandSystem available"});
         } catch (const std::exception& e) {
-            return {KeyResult::Failed, CommandResult{false, std::string("Command execution failed: ") + e.what()}};
+            return KeyProcessingResult(KeyResult::Failed, CommandResult{false, std::string("Command execution failed: ") + e.what()});
         } catch (...) {
-            return {KeyResult::Failed, CommandResult{false, "Command execution failed with unknown error"}};
+            return KeyProcessingResult(KeyResult::Failed, CommandResult{false, "Command execution failed with unknown error"});
         }
     }
     
     // Check if this could be a prefix for other bindings
-    if (has_prefix_bindings_impl(current_sequence_)) {
-        return {KeyResult::Partial, std::nullopt};
+    if (node && !node->children.empty()) {
+        return KeyProcessingResult(KeyResult::Partial);
     }
     
     // No binding found, clear sequence and return unbound
     clear_partial_sequence();
-    return {KeyResult::Unbound, std::nullopt};
+    return KeyProcessingResult(KeyResult::Unbound);
 }
 
 KeyProcessingResult KeyBindingManager::process_key(const std::string& key_str) {
@@ -419,18 +434,6 @@ bool KeyBindingManager::has_sequence_timed_out() const {
     return (now - sequence_start_time_) > sequence_timeout_;
 }
 
-// Helper function to find a node in the trie
-KeyBindingManager::TrieNode* KeyBindingManager::find_node(const KeySequence& sequence) const {
-    TrieNode* current = root_.get();
-    for (const Key& key : sequence.keys()) {
-        if (current->children.find(key) == current->children.end()) {
-            return nullptr;
-        }
-        current = current->children[key].get();
-    }
-    return current;
-}
-
 // Helper to traverse trie and collect all bindings
 void KeyBindingManager::collect_bindings(TrieNode* node, KeySequence current_prefix, std::vector<KeyBinding>& result) const {
     if (!node) return;
@@ -446,19 +449,5 @@ void KeyBindingManager::collect_bindings(TrieNode* node, KeySequence current_pre
     }
 }
 
-std::optional<KeyBinding> KeyBindingManager::find_exact_binding(const KeySequence& sequence) const {
-    TrieNode* node = find_node(sequence);
-    if (node && node->binding.has_value()) {
-        return node->binding;
-    }
-    return std::nullopt;
-}
-
-bool KeyBindingManager::has_prefix_bindings_impl(const KeySequence& sequence) const {
-    TrieNode* node = find_node(sequence);
-    return node && !node->children.empty();
-}
-
-} // namespace lumacs
 
 } // namespace lumacs

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1188 - 49
src/lua_api.cpp


+ 68 - 11
src/minibuffer_manager.cpp

@@ -3,7 +3,9 @@
 #include "lumacs/lua_api.hpp"     // For LuaApi interaction
 #include "lumacs/minibuffer_mode_hash.hpp" // New include for MinibufferMode hash
 #include "lumacs/completion_system.hpp" // Include for CompletionSystem
+#include "lumacs/command_system.hpp" // For Command and CommandSystem
 #include <functional> // Required for std::function
+#include <sstream>    // For parsing command line
 
 #include <iostream> // TODO: Replace with proper logging
 
@@ -29,11 +31,11 @@ void MinibufferManager::activate_minibuffer(MinibufferMode mode, const std::stri
     input_buffer_ = ""; // Clear previous input
     on_submit_callback_ = on_submit;
     on_cancel_callback_ = on_cancel;
+    cursor_position_ = 0; // Initialize cursor position
     
     // Set current history manager based on mode
     current_history_ = &histories_[mode];
 
-    // TODO: Update completion candidates based on mode
     update_completion_candidates(); 
 }
 
@@ -46,6 +48,7 @@ void MinibufferManager::deactivate_minibuffer() {
     current_history_ = nullptr; // Clear current history pointer
     completion_candidates_.clear();
     completion_index_ = 0;
+    cursor_position_ = 0; // Reset cursor position
 }
 
 bool MinibufferManager::handle_key_event(const std::string& key_name) {
@@ -67,11 +70,34 @@ bool MinibufferManager::handle_key_event(const std::string& key_name) {
         deactivate_minibuffer();
         return true;
     } else if (key_name == "BackSpace") {
-        if (!input_buffer_.empty()) {
-            input_buffer_.pop_back();
+        if (cursor_position_ > 0) {
+            input_buffer_.erase(cursor_position_ - 1, 1);
+            cursor_position_--;
             update_completion_candidates(); // Update completions on backspace
         }
         return true;
+    } else if (key_name == "Delete") {
+        if (cursor_position_ < input_buffer_.length()) {
+            input_buffer_.erase(cursor_position_, 1);
+            update_completion_candidates(); // Update completions on delete
+        }
+        return true;
+    } else if (key_name == "Left" || key_name == "ArrowLeft") {
+        if (cursor_position_ > 0) {
+            cursor_position_--;
+        }
+        return true;
+    } else if (key_name == "Right" || key_name == "ArrowRight") {
+        if (cursor_position_ < input_buffer_.length()) {
+            cursor_position_++;
+        }
+        return true;
+    } else if (key_name == "Home") {
+        cursor_position_ = 0;
+        return true;
+    } else if (key_name == "End") {
+        cursor_position_ = input_buffer_.length();
+        return true;
     } else if (key_name == "Tab") {
         complete();
         return true;
@@ -82,18 +108,15 @@ bool MinibufferManager::handle_key_event(const std::string& key_name) {
         history_next();
         return true;
     } else if (key_name.length() == 1) { // Regular character input
-        input_buffer_ += key_name;
+        input_buffer_.insert(cursor_position_, key_name);
+        cursor_position_++;
         update_completion_candidates(); // Update completions on new input
         return true;
     }
-    // TODO: Handle other special keys like Left/Right arrows, Home/End, Delete
-    // For now, if not handled, let the UI decide (though it shouldn't for minibuffer active)
     return false; 
 }
 
 std::string MinibufferManager::get_prompt() const {
-    // In a real scenario, this might combine prompt_text_ with current completions
-    // and possibly a visual indicator for current completion index.
     return prompt_text_;
 }
 
@@ -101,6 +124,18 @@ std::string MinibufferManager::get_input_buffer() const {
     return input_buffer_;
 }
 
+size_t MinibufferManager::get_cursor_position() const {
+    return cursor_position_;
+}
+
+MinibufferMode MinibufferManager::get_current_mode() const { return current_mode_; }
+
+void MinibufferManager::set_input_buffer(const std::string& input) {
+    input_buffer_ = input;
+    cursor_position_ = input_buffer_.length();
+    update_completion_candidates();
+}
+
 void MinibufferManager::add_to_history() {
     if (current_history_) {
         current_history_->add_item(input_buffer_);
@@ -110,14 +145,16 @@ void MinibufferManager::add_to_history() {
 void MinibufferManager::history_previous() {
     if (current_history_) {
         input_buffer_ = current_history_->previous();
-        // TODO: Update completion candidates based on history item
+        cursor_position_ = input_buffer_.length(); // Move cursor to end of history item
+        update_completion_candidates();
     }
 }
 
 void MinibufferManager::history_next() {
     if (current_history_) {
         input_buffer_ = current_history_->next();
-        // TODO: Update completion candidates based on history item
+        cursor_position_ = input_buffer_.length(); // Move cursor to end of history item
+        update_completion_candidates();
     }
 }
 
@@ -153,4 +190,24 @@ void MinibufferManager::complete() {
     }
 }
 
-} // namespace lumacs
+CommandResult MinibufferManager::parse_and_execute_command_string(const std::string& command_line) {
+    std::string command_name;
+    std::vector<std::string> args;
+    std::stringstream ss(command_line);
+    
+    ss >> command_name; // Extract command name
+
+    std::string arg;
+    while (ss >> arg) { // Extract arguments
+        args.push_back(arg);
+    }
+
+    // Directly execute the command. If the command is interactive and needs more arguments,
+    // the execute method will return PendingInput. This assumes the caller (e.g., Lua API)
+    // will know how to handle CommandStatus::PendingInput if it tries to execute an
+    // interactive command that requires more input than provided in the string.
+    // However, for typical Lua API calls, commands are expected to be fully specified.
+    return core_.command_system().execute(command_name, args);
+}
+
+} // namespace lumacs

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů