Explorar o código

refactor(core): Decouple EditorCore and refine Lua API interactions

Bernardo Magri hai 1 mes
pai
achega
7ab3711c76

+ 4 - 0
documentation/PLAN.md

@@ -195,6 +195,7 @@ This phase aimed to enhance the Command System to support robust, type-safe, and
 - ✅ **EditorCore Decomposition (Rectangle Operations Management)**: Extracted rectangle operations management into a dedicated `RectangleManager` class.
 - ✅ **Testing Infrastructure (Framework Integration)**: Integrated Google Test and removed custom test framework.
 - ✅ **Testing Infrastructure (Migrate Existing Tests)**: Converted `test_buffer.cpp` and `test_editor_core.cpp` to Google Test format.
+- ✅ **Build Fixes**: Resolved circular dependencies, missing definitions, and GTK4 incompatibilities across the codebase.
 
 ## Technical Debt/Notes
 
@@ -208,6 +209,9 @@ This phase aimed to enhance the Command System to support robust, type-safe, and
 - **Testing**: Tests temporarily disabled to unblock build. Need to restore/rewrite.
 - **Rendering Performance**: Rendering cache was temporarily removed during Pango refactor. Monitor performance on large buffers.
 - **Focus Stability**: GTK frontend caches active_window_ during redraw cycles to prevent race conditions in multi-window async rendering
+- **GTK Popup**: `GtkCompletionPopup` logic is stubbed to fix build; needs full GTK4 migration (Popover).
+- **TUI ISearch**: ISearch highlighting temporarily disabled in TUI.
+- **Backspace Bug**: Backspace functionality is currently buggy, leading to the cursor staying in place after deletion, and requires further investigation.
 
 ## General Instructions for the LLM Executor
 

+ 7 - 2
include/lumacs/buffer.hpp

@@ -238,9 +238,13 @@ public:
     // === Events & Hooks ===
 
     using BufferEventCallback = std::function<void(const BufferEventData&)>;
+    using BufferEventCallbackHandle = size_t;
 
     /// @brief Register a callback for buffer events.
-    void on_buffer_event(BufferEventCallback callback);
+    BufferEventCallbackHandle on_buffer_event(BufferEventCallback callback);
+
+    /// @brief Disconnect a previously registered buffer event callback.
+    void disconnect_event_callback(BufferEventCallbackHandle handle);
 
     /// @brief Get the language/mode identifier (e.g., "cpp", "lua").
     [[nodiscard]] const std::string& language() const noexcept { return language_; }
@@ -306,7 +310,8 @@ private:
     std::vector<std::vector<StyledRange>> line_styles_;
 
     // Event callbacks
-    std::vector<BufferEventCallback> event_callbacks_;
+    std::map<BufferEventCallbackHandle, BufferEventCallback> event_callbacks_;
+    size_t next_callback_handle_ = 0;
 
     // Undo/Redo stacks
     std::vector<UndoState> undo_stack_;

+ 4 - 0
include/lumacs/buffer_manager.hpp

@@ -23,6 +23,10 @@ public:
     /// @param name The name of the new buffer (defaults to "*scratch*").
     void new_buffer(std::string name = "*scratch*");
 
+    /// @brief Creates a buffer but does not attach it to any window.
+    /// Useful for initialization or background buffer creation.
+    std::shared_ptr<Buffer> create_buffer_no_window(std::string name);
+
     /// @brief Loads the content of a file into a new or existing buffer.
     /// If the file is already open, switches to that buffer.
     /// @param path The path to the file to load.

+ 12 - 9
include/lumacs/command_system.hpp

@@ -8,10 +8,11 @@
 #include <variant>
 
 #include "lumacs/i_command_target.hpp" // New include for ICommandTarget
+// #include "lumacs/editor_core.hpp" // Required for EditorCore definition - moved to .cpp
 
 namespace lumacs {
 
-// class EditorCore; // No longer needed directly by CommandContext
+class EditorCore; // Forward declaration
 class MinibufferManager; // Forward declare MinibufferManager for interactive args
 
 /// @brief Defines the status of a command execution.
@@ -32,7 +33,7 @@ struct CommandResult {
 class CommandContext {
 public:
     CommandContext(ICommandTarget& target, MinibufferManager& minibuffer_manager, const std::vector<std::string>& args)
-        : target_(target), minibuffer_manager_(minibuffer_manager), args_(args) {}
+        : target_(target), minibuffer_manager_(minibuffer_manager), args_(args) {} // Corrected initializer list order
 
     /// @brief Get argument at index as string.
     std::optional<std::string> get_string_arg(size_t index) const;
@@ -51,12 +52,13 @@ public:
     // 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_;
+    /// @brief Get all gathered arguments.
+    [[nodiscard]] const std::vector<std::string>& get_args() const { return args_; }
 
 private:
-    ICommandTarget& target_; // Changed from EditorCore& core_
-    MinibufferManager& minibuffer_manager_;
+    ICommandTarget& target_; // Declared first
+    MinibufferManager& minibuffer_manager_; // Declared second
+    std::vector<std::string> args_; // Declared third
 };
 
 /// @brief Type for command functions.
@@ -111,13 +113,14 @@ private:
     EditorCore& core_; // Reference to EditorCore
     MinibufferManager& minibuffer_manager_; // Reference to MinibufferManager
     std::unordered_map<std::string, Command> commands_;
+    std::unordered_map<std::string, std::string> alias_map_; // Maps alias to canonical command name
 
     // 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.
+
+    CommandResult process_next_interactive_argument();
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 71 - 145
include/lumacs/editor_core.hpp

@@ -2,60 +2,50 @@
 
 #include "lumacs/buffer.hpp"
 #include "lumacs/window.hpp"
-// #include "lumacs/kill_ring.hpp" // Now using KillRingManager
 #include "lumacs/theme.hpp"
 #include "lumacs/config.hpp"
 #include "lumacs/keybinding.hpp"
 #include "lumacs/modeline.hpp"
-#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 "lumacs/plugin_manager.hpp" // New include for PluginManager
-#include "lumacs/buffer_manager.hpp" // New include for BufferManager
-#include "lumacs/window_manager.hpp" // New include for WindowManager
-#include "lumacs/kill_ring_manager.hpp" // New include for KillRingManager
-#include "lumacs/register_manager.hpp" // New include for RegisterManager
-#include "lumacs/macro_manager.hpp" // New include for MacroManager
-#include "lumacs/rectangle_manager.hpp" // New include for RectangleManager
+#include "lumacs/minibuffer_manager.hpp"
+#include "lumacs/completion_system.hpp"
+#include "lumacs/ui_interface.hpp"
+#include "lumacs/i_command_target.hpp"
+#include "lumacs/plugin_manager.hpp"
+#include "lumacs/buffer_manager.hpp"
+#include "lumacs/window_manager.hpp"
+#include "lumacs/kill_ring_manager.hpp"
+#include "lumacs/register_manager.hpp"
+#include "lumacs/macro_manager.hpp"
+#include "lumacs/rectangle_manager.hpp"
+#include "lumacs/layout_node.hpp"
 #include <memory>
 #include <functional>
 #include <vector>
 #include <list>
 #include <unordered_map>
-#include <chrono> // For std::chrono::steady_clock
-#include <optional> // For std::optional
+#include <chrono>
+#include <optional>
 
 namespace lumacs {
 
-class LuaApi; // Forward declaration
-class CommandSystem; // Forward declaration
-class PluginManager; // Forward declaration
-// class WindowManager; // Forward declaration already in window_manager.hpp
+class LuaApi;
+class CommandSystem;
+class PluginManager;
 
-/// @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 : public ICommandTarget { // Inherit from ICommandTarget
+class EditorCore : public ICommandTarget {
 public:
     EditorCore();
     ~EditorCore();
 
-    // Disable copy, allow move
     EditorCore(const EditorCore&) = delete;
     EditorCore& operator=(const EditorCore&) = delete;
-    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
+    EditorCore(EditorCore&&) noexcept = default;
+    EditorCore& operator=(EditorCore&&) noexcept = default;
 
     // === ICommandTarget Implementation ===
-    // These methods implement the ICommandTarget interface.
-
     // Buffer Management (Delegated to BufferManager)
-    [[nodiscard]] const Buffer& buffer() const noexcept override { return buffer_manager_->active_buffer()->get(); }
-    [[nodiscard]] Buffer& buffer() noexcept override { return buffer_manager_->active_buffer()->get(); }
+    [[nodiscard]] const Buffer& buffer() const noexcept override { return *buffer_manager_->active_buffer(); }
+    [[nodiscard]] Buffer& buffer() noexcept override { return *buffer_manager_->active_buffer(); }
     bool load_file(const std::filesystem::path& path) override { return buffer_manager_->load_file(path); }
     void new_buffer(std::string name = "*scratch*") override { buffer_manager_->new_buffer(std::move(name)); }
     [[nodiscard]] std::vector<std::string> get_buffer_names() const override { return buffer_manager_->get_buffer_names(); }
@@ -89,6 +79,8 @@ public:
     void goto_beginning() override;
     void goto_end() override;
     void goto_line(size_t line) override;
+
+    // Kill Ring (Delegated to KillRingManager)
     void kill_line() override;
     void kill_region() override;
     void copy_region_as_kill() override;
@@ -111,18 +103,28 @@ public:
     ThemeManager& theme_manager() override { return theme_manager_; }
     const ThemeManager& theme_manager() const override { return theme_manager_; }
 
+    // Registers (Delegated to RegisterManager)
+    void copy_to_register(char register_name, const std::string& text) override;
+    bool insert_register(char register_name) override;
+    void copy_region_to_register(char register_name) override;
+    bool yank_from_register(char register_name) override;
+
+    // Keyboard Macros (Delegated to MacroManager)
+    void start_kbd_macro() override;
+    void end_kbd_macro_or_call() override;
+    void record_key_sequence(const std::string& key_sequence) override;
+    [[nodiscard]] bool is_recording_macro() const noexcept override;
+
+    // Rectangles (Delegated to RectangleManager)
+    void kill_rectangle() override;
+    void yank_rectangle() override;
+    void string_rectangle(const std::string& text) override;
+
     // === 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(); 
 
-    // === Actions ===
-    // These methods trigger specific input modes in the UI.
-
     void enter_command_mode();
     void enter_buffer_switch_mode();
     void enter_kill_buffer_mode();
@@ -131,155 +133,79 @@ public:
     void enter_isearch_mode();
     void enter_isearch_backward_mode();
 
-    /// @brief Get detailed information about all buffers.
     [[nodiscard]] std::vector<BufferManager::BufferInfo> get_all_buffer_info() const { return buffer_manager_->get_all_buffer_info(); }
-
-    /// @brief Get the root node of the window layout tree.
     std::shared_ptr<LayoutNode> root_layout() const { return window_manager_->root_layout(); }
-
-    // === Viewport Management (Proxies to active window) ===
     
     const Viewport& viewport() const noexcept;
     void set_viewport_size(int width, int height);
     void adjust_scroll();
     std::pair<size_t, size_t> visible_line_range() const;
 
-    // === Event Callbacks ===
-
     using EventCallback = std::function<void(EditorEvent)>;
-
-    /// @brief Register a callback to be notified of editor events.
-    void on_event(EventCallback callback) {
-        event_callbacks_.push_back(std::move(callback));
-    }
-
-    /// @brief Clear all registered event callbacks.
-    void clear_event_callbacks() {
-        event_callbacks_.clear();
-    }
-
-    // === Undo/Redo ===
+    void on_event(EventCallback callback) { event_callbacks_.push_back(std::move(callback)); }
+    void clear_event_callbacks() { event_callbacks_.clear(); }
 
     bool undo();
     bool redo();
     bool can_undo() const;
     bool can_redo() const;
 
-    // === Kill Ring ===
-    // Delegated to KillRingManager
-    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;
-
-    /// @brief Access the global Kill Ring (clipboard history).
     [[nodiscard]] KillRingManager& kill_ring_manager() noexcept { return *kill_ring_manager_; }
     [[nodiscard]] const KillRingManager& kill_ring_manager() const noexcept { return *kill_ring_manager_; }
-
-    // === Registers ===
-    // Delegated to RegisterManager
-    void copy_to_register(char register_name, const std::string& text) override;
-    bool insert_register(char register_name) override;
-    void copy_region_to_register(char register_name) override;
-    bool yank_from_register(char register_name) override;
-
-    /// @brief Access the global Register Manager.
     [[nodiscard]] RegisterManager& register_manager() noexcept { return *register_manager_; }
     [[nodiscard]] const RegisterManager& register_manager() const noexcept { return *register_manager_; }
-
-    // === Keyboard Macros ===
-    // Delegated to MacroManager
-    void start_kbd_macro() override;
-    void end_kbd_macro_or_call() override;
-    void record_key_sequence(const std::string& key_sequence) override;
-    [[nodiscard]] bool is_recording_macro() const noexcept override { return macro_manager_->is_recording_macro(); }
-
-    /// @brief Access the global Macro Manager.
     [[nodiscard]] MacroManager& macro_manager() noexcept { return *macro_manager_; }
     [[nodiscard]] const MacroManager& macro_manager() const noexcept { return *macro_manager_; }
-
-    // === Rectangles ===
-    // Delegated to RectangleManager
-    void kill_rectangle() override;
-    void yank_rectangle() override;
-    void string_rectangle(const std::string& text) override;
-
-    /// @brief Access the global Rectangle Manager.
     [[nodiscard]] RectangleManager& rectangle_manager() noexcept { return *rectangle_manager_; }
     [[nodiscard]] const RectangleManager& rectangle_manager() const noexcept { return *rectangle_manager_; }
-
-    // === Key Binding System ===                                                                                                                                 
-    [[nodiscard]] KeyBindingManager& keybinding_manager() noexcept { return *keybinding_manager_; }                                                                
-    [[nodiscard]] const KeyBindingManager& keybinding_manager() const noexcept { return *keybinding_manager_; }                                                    
-                                                                                                                                                                  
-    // === Lua API ===                                                                                                                                            
+    [[nodiscard]] KeyBindingManager& keybinding_manager() noexcept { return *keybinding_manager_; }
+    [[nodiscard]] const KeyBindingManager& keybinding_manager() const noexcept { return *keybinding_manager_; }
     [[nodiscard]] LuaApi* lua_api() const { return lua_api_.get(); }
-    
-    // === Command System ===
     [[nodiscard]] CommandSystem& command_system() noexcept { return *command_system_; }
     [[nodiscard]] const CommandSystem& command_system() const noexcept { return *command_system_; }
-
-    // === Modeline Manager ===
-    [[nodiscard]] ModelineManager& modeline_manager() noexcept { return modeline_manager_; }
-    [[nodiscard]] const ModelineManager& modeline_manager() const noexcept { return modeline_manager_; }
-
-    // === Minibuffer Manager ===
-    [[nodiscard]] MinibufferManager& minibuffer_manager() noexcept { return *minibuffer_manager_; }
-    [[nodiscard]] const MinibufferManager& minibuffer_manager() const noexcept { return *minibuffer_manager_; }
-
-    // === Completion System ===
     [[nodiscard]] CompletionSystem& completion_system() noexcept { return *completion_system_; }
     [[nodiscard]] const CompletionSystem& completion_system() const noexcept { return *completion_system_; }
-
-    // === Plugin Manager ===
+    [[nodiscard]] ModelineManager& modeline_manager() noexcept { return modeline_manager_member_; }
+    [[nodiscard]] const ModelineManager& modeline_manager() const noexcept { return modeline_manager_member_; }
+    [[nodiscard]] MinibufferManager& minibuffer_manager() noexcept { return *minibuffer_manager_; }
+    [[nodiscard]] const MinibufferManager& minibuffer_manager() const noexcept { return *minibuffer_manager_; }
     [[nodiscard]] PluginManager& plugin_manager() noexcept { return *plugin_manager_; }
     [[nodiscard]] const PluginManager& plugin_manager() const noexcept { return *plugin_manager_; }
-
-    // === Buffer Manager ===
     [[nodiscard]] BufferManager& buffer_manager() noexcept { return *buffer_manager_; }
     [[nodiscard]] const BufferManager& buffer_manager() const noexcept { return *buffer_manager_; }
+    [[nodiscard]] WindowManager& window_manager() noexcept { return *window_manager_; }
+    [[nodiscard]] const WindowManager& window_manager() const noexcept { return *window_manager_; }
 
-    /// @brief Helper to collect all windows in traversal order.
-    /// @param node The current node to traverse.
-    /// @param windows The vector to populate with windows.
-    void collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) { window_manager_->collect_windows(node, windows); }
-
+    void collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows);
+    void emit_event(EditorEvent event);
 
 private:
-    // Word movement helpers
     Position calculate_forward_word_pos(Position start_pos);
     Position calculate_backward_word_pos(Position start_pos);
 
     std::string last_message_;
     std::optional<std::chrono::steady_clock::time_point> message_clear_time_;
-
     std::vector<EventCallback> event_callbacks_;
-
-    // Last yank position (for yank-pop)
     std::optional<Position> last_yank_start_;
     std::optional<Position> last_yank_end_;
 
-    // Subsystems
-    ThemeManager theme_manager_;
-    Config config_;
-    std::unique_ptr<CommandSystem> command_system_; // Must be declared before KeyBindingManager
-    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
-    std::unique_ptr<PluginManager> plugin_manager_; // Added missing member
-    std::unique_ptr<BufferManager> buffer_manager_; // New: BufferManager
-    std::unique_ptr<WindowManager> window_manager_; // New: WindowManager
-    std::unique_ptr<KillRingManager> kill_ring_manager_; // New: KillRingManager
-    std::unique_ptr<RegisterManager> register_manager_; // New: RegisterManager
-    std::unique_ptr<MacroManager> macro_manager_; // New: MacroManager
-    std::unique_ptr<RectangleManager> rectangle_manager_; // New: RectangleManager
-
-    void emit_event(EditorEvent event);
+    // Subsystems - Order matters for initialization based on dependencies
+    // Declared in dependency order
+    ThemeManager theme_manager_; // No dependencies
+    Config config_; // No dependencies
+    ModelineManager modeline_manager_member_; // Direct member, declared here
+    std::unique_ptr<LuaApi> lua_api_; // Depends on nothing
+    std::unique_ptr<CompletionSystem> completion_system_; // Depends on EditorCore (this)
+    std::unique_ptr<MinibufferManager> minibuffer_manager_; // Depends on EditorCore, LuaApi, CompletionSystem
+    std::unique_ptr<CommandSystem> command_system_; // Depends on EditorCore, MinibufferManager
+    std::unique_ptr<KeyBindingManager> keybinding_manager_; // Depends on CommandSystem
+    std::unique_ptr<PluginManager> plugin_manager_; // Depends on EditorCore, LuaApi
+    std::unique_ptr<BufferManager> buffer_manager_; // Depends on EditorCore
+    std::unique_ptr<WindowManager> window_manager_; // Depends on EditorCore, BufferManager
+    std::unique_ptr<KillRingManager> kill_ring_manager_; // No dependencies
+    std::unique_ptr<RegisterManager> register_manager_; // No dependencies
+    std::unique_ptr<MacroManager> macro_manager_; // Depends on EditorCore
+    std::unique_ptr<RectangleManager> rectangle_manager_; // Depends on EditorCore
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 8 - 7
include/lumacs/gtk_completion_popup.hpp

@@ -3,6 +3,7 @@
 #ifdef LUMACS_WITH_GTK
 
 #include <gtkmm.h>
+#include <sigc++/sigc++.h> // Added explicit include
 #include <vector>
 #include <string>
 
@@ -11,7 +12,7 @@
 namespace lumacs {
 
 /// @brief A GTK widget that displays a list of completion candidates in a popup.
-class GtkCompletionPopup : public Gtk::Window {
+class GtkCompletionPopup : public Gtk::Popover {
 public:
     GtkCompletionPopup();
     ~GtkCompletionPopup() override;
@@ -19,10 +20,10 @@ public:
     /// @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);
+    /// @param relative_to The widget to position the popup relative to.
+    /// @param x_offset The x-offset from the relative widget's origin.
+    /// @param y_offset The y-offset from the relative widget's origin.
+    void show_popup(const std::vector<CompletionCandidate>& candidates, size_t active_index, Gtk::Widget& relative_to, int x_offset, int y_offset);
 
     /// @brief Hides the completion popup.
     void hide_popup();
@@ -37,10 +38,10 @@ public:
     std::optional<CompletionCandidate> get_selected_candidate() const;
 
     /// Signals
-    using type_signal_candidate_selected = sigc::signal<void, CompletionCandidate>;
+    using type_signal_candidate_selected = sigc::signal<void(CompletionCandidate)>;
     type_signal_candidate_selected signal_candidate_selected();
 
-    using type_signal_cancelled = sigc::signal<void>;
+    using type_signal_cancelled = sigc::signal<void()>;
     type_signal_cancelled signal_cancelled();
 
 protected:

+ 19 - 2
include/lumacs/i_command_target.hpp

@@ -65,7 +65,7 @@ public:
     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 kill_region() = 0; // Fixed: Removed duplicate 'void'
     virtual void copy_region_as_kill() = 0;
     virtual void yank() = 0;
     virtual void yank_pop() = 0;
@@ -85,6 +85,23 @@ public:
     // Theme Management
     virtual ThemeManager& theme_manager() = 0;
     virtual const ThemeManager& theme_manager() const = 0;
+
+    // Registers Management
+    virtual void copy_to_register(char register_name, const std::string& text) = 0;
+    virtual bool insert_register(char register_name) = 0;
+    virtual void copy_region_to_register(char register_name) = 0;
+    virtual bool yank_from_register(char register_name) = 0;
+
+    // Keyboard Macro Management
+    virtual void start_kbd_macro() = 0;
+    virtual void end_kbd_macro_or_call() = 0;
+    virtual void record_key_sequence(const std::string& key_sequence) = 0;
+    virtual bool is_recording_macro() const noexcept = 0;
+
+    // Rectangle Operations Management
+    virtual void kill_rectangle() = 0;
+    virtual void yank_rectangle() = 0;
+    virtual void string_rectangle(const std::string& text) = 0;
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 2 - 2
include/lumacs/lua_api.hpp

@@ -57,7 +57,7 @@ public:
     /// @brief Process a key press via the Lua layer/KeyBindingManager.
     /// @param key The key name/sequence.
     /// @return The result of processing (Unbound, Executed, etc.).
-    KeyResult process_key(const std::string& key);
+    KeyProcessingResult process_key(const std::string& key); // Changed return type
 
     /// @brief Get all registered Lua key bindings (Legacy).
     [[nodiscard]] std::map<std::string, sol::function> key_bindings() const {
@@ -83,4 +83,4 @@ private:
     void register_functions();
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 30 - 8
include/lumacs/minibuffer_manager.hpp

@@ -4,6 +4,7 @@
 #include <optional>
 #include <unordered_map> // For std::unordered_map
 
+#include "lumacs/command_system.hpp" // Include for CommandResult definition
 #include "lumacs/history_manager.hpp" // New include
 #include "lumacs/minibuffer_mode_hash.hpp" // Include for MinibufferMode hash specialization
 #include "lumacs/minibuffer_mode.hpp" // Include for MinibufferMode enum definition
@@ -55,12 +56,24 @@ public:
     void history_previous();
     void history_next();
 
-    // Completion related methods (will be expanded in Subtask Z.3)
+    // Completion related methods
     void update_completion_candidates();
     std::vector<CompletionCandidate> get_completion_candidates() const;
     std::optional<std::string> get_current_completion() const;
     void complete(); // For tab completion
 
+    // ISearch related methods and state
+    bool is_isearch_active() const { return isearch_active_; }
+    std::optional<Range> get_isearch_match_range() const { return isearch_match_range_; }
+    bool is_isearch_failed() const { return isearch_failed_; }
+
+    /// @brief Parses a command string and executes the corresponding command.
+
+    /// @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); // Made public
+
 private:
     EditorCore& core_;
     LuaApi& lua_api_;
@@ -81,14 +94,23 @@ private:
     std::vector<CompletionCandidate> completion_candidates_;
     size_t completion_index_ = 0;
 
+    // ISearch state
+    bool isearch_active_ = false;
+    std::optional<Range> isearch_match_range_;
+    bool isearch_failed_ = false;
+    std::optional<Position> isearch_start_cursor_; // Cursor position when ISearch started
+    bool isearch_direction_forward_ = true; // True for forward search, false for backward
+    std::string last_search_query_; // Last successful or attempted query
+
+    // ISearch control methods
+    void start_isearch(bool forward);
+    void update_isearch(const std::string& query);
+    void next_isearch_match();
+    void previous_isearch_match();
+    void stop_isearch();
+
     /// @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
-
+} // namespace lumacs

+ 1 - 0
include/lumacs/window.hpp

@@ -24,6 +24,7 @@ class Window {
 public:
     /// @brief Create a window displaying the given buffer.
     Window(std::shared_ptr<Buffer> buffer);
+    ~Window();
 
     /// @brief Change the buffer displayed in this window.
     void set_buffer(std::shared_ptr<Buffer> buffer);

+ 2 - 1
include/lumacs/window_manager.hpp

@@ -5,6 +5,7 @@
 #include <list> // For std::list
 #include "lumacs/window.hpp" // For Window and LayoutNode
 #include "lumacs/buffer.hpp" // For shared_ptr<Buffer>
+#include "lumacs/layout_node.hpp" // New include for LayoutNode
 
 namespace lumacs {
 
@@ -58,4 +59,4 @@ private:
                             std::shared_ptr<LayoutNode> replacement);
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 12 - 5
scripts/clean.sh

@@ -2,6 +2,7 @@
 #include <fstream>
 #include <sstream>
 #include <algorithm>
+#include <iostream> // Required for std::cerr debugging
 
 namespace lumacs {
 
@@ -201,7 +202,7 @@ void Buffer::erase_char(Position pos) {
         return; // Nothing to delete
     }
 
-    pos = clamp_position(pos);
+    pos = clamp_position(pos); // Clamp the target position for deletion
 
     if (pos.column > 0) {
         // Delete character before cursor - clear styles for this line
@@ -439,8 +440,14 @@ void Buffer::clear_line_styles(size_t line) {
 
 // === Events & Hooks ===
 
-void Buffer::on_buffer_event(BufferEventCallback callback) {
-    event_callbacks_.push_back(std::move(callback));
+Buffer::BufferEventCallbackHandle Buffer::on_buffer_event(BufferEventCallback callback) {
+    BufferEventCallbackHandle handle = next_callback_handle_++;
+    event_callbacks_[handle] = std::move(callback);
+    return handle;
+}
+
+void Buffer::disconnect_event_callback(BufferEventCallbackHandle handle) {
+    event_callbacks_.erase(handle);
 }
 
 void Buffer::emit_event(BufferEvent event, size_t line) {
@@ -449,8 +456,8 @@ void Buffer::emit_event(BufferEvent event, size_t line) {
     data.line = line;
     data.language = language_;
 
-    for (const auto& callback : event_callbacks_) {
-        callback(data);
+    for (const auto& pair : event_callbacks_) {
+        pair.second(data); // Call the std::function in the pair
     }
 }
 

+ 8 - 3
src/buffer_manager.cpp

@@ -8,13 +8,18 @@ namespace lumacs {
 BufferManager::BufferManager(EditorCore& core) : core_(core) {
 }
 
+std::shared_ptr<Buffer> BufferManager::create_buffer_no_window(std::string name) {
+    auto new_buf = std::make_shared<Buffer>(std::move(name));
+    buffers_.push_back(new_buf);
+    return new_buf;
+}
+
 void BufferManager::new_buffer(std::string name) {
-    auto new_buffer = std::make_shared<Buffer>(std::move(name));
-    buffers_.push_back(new_buffer);
+    auto new_buf = create_buffer_no_window(std::move(name));
     
     // Set this new buffer in the active window
     if (core_.active_window()) {
-        core_.active_window()->set_buffer(new_buffer);
+        core_.active_window()->set_buffer(new_buf);
     } else {
         // This case should ideally not happen if EditorCore initializes properly
         // For robustness, create a default window if none exists

+ 10 - 4
src/command_system.cpp

@@ -1,5 +1,5 @@
 #include "lumacs/command_system.hpp"
-#include "lumacs/editor_core.hpp" 
+#include "lumacs/editor_core.hpp" // Now included here for full definition
 #include "lumacs/minibuffer_manager.hpp" // Added for interactive argument gathering
 #include "lumacs/i_command_target.hpp" // Added for ICommandTarget
 #include <algorithm> // For std::sort
@@ -32,8 +32,14 @@ std::optional<int> CommandContext::get_int_arg(size_t index) const {
 
 // 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; }
+std::optional<std::string> CommandContext::read_string(const std::string& prompt) { 
+    (void)prompt; 
+    return std::nullopt; 
+}
+std::optional<std::string> CommandContext::read_file_path(const std::string& prompt) { 
+    (void)prompt; 
+    return std::nullopt; 
+}
 
 
 // --- CommandSystem Implementations ---
@@ -239,4 +245,4 @@ std::optional<std::string> CommandSystem::get_command_interactive_spec(const std
     return std::nullopt;
 }
 
-} // namespace lumacs
+} // namespace lumacs

+ 148 - 164
src/editor_core.cpp

@@ -16,41 +16,31 @@
 namespace lumacs {
 
 EditorCore::EditorCore() :
-    // root_node_(), // Managed by WindowManager
-    // active_window_(), // Managed by WindowManager
     last_message_(),
     message_clear_time_(),
     event_callbacks_(),
-    // kill_ring_(), // Managed by KillRingManager
     last_yank_start_(),
     last_yank_end_(),
-    // registers_(), // Managed by RegisterManager
-    // current_macro_(), // Managed by MacroManager
-    // last_macro_(), // Managed by MacroManager
-    // recording_macro_(false), // Managed by MacroManager
-    // rectangle_kill_ring_(), // Managed by RectangleManager
     theme_manager_(),
     config_(),
-    // Subsystem initializations - order matters for dependencies
-    lua_api_(std::make_unique<LuaApi>()), // LuaApi is a dependency for PluginManager and MinibufferManager
-    completion_system_(std::make_unique<CompletionSystem>(*this)), // CompletionSystem is a dependency for MinibufferManager
-    minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)), // MinibufferManager is a dependency for CommandSystem
-    command_system_(std::make_unique<CommandSystem>(*this, *minibuffer_manager_)), // CommandSystem is a dependency for KeyBindingManager
-    keybinding_manager_(std::make_unique<KeyBindingManager>(*command_system_)), // KeyBindingManager needs CommandSystem
-    plugin_manager_(std::make_unique<PluginManager>(*this, *lua_api_)), // PluginManager needs EditorCore and LuaApi
-    buffer_manager_(std::make_unique<BufferManager>(*this)), // BufferManager needs EditorCore
-    window_manager_(std::make_unique<WindowManager>(*this)), // WindowManager needs EditorCore and BufferManager (implicitly via EditorCore)
-    kill_ring_manager_(std::make_unique<KillRingManager>()), // KillRingManager is simple, no dependencies here
-    register_manager_(std::make_unique<RegisterManager>()), // RegisterManager is simple
-    macro_manager_(std::make_unique<MacroManager>(*this)), // MacroManager needs EditorCore
-    rectangle_manager_(std::make_unique<RectangleManager>(*this)) // RectangleManager needs EditorCore
+    modeline_manager_member_(), // Direct member
+    // Subsystem initializations - order matters for dependencies based on member declaration order in .hpp
+    // Corrected Order based on dependencies and declaration order:
+    lua_api_(std::make_unique<LuaApi>()),
+    completion_system_(std::make_unique<CompletionSystem>(*this)),
+    minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)),
+    command_system_(std::make_unique<CommandSystem>(*this, *minibuffer_manager_)),
+    keybinding_manager_(std::make_unique<KeyBindingManager>(command_system_.get())), // Pass raw pointer
+    plugin_manager_(std::make_unique<PluginManager>(*this, *lua_api_)),
+    buffer_manager_(std::make_unique<BufferManager>(*this)),
+    window_manager_(std::make_unique<WindowManager>(*this)),
+    kill_ring_manager_(std::make_unique<KillRingManager>()),
+    register_manager_(std::make_unique<RegisterManager>()),
+    macro_manager_(std::make_unique<MacroManager>(*this)),
+    rectangle_manager_(std::make_unique<RectangleManager>(*this))
 {
     // LuaApi needs core_ pointer to be valid, so set it after constructor body starts
     lua_api_->set_core(*this);
-
-    // Initial buffer and window creation is now managed by BufferManager and WindowManager.
-    // The WindowManager constructor will create the initial window and assign it a buffer
-    // using the BufferManager.
     
     // Initialize themes
     theme_manager_.create_default_themes();
@@ -62,83 +52,39 @@ EditorCore::EditorCore() :
 
 EditorCore::~EditorCore() = default;
 
-// === Window Management (Delegated to WindowManager) ===
-
-// These are still in EditorCore because they are part of the ICommandTarget interface
-// but their implementation delegates to WindowManager.
-
-/*
-// Helper to recursively replace a window in the tree (Moved to WindowManager)
-bool replace_window_node(...) { ... }
-
-// Recursive parent finder (Moved to WindowManager)
-LayoutNode* find_parent_of_node(...) { ... }
-
-// Recursive leaf finder (Moved to WindowManager)
-LayoutNode* find_node_with_window(...) { ... }
-*/
-
-void EditorCore::split_horizontally() {
-    window_manager_->split_horizontally();
-}
-
-void EditorCore::split_vertically() {
-    window_manager_->split_vertically();
-}
-
-void EditorCore::close_active_window() {
-    window_manager_->close_active_window();
-}
-
-void EditorCore::collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) {
-    window_manager_->collect_windows(node, windows);
-}
-
-void EditorCore::next_window() {
-    window_manager_->next_window();
-}
-
-void EditorCore::next_window_safe() {
-    next_window();
-}
-
-bool EditorCore::set_active_window(std::shared_ptr<Window> window) {
-    return window_manager_->set_active_window(window);
-}
-
 // === Cursor Proxies ===
 
 Position EditorCore::cursor() const noexcept {
-    return active_window()->cursor(); // Uses delegated active_window()
+    return window_manager_->active_window()->cursor(); 
 }
 
 void EditorCore::set_cursor(Position pos) {
-    active_window()->set_cursor(pos); // Uses delegated active_window()
+    window_manager_->active_window()->set_cursor(pos); 
     emit_event(EditorEvent::CursorMoved);
 }
 
 void EditorCore::move_up() {
-    active_window()->move_up();
+    window_manager_->active_window()->move_up();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_down() {
-    active_window()->move_down();
+    window_manager_->active_window()->move_down();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_left() {
-    active_window()->move_left();
+    window_manager_->active_window()->move_left();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_right() {
-    active_window()->move_right();
+    window_manager_->active_window()->move_right();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_to_line_start() {
-    active_window()->move_to_line_start();
+    window_manager_->active_window()->move_to_line_start();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_to_line_end() {
-    active_window()->move_to_line_end();
+    window_manager_->active_window()->move_to_line_end();
     emit_event(EditorEvent::CursorMoved);
 }
 
@@ -149,20 +95,20 @@ static bool is_word_char(char c) {
 }
 
 void EditorCore::move_forward_word() {
-    auto new_pos = calculate_forward_word_pos(active_window()->cursor());
-    active_window()->set_cursor(new_pos);
+    auto new_pos = calculate_forward_word_pos(window_manager_->active_window()->cursor());
+    window_manager_->active_window()->set_cursor(new_pos);
     emit_event(EditorEvent::CursorMoved);
 }
 
 void EditorCore::move_backward_word() {
-    auto new_pos = calculate_backward_word_pos(active_window()->cursor());
-    active_window()->set_cursor(new_pos);
+    auto new_pos = calculate_backward_word_pos(window_manager_->active_window()->cursor());
+    window_manager_->active_window()->set_cursor(new_pos);
     emit_event(EditorEvent::CursorMoved);
 }
 
 Position EditorCore::calculate_forward_word_pos(Position start_pos) {
     // Delegates to active buffer
-    auto& buf = buffer(); 
+    auto& buf = *buffer_manager_->active_buffer(); 
     auto cursor = start_pos;
     
     // Check if we are at the end of buffer
@@ -204,7 +150,7 @@ Position EditorCore::calculate_forward_word_pos(Position start_pos) {
 
 Position EditorCore::calculate_backward_word_pos(Position start_pos) {
     // Delegates to active buffer
-    auto& buf = buffer(); 
+    auto& buf = *buffer_manager_->active_buffer(); 
     auto cursor = start_pos;
 
     // Skip whitespace and punctuation backwards
@@ -237,8 +183,8 @@ Position EditorCore::calculate_backward_word_pos(Position start_pos) {
 }
 
 void EditorCore::page_up() {
-    auto& viewport = active_window()->viewport(); // Uses delegated active_window()
-    auto cursor = active_window()->cursor(); // Uses delegated active_window()
+    auto& viewport = window_manager_->active_window()->viewport(); 
+    auto cursor = window_manager_->active_window()->cursor(); 
 
     // Move up by viewport height (minus 2 for overlap)
     int page_size = std::max(1, viewport.height - 2);
@@ -250,20 +196,20 @@ void EditorCore::page_up() {
     }
 
     // Keep column position if possible
-    auto& buf = buffer();
+    auto& buf = *buffer_manager_->active_buffer();
     const auto& line = buf.line(cursor.line);
     cursor.column = std::min(cursor.column, line.size());
 
-    active_window()->set_cursor(cursor); // Uses delegated active_window()
-    active_window()->adjust_scroll(); // Uses delegated active_window()
+    window_manager_->active_window()->set_cursor(cursor); 
+    window_manager_->active_window()->adjust_scroll(); 
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::page_down() {
-    auto& viewport = active_window()->viewport(); // Uses delegated active_window()
-    auto cursor = active_window()->cursor(); // Uses delegated active_window()
-    auto& buf = buffer();
+    auto& viewport = window_manager_->active_window()->viewport(); 
+    auto cursor = window_manager_->active_window()->cursor(); 
+    auto& buf = *buffer_manager_->active_buffer();
 
     // Move down by viewport height (minus 2 for overlap)
     int page_size = std::max(1, viewport.height - 2);
@@ -274,39 +220,39 @@ void EditorCore::page_down() {
     const auto& line = buf.line(cursor.line);
     cursor.column = std::min(cursor.column, line.size());
 
-    active_window()->set_cursor(cursor); // Uses delegated active_window()
-    active_window()->adjust_scroll(); // Uses delegated active_window()
+    window_manager_->active_window()->set_cursor(cursor); 
+    window_manager_->active_window()->adjust_scroll(); 
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::goto_beginning() {
     Position pos = {0, 0};
-    active_window()->set_cursor(pos); // Uses delegated active_window()
-    active_window()->adjust_scroll(); // Uses delegated active_window()
+    window_manager_->active_window()->set_cursor(pos); 
+    window_manager_->active_window()->adjust_scroll(); 
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::goto_end() {
-    auto& buf = buffer();
+    auto& buf = *buffer_manager_->active_buffer();
     size_t last_line = buf.line_count() > 0 ? buf.line_count() - 1 : 0;
     size_t last_col = buf.line(last_line).size();
     Position pos = {last_line, last_col};
 
-    active_window()->set_cursor(pos); // Uses delegated active_window()
-    active_window()->adjust_scroll(); // Uses delegated active_window()
+    window_manager_->active_window()->set_cursor(pos); 
+    window_manager_->active_window()->adjust_scroll(); 
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::goto_line(size_t line) {
-    auto& buf = buffer();
+    auto& buf = *buffer_manager_->active_buffer();
     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); // Uses delegated active_window()
-    active_window()->adjust_scroll(); // Uses delegated active_window()
+    window_manager_->active_window()->set_cursor(pos); 
+    window_manager_->active_window()->adjust_scroll(); 
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
@@ -314,32 +260,32 @@ void EditorCore::goto_line(size_t line) {
 // === Viewport Management (Proxies to active window) ===
 
 const Viewport& EditorCore::viewport() const noexcept {
-    return active_window()->viewport(); // Uses delegated active_window()
+    return window_manager_->active_window()->viewport(); 
 }
 
 void EditorCore::set_viewport_size(int width, int height) {
-    active_window()->set_viewport_size(width, height); // Uses delegated active_window()
+    window_manager_->active_window()->set_viewport_size(width, height); 
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::adjust_scroll() {
-    active_window()->adjust_scroll(); // Uses delegated active_window()
+    window_manager_->active_window()->adjust_scroll(); 
     emit_event(EditorEvent::ViewportChanged);
 }
 
 std::pair<size_t, size_t> EditorCore::visible_line_range() const {
-    return active_window()->visible_line_range(); // Uses delegated active_window()
+    return window_manager_->active_window()->visible_line_range(); 
 }
 
 // === Undo/Redo ===
 
 bool EditorCore::undo() {
-    auto& buf = buffer();
-    buf.save_undo_state(active_window()->cursor()); // Uses delegated active_window()
+    auto& buf = *buffer_manager_->active_buffer();
+    buf.save_undo_state(window_manager_->active_window()->cursor()); 
 
-    Position new_cursor = active_window()->cursor(); // Uses delegated active_window()
+    Position new_cursor = window_manager_->active_window()->cursor(); 
     if (buf.undo(new_cursor)) {
-        active_window()->set_cursor(new_cursor); // Uses delegated active_window()
+        window_manager_->active_window()->set_cursor(new_cursor); 
         emit_event(EditorEvent::CursorMoved);
         emit_event(EditorEvent::BufferModified);
         return true;
@@ -348,10 +294,10 @@ bool EditorCore::undo() {
 }
 
 bool EditorCore::redo() {
-    auto& buf = buffer();
-    Position new_cursor = active_window()->cursor(); // Uses delegated active_window()
+    auto& buf = *buffer_manager_->active_buffer();
+    Position new_cursor = window_manager_->active_window()->cursor(); 
     if (buf.redo(new_cursor)) {
-        active_window()->set_cursor(new_cursor); // Uses delegated active_window()
+        window_manager_->active_window()->set_cursor(new_cursor); 
         emit_event(EditorEvent::CursorMoved);
         emit_event(EditorEvent::BufferModified);
         return true;
@@ -360,56 +306,52 @@ bool EditorCore::redo() {
 }
 
 bool EditorCore::can_undo() const {
-    return buffer().can_undo();
+    return buffer_manager_->active_buffer()->can_undo();
 }
 
 bool EditorCore::can_redo() const {
-    return buffer().can_redo();
+    return buffer_manager_->active_buffer()->can_redo();
 }
 
 // === Kill Ring (Delegated to KillRingManager) ===
 
 void EditorCore::kill_line() {
-    auto& buf = buffer();
-    auto cursor = active_window()->cursor();
-    const auto& line = buf.line(cursor.line);
-
-    // If at end of line, kill the newline (join with next line)
-    if (cursor.column >= line.size()) {
+    auto& buf = *buffer_manager_->active_buffer();
+    auto cursor = window_manager_->active_window()->cursor();
+    const auto& line_text = buf.line(cursor.line);
+
+    Range range_to_kill;
+    std::string killed_text;
+
+    if (cursor.column == 0 && cursor.line < buf.line_count() - 1) {
+        // Kill whole line including newline
+        range_to_kill = {{cursor.line, 0}, {cursor.line + 1, 0}};
+    } else if (cursor.column < line_text.size()) {
+        // Kill from cursor to end of line
+        range_to_kill = {cursor, {cursor.line, line_text.size()}};
+    } else { // Cursor is at or past end of line
         if (cursor.line < buf.line_count() - 1) {
-            // Kill the newline character
-            Position start = {cursor.line, line.size()};
-            Position end = {cursor.line + 1, 0};
-            Range range = {start, end};
-
-            kill_ring_manager_->push("\n"); // Delegate to manager
-
-            buf.erase(range);
-            emit_event(EditorEvent::BufferModified);
-
-            std::cerr << "[DEBUG] Killed newline at end of line " << cursor.line << std::endl;
+            // Kill only the newline (join with next line)
+            range_to_kill = {{cursor.line, line_text.size()}, {cursor.line + 1, 0}};
+        } else {
+            // At end of last line, nothing to kill
+            return;
         }
-        return;
     }
 
-    // Kill from cursor to end of line
-    Position start = cursor;
-    Position end = {cursor.line, line.size()};
-    Range range = {start, end};
-
-    std::string killed_text = buf.get_text_in_range(range);
+    killed_text = buf.get_text_in_range(range_to_kill); // Get text BEFORE erase
     if (!killed_text.empty()) {
-        kill_ring_manager_->push(killed_text); // Delegate to manager
-        buf.erase(range);
+        kill_ring_manager_->push(killed_text);
+        buf.erase(range_to_kill);
+        // Cursor position does not change after kill-line; it remains at the beginning of the killed region.
         emit_event(EditorEvent::BufferModified);
-
-        std::cerr << "[DEBUG] Killed text: '" << killed_text << "'" << std::endl;
+        emit_event(EditorEvent::CursorMoved); 
     }
 }
 
 void EditorCore::kill_region() {
-    auto& buf = buffer();
-    auto cursor = active_window()->cursor();
+    auto& buf = *buffer_manager_->active_buffer();
+    auto cursor = window_manager_->active_window()->cursor();
 
     auto region = buf.get_region(cursor);
     if (!region.has_value()) {
@@ -423,7 +365,7 @@ void EditorCore::kill_region() {
         buf.deactivate_mark();
 
         // Move cursor to start of killed region
-        active_window()->set_cursor(region.value().start);
+        window_manager_->active_window()->set_cursor(region.value().start);
 
         emit_event(EditorEvent::BufferModified);
         emit_event(EditorEvent::CursorMoved);
@@ -433,13 +375,13 @@ void EditorCore::kill_region() {
 }
 
 void EditorCore::kill_word() {
-    auto cursor = active_window()->cursor();
+    auto cursor = window_manager_->active_window()->cursor();
     auto end_pos = calculate_forward_word_pos(cursor);
     
     if (cursor == end_pos) return; 
     
     Range range = {cursor, end_pos};
-    auto& buf = buffer();
+    auto& buf = *buffer_manager_->active_buffer();
     std::string text = buf.get_text_in_range(range);
     
     if (!text.empty()) {
@@ -451,19 +393,19 @@ void EditorCore::kill_word() {
 }
 
 void EditorCore::backward_kill_word() {
-    auto cursor = active_window()->cursor();
+    auto cursor = window_manager_->active_window()->cursor();
     auto start_pos = calculate_backward_word_pos(cursor);
     
     if (cursor == start_pos) return; 
     
     Range range = {start_pos, cursor};
-    auto& buf = buffer();
+    auto& buf = *buffer_manager_->active_buffer();
     std::string text = buf.get_text_in_range(range);
     
     if (!text.empty()) {
         kill_ring_manager_->push(text); // Delegate to manager
         buf.erase(range);
-        active_window()->set_cursor(start_pos);
+        window_manager_->active_window()->set_cursor(start_pos);
         emit_event(EditorEvent::BufferModified);
         emit_event(EditorEvent::CursorMoved);
         std::cerr << "[DEBUG] Backward killed word: '" << text << "'" << std::endl;
@@ -472,8 +414,8 @@ void EditorCore::backward_kill_word() {
 
 
 void EditorCore::copy_region_as_kill() {
-    auto& buf = buffer();
-    auto cursor = active_window()->cursor();
+    auto& buf = *buffer_manager_->active_buffer();
+    auto cursor = window_manager_->active_window()->cursor();
 
     auto region = buf.get_region(cursor);
     if (!region.has_value()) {
@@ -502,8 +444,8 @@ void EditorCore::yank() {
         return;
     }
 
-    auto& buf = buffer();
-    auto cursor = active_window()->cursor();
+    auto& buf = *buffer_manager_->active_buffer();
+    auto cursor = window_manager_->active_window()->cursor();
 
     // Save yank start position
     last_yank_start_ = cursor;
@@ -528,7 +470,7 @@ void EditorCore::yank() {
     }
 
     last_yank_end_ = new_cursor;
-    active_window()->set_cursor(new_cursor);
+    window_manager_->active_window()->set_cursor(new_cursor);
 
     emit_event(EditorEvent::BufferModified);
     emit_event(EditorEvent::CursorMoved);
@@ -548,7 +490,7 @@ void EditorCore::yank_pop() {
     }
 
     // Delete the previously yanked text
-    auto& buf = buffer();
+    auto& buf = *buffer_manager_->active_buffer();
     Range yank_range = {last_yank_start_.value(), last_yank_end_.value()};
     buf.erase(yank_range);
 
@@ -557,7 +499,7 @@ void EditorCore::yank_pop() {
 
     // Restore cursor to yank start
     auto cursor = last_yank_start_.value();
-    active_window()->set_cursor(cursor);
+    window_manager_->active_window()->set_cursor(cursor);
 
     // Insert new text
     buf.insert(cursor, text);
@@ -575,7 +517,7 @@ void EditorCore::yank_pop() {
     }
 
     last_yank_end_ = new_cursor;
-    active_window()->set_cursor(new_cursor);
+    window_manager_->active_window()->set_cursor(new_cursor);
 
     emit_event(EditorEvent::BufferModified);
     emit_event(EditorEvent::CursorMoved);
@@ -609,8 +551,8 @@ bool EditorCore::insert_register(char register_name) {
     }
     std::string text = text_opt.value();
     
-    auto& buf = buffer();
-    Position cursor = active_window()->cursor();
+    auto& buf = *buffer_manager_->active_buffer();
+    Position cursor = window_manager_->active_window()->cursor();
     buf.insert(cursor, text);
     
     // Move cursor to end of inserted text
@@ -623,7 +565,7 @@ bool EditorCore::insert_register(char register_name) {
         cursor.column += text.size();
     }
     
-    active_window()->set_cursor(cursor);
+    window_manager_->active_window()->set_cursor(cursor);
     emit_event(EditorEvent::BufferModified);
     emit_event(EditorEvent::CursorMoved);
     
@@ -632,8 +574,8 @@ bool EditorCore::insert_register(char register_name) {
 }
 
 void EditorCore::copy_region_to_register(char register_name) {
-    auto& buf = buffer();
-    Position cursor = active_window()->cursor();
+    auto& buf = *buffer_manager_->active_buffer();
+    Position cursor = window_manager_->active_window()->cursor();
     
     auto region = buf.get_region(cursor);
     if (!region) {
@@ -677,6 +619,48 @@ void EditorCore::string_rectangle(const std::string& text) {
     rectangle_manager_->string_rectangle(text);
 }
 
+// === Helper Implementation ===
+
+void EditorCore::collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) {
+    if (!node) return;
+    if (node->type == LayoutNode::Type::Leaf && node->window) {
+        windows.push_back(node->window);
+    } else {
+        collect_windows(node->child1.get(), windows);
+        collect_windows(node->child2.get(), windows);
+    }
+}
+
+void EditorCore::set_message(std::string msg) {
+    last_message_ = std::move(msg);
+    message_clear_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(3);
+    emit_event(EditorEvent::Message);
+}
+
+void EditorCore::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);
+    }
+}
+
+void EditorCore::request_quit() {
+    emit_event(EditorEvent::Quit);
+}
+
+bool EditorCore::is_recording_macro() const noexcept {
+    return macro_manager_->is_recording_macro();
+}
+
+void EditorCore::enter_command_mode() { emit_event(EditorEvent::CommandMode); }
+void EditorCore::enter_buffer_switch_mode() { emit_event(EditorEvent::BufferSwitchMode); }
+void EditorCore::enter_kill_buffer_mode() { emit_event(EditorEvent::KillBufferMode); }
+void EditorCore::enter_find_file_mode() { emit_event(EditorEvent::FindFileMode); }
+void EditorCore::enter_theme_selection_mode() { emit_event(EditorEvent::ThemeSelectionMode); }
+void EditorCore::enter_isearch_mode() { emit_event(EditorEvent::ISearchMode); }
+void EditorCore::enter_isearch_backward_mode() { emit_event(EditorEvent::ISearchBackwardMode); }
+
 // === Private ===
 
 void EditorCore::emit_event(EditorEvent event) {
@@ -685,4 +669,4 @@ void EditorCore::emit_event(EditorEvent event) {
     }
 }
 
-} // namespace lumacs
+} // namespace lumacs

+ 24 - 31
src/gtk_completion_popup.cpp

@@ -33,13 +33,12 @@ Gtk::Widget* create_candidate_row_widget(const CompletionCandidate& candidate) {
 }
 
 GtkCompletionPopup::GtkCompletionPopup()
-    : Gtk::Window(Gtk::Window::Popup), // Use Gtk::Window::Popup for a transient window
+    : Gtk::Popover(), // Inherit from Gtk::Popover
       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 properties for the popup
+    // Gtk::Popover automatically adjusts size based on content.
+    // No explicit decorated, resizable, hide_on_close needed for popovers.
     set_child(list_scrolled_window_); // Set scrolled window as child
 
     list_scrolled_window_.set_child(list_box_);
@@ -51,19 +50,10 @@ GtkCompletionPopup::GtkCompletionPopup()
     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 {"
+        "popover.lumacs-completion-popup {"
         "   border: 1px solid @border_color;"
         "   background-color: @theme_bg_color;"
         "}"
@@ -82,43 +72,46 @@ GtkCompletionPopup::GtkCompletionPopup()
     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);
+    // However, for popovers, set_autohide() often handles its own lifetime.
 }
 
 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) {
+void GtkCompletionPopup::show_popup(const std::vector<CompletionCandidate>& candidates, size_t active_index, Gtk::Widget& relative_to, int x_offset, int y_offset) {
     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);
+    set_parent(relative_to);
     
-    // 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());
+    // Calculate the point to which the popover should be pointing.
+    // It's usually below the widget, aligned to its left or a specific sub-rectangle.
+    // For now, let's point below the widget.
+    Gdk::Rectangle rect;
+    rect.set_x(x_offset);
+    rect.set_y(y_offset);
+    rect.set_width(1); // Small width, it's a point
+    rect.set_height(1); // Small height, it's a point
+
+    set_pointing_to(rect);
+    set_autohide(true); // Popover will hide when focus is lost outside.
     
-    set_visible(true);
-    present(); // Bring to front and give focus
+    popup();
 }
 
 void GtkCompletionPopup::hide_popup() {
     candidates_.clear();
     active_index_ = 0;
     list_box_.remove_all();
-    set_visible(false);
+    popdown();
 }
 
 void GtkCompletionPopup::update_list() {
@@ -138,7 +131,7 @@ void GtkCompletionPopup::set_active_row(size_t index) {
         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
+            // row->scroll_to_row(); // Ensure the selected row is visible (TODO: Fix for GTK4)
             active_index_ = index;
         }
     }

+ 65 - 16
src/gtk_editor.cpp

@@ -86,7 +86,6 @@ void GtkEditor::handle_editor_event(EditorEvent event) {
         queue_redraw_all_windows(content_widget_);
     }
 
-    bool minibuffer_activated = false;
     if (event == EditorEvent::CommandMode) {
         core_->minibuffer_manager().activate_minibuffer(
             MinibufferMode::Command, "M-x ",
@@ -99,7 +98,6 @@ void GtkEditor::handle_editor_event(EditorEvent event) {
                 }
             }, nullptr
         );
-        minibuffer_activated = true;
     } else if (event == EditorEvent::FindFileMode) {
         core_->minibuffer_manager().activate_minibuffer(
             MinibufferMode::FilePath, "Find file: ",
@@ -108,7 +106,6 @@ void GtkEditor::handle_editor_event(EditorEvent event) {
                 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: ",
@@ -117,7 +114,6 @@ void GtkEditor::handle_editor_event(EditorEvent event) {
                 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: ",
@@ -126,7 +122,6 @@ void GtkEditor::handle_editor_event(EditorEvent event) {
                 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: ",
@@ -141,13 +136,11 @@ void GtkEditor::handle_editor_event(EditorEvent event) {
                 }
             }, 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_);
@@ -196,11 +189,11 @@ void GtkEditor::queue_redraw_all_windows(Gtk::Widget* widget) {
 // 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) {
+    if ((state & Gdk::ModifierType::CONTROL_MASK) == 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) { 
+    if ((state & Gdk::ModifierType::ALT_MASK) == Gdk::ModifierType::ALT_MASK || (state & Gdk::ModifierType::META_MASK) == Gdk::ModifierType::META_MASK) { 
         key_name += "M-";
     }
     // Shift is generally handled by the keyval itself for letters (e.g., 'A' vs 'a')
@@ -214,13 +207,13 @@ std::string GtkEditor::get_lumacs_key_name(guint keyval, Gdk::ModifierType state
         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));
+        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_BackSpace: key_name += "Backspace"; break; // Fixed case sensitivity
             case GDK_KEY_Delete: key_name += "Delete"; break;
             case GDK_KEY_Up: key_name += "ArrowUp"; break;
             case GDK_KEY_Down: key_name += "ArrowDown"; break;
@@ -255,6 +248,7 @@ std::string GtkEditor::get_lumacs_key_name(guint keyval, Gdk::ModifierType state
 
 
 bool GtkEditor::on_global_key_pressed(guint keyval, guint keycode, Gdk::ModifierType state) {
+    (void)keycode;
     if (!core_) return false;
 
     // Translate GDK event to Lumacs key name
@@ -317,14 +311,33 @@ bool GtkEditor::on_global_key_pressed(guint keyval, guint keycode, Gdk::Modifier
         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) {
+        KeyProcessingResult result = core_->keybinding_manager().process_key(lumacs_key_name);
+        if (result.type == KeyResult::Executed) {
             queue_redraw_all_windows(content_widget_);
             return true;
-        } else if (result.status == KeyResult::Status::Pending) {
+        } else if (result.type == KeyResult::Partial) {
             // Multi-key sequence in progress, wait for next key
             queue_redraw_all_windows(content_widget_);
             return true;
+        } else if (result.type == KeyResult::Unbound) {
+            // Check if it's a self-insertable character
+            // We assume single characters without modifiers (except Shift) are self-insertable
+            // if they are not bound to something else.
+            // lumacs_key_name format: "a", "S-a", "C-a"
+            bool has_ctrl = lumacs_key_name.find("C-") != std::string::npos;
+            bool has_meta = lumacs_key_name.find("M-") != std::string::npos;
+            
+            // Allow Shift-key (e.g. "S-a" -> "A" is handled by GDK usually giving "A" directly if we use unicode, 
+            // but our get_lumacs_key_name handles it.
+            // Actually, for printable chars, get_lumacs_key_name returns the unicode char (e.g. "A").
+            // So we just check length == 1 and no modifiers.
+            
+            if (!has_ctrl && !has_meta && lumacs_key_name.length() == 1) {
+                 // Execute self-insert-command
+                 core_->command_system().execute("self-insert-command", {lumacs_key_name});
+                 queue_redraw_all_windows(content_widget_);
+                 return true;
+            }
         }
         // If not processed by keybinding, let GTK handle it (e.g., for system shortcuts)
         return false; 
@@ -372,8 +385,7 @@ void GtkEditor::on_activate() {
 
     // 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
+    // Popovers are automatically transient and handle their own hide_on_close logic.
     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));
 
@@ -583,6 +595,43 @@ Gtk::Widget* GtkEditor::create_widget_for_layout_node(std::shared_ptr<LayoutNode
     }
 }
 
+// === Completion Popup Helpers ===
+
+void GtkEditor::show_completion_popup() {
+    if (!completion_popup_ || !window_) return;
+
+    auto candidates = core_->minibuffer_manager().get_completion_candidates();
+    if (candidates.empty()) {
+        hide_completion_popup();
+        return;
+    }
+
+    // Minibuffer is rendered at the bottom of the main window/drawing area.
+    // We need to position the popover relative to the main window, pointing to the minibuffer area.
+    Gdk::Rectangle rect;
+    rect.set_x(0); // Minibuffer starts at x=0
+    rect.set_y(window_->get_height() - 1); // Last line of the window
+    rect.set_width(window_->get_width());
+    rect.set_height(1); // Small height to represent the minibuffer line
+
+    completion_popup_->show_popup(candidates, 0, *window_, rect.get_x(), rect.get_y());
+}
+
+void GtkEditor::hide_completion_popup() {
+    if (completion_popup_) {
+        completion_popup_->hide_popup();
+    }
+}
+
+void GtkEditor::on_completion_selected(CompletionCandidate candidate) {
+    core_->minibuffer_manager().set_input_buffer(candidate.text);
+    hide_completion_popup();
+}
+
+void GtkEditor::on_completion_cancelled() {
+    hide_completion_popup();
+}
+
 std::unique_ptr<IEditorView> create_gtk_editor() {
     return std::make_unique<GtkEditor>();
 }

+ 8 - 8
src/gtk_renderer.cpp

@@ -112,10 +112,10 @@ std::optional<Position> GtkRenderer::resolve_screen_pos(std::shared_ptr<Window>
 void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
                             std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
     // Safety check - don't draw if core has no active theme (during destruction)
-    if (!core_.active_theme()) return;
+    if (!core_.theme_manager().active_theme()) return;
 
     // Fill background of the entire drawing area
-    auto theme = core_.active_theme();
+    auto theme = core_.theme_manager().active_theme();
     Color bg = theme->get_bg_color(ThemeElement::Background);
     cr->set_source_rgb(bg.r / 255.0, bg.g / 255.0, bg.b / 255.0);
     cr->paint();
@@ -141,7 +141,7 @@ void GtkRenderer::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, in
 void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
                                 std::shared_ptr<Window> window, Gtk::DrawingArea* widget,
                                 std::shared_ptr<Window> active_window_cache, bool cursor_visible_state) {
-    if (!core_.active_theme() || !window || !widget) return;
+    if (!core_.theme_manager().active_theme() || !window || !widget) return;
 
     // Use a temporary layout that is properly initialized
     auto layout = Pango::Layout::create(widget->get_pango_context());
@@ -149,7 +149,7 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
 
     const auto cursor = window->cursor();
     const auto& buffer = window->buffer();
-    auto theme = core_.active_theme();
+    auto theme = core_.theme_manager().active_theme();
 
     // Fill background
     Color bg = theme->get_bg_color(ThemeElement::Background);
@@ -306,7 +306,7 @@ void GtkRenderer::draw_window(const Cairo::RefPtr<Cairo::Context>& cr, int width
 void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height,
                                            std::shared_ptr<Window> window,
                                            std::shared_ptr<Window> active_window_cache) {
-    if (!core_.active_theme() || !window) return;
+    if (!core_.theme_manager().active_theme() || !window) return;
 
     // The logic for is_active needs to be passed from GtkEditor
     bool is_active = (window == active_window_cache);
@@ -316,7 +316,7 @@ void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>
     double modeline_x = PADDING_LEFT;
     
     // Get theme colors
-    auto theme = core_.active_theme();
+    auto theme = core_.theme_manager().active_theme();
     ThemeElement element = is_active ? ThemeElement::StatusLine : ThemeElement::StatusLineInactive;
     
     Color bg = theme->get_bg_color(element);
@@ -368,7 +368,7 @@ void GtkRenderer::render_modeline_for_window(const Cairo::RefPtr<Cairo::Context>
 
 
 void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height) {
-    if (!core_.active_theme()) return;
+    if (!core_.theme_manager().active_theme()) return;
 
     // Only render if minibuffer is active or a message is set
     if (!core_.minibuffer_manager().is_active() && core_.last_message().empty()) {
@@ -380,7 +380,7 @@ void GtkRenderer::render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int
     double minibuffer_x = PADDING_LEFT;
     
     // Get theme colors
-    auto theme = core_.active_theme();
+    auto theme = core_.theme_manager().active_theme();
     Color bg = theme->get_bg_color(ThemeElement::Background);
     Color fg = theme->get_fg_color(ThemeElement::Normal);
     

+ 8 - 8
src/keybinding.cpp

@@ -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; // Fixed escaping
+        if (s == "\\") return BaseKey::Backslash; // Fixed escaping
         if (s == "]") return BaseKey::RightBracket;
         if (s == "'") return BaseKey::Quote;
         // Add more keys as needed
@@ -90,7 +90,7 @@ namespace {
             case BaseKey::Slash: return "/";
             case BaseKey::Backtick: return "`";
             case BaseKey::LeftBracket: return "[";
-            case BaseKey::Backslash: return "\"; // Fixed escaping
+            case BaseKey::Backslash: return "\\"; // Fixed escaping
             case BaseKey::RightBracket: return "]";
             case BaseKey::Quote: return "'";
             default: return "Unknown";
@@ -238,8 +238,8 @@ KeyBinding::KeyBinding(const KeySequence& seq, std::string cmd_name, std::string
     : 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 description) // Corrected description parameter type
-    : sequence(key_str), command_name(std::move(cmd_name)), description(std::move(description)) {
+KeyBinding::KeyBinding(const std::string& key_str, std::string cmd_name, const std::string& description) // Corrected description parameter type
+    : sequence(key_str), command_name(std::move(cmd_name)), description(description) {
 }
 
 // ============================================================================ 
@@ -356,13 +356,13 @@ KeyProcessingResult KeyBindingManager::process_key(const Key& key) {
             // Execute the command via the CommandSystem
             if (command_system_) {
                 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 KeyProcessingResult(cmd_result.status == CommandStatus::Success ? KeyResult::Executed : KeyResult::Failed, cmd_result);
             }
-            return KeyProcessingResult(KeyResult::Failed, CommandResult{false, "No CommandSystem available"});
+            return KeyProcessingResult(KeyResult::Failed, CommandResult{CommandStatus::Failure, "No CommandSystem available"});
         } catch (const std::exception& e) {
-            return KeyProcessingResult(KeyResult::Failed, CommandResult{false, std::string("Command execution failed: ") + e.what()});
+            return KeyProcessingResult(KeyResult::Failed, CommandResult{CommandStatus::Failure, std::string("Command execution failed: ") + e.what()});
         } catch (...) {
-            return KeyProcessingResult(KeyResult::Failed, CommandResult{false, "Command execution failed with unknown error"});
+            return KeyProcessingResult(KeyResult::Failed, CommandResult{CommandStatus::Failure, "Command execution failed with unknown error"});
         }
     }
     

+ 204 - 113
src/lua_api.cpp

@@ -3,12 +3,28 @@
 #include "lumacs/minibuffer_mode.hpp" // Added for MinibufferMode enum binding
 #include "lumacs/completion_system.hpp" // Added for CompletionSystem access
 #include "lumacs/plugin_manager.hpp" // Added for PluginManager access
+#include "lumacs/buffer_manager.hpp" // Added for BufferManager::BufferInfo
 #include <iostream>
 #include <fstream>
 #include <filesystem> // For std::filesystem::path
 
 namespace lumacs {
 
+// Helper class to adapt a lambda/function to ICompletionSource
+class LambdaCompletionSource : public ICompletionSource {
+public:
+    using ProviderFunc = std::function<std::vector<CompletionCandidate>(const std::string&)>;
+
+    explicit LambdaCompletionSource(ProviderFunc provider) : provider_(std::move(provider)) {}
+
+    std::vector<CompletionCandidate> get_candidates(const std::string& input) override {
+        return provider_(input);
+    }
+
+private:
+    ProviderFunc provider_;
+};
+
 LuaApi::LuaApi() {
     lua_.open_libraries(
         sol::lib::base,
@@ -79,22 +95,57 @@ bool LuaApi::load_init_file() {
 void LuaApi::bind_key(std::string key, sol::function callback, std::string description) {
     std::cerr << "[DEBUG] Registering key binding: " << key << std::endl;
     
-    // Store in legacy map for backward compatibility
-    legacy_key_bindings_[key] = callback;
-    
-    // Register with new keybinding system
-    core_->keybinding_manager().bind(key, [callback]() -> bool {
-        try {
-            callback();
-            return true;  // Assume success if no exception is thrown
-        } catch (const sol::error& e) {
-            std::cerr << "Lua error in key binding: " << e.what() << std::endl;
-            return false;
-        }
-    }, description);
+    // Create a unique command name for the Lua function
+    // This is a simple way; a more robust system might handle conflicts or namespaces
+    std::string command_name = "lua_cmd_" + key; // Use key itself as part of name
+
+    // Register a C++ CommandFunction that wraps the Lua callback
+    core_->command_system().register_command(command_name, 
+        [callback, this](CommandContext& context) -> CommandResult { // Added 'this' to capture
+            try {
+                // Pass args from context to Lua function
+                sol::table args_table = get_lua_state().create_table(); // Use get_lua_state()
+                const auto& args = context.get_args(); // Use getter
+                for (size_t i = 0; i < args.size(); ++i) {
+                    args_table[i + 1] = args[i];
+                }
+                auto result = callback(args_table);
+                if (result.valid()) {
+                    if (result.get_type() == sol::type::table) {
+                        sol::table res_table = result;
+                        CommandStatus status = CommandStatus::Success;
+                        std::string message = "";
+                        
+                        if (res_table["success"].valid()) {
+                            status = res_table["success"].get<bool>() ? CommandStatus::Success : CommandStatus::Failure;
+                        }
+                        if (res_table["message"].valid()) {
+                            message = res_table["message"];
+                        }
+                        return CommandResult{status, message};
+                    } else if (result.get_type() == sol::type::string) {
+                        std::string message = result.get<std::string>();
+                        return CommandResult{CommandStatus::Success, message};
+                    } else {
+                        return CommandResult{CommandStatus::Success, ""};
+                    }
+                } else {
+                    return CommandResult{CommandStatus::Success, ""};
+                }
+            } catch (const sol::error& e) {
+                return CommandResult{CommandStatus::Failure, "Lua error in key binding callback: " + std::string(e.what())};
+            }
+        },
+        description, // Use original description
+        false,       // Not interactive directly via spec, Lua callback handles interactivity
+        ""           // No interactive spec for this wrapper command
+    );
+
+    // Now bind the key to the newly registered C++ command's name
+    core_->keybinding_manager().bind(KeySequence(key), command_name, description);
 }
 
-KeyResult LuaApi::process_key(const std::string& key) {
+KeyProcessingResult LuaApi::process_key(const std::string& key) { // Corrected return type
     return core_->keybinding_manager().process_key(key);
 }
 
@@ -155,10 +206,10 @@ void LuaApi::setup_api() {
     lua_["lumacs"] = lumacs_table;
 
     // Initialize MobDebug if enabled
-    if (core_->config().get_bool("debug_lua", false)) {
+    if (core_->config().get<bool>("debug_lua", false)) { // Corrected Config access
         std::cerr << "[DEBUG] Lua debugging enabled." << std::endl;
-        std::string debug_host = core_->config().get_string("debug_host", "127.0.0.1");
-        int debug_port = core_->config().get_int("debug_port", 8171);
+        std::string debug_host = core_->config().get<std::string>("debug_host", "127.0.0.1"); // Corrected Config access
+        int debug_port = core_->config().get<int>("debug_port", 8171); // Corrected Config access
 
         try {
             // MobDebug requires socket library, which is already opened in constructor.
@@ -293,13 +344,13 @@ void LuaApi::register_types() {
     );
 
     // BufferInfo type
-    lua_.new_usertype<EditorCore::BufferInfo>("BufferInfo",
+    lua_.new_usertype<BufferManager::BufferInfo>("BufferInfo", // Corrected
         sol::no_constructor,
-        "name", &EditorCore::BufferInfo::name,
-        "size", &EditorCore::BufferInfo::size,
-        "modified", &EditorCore::BufferInfo::modified,
-        "mode", &EditorCore::BufferInfo::mode,
-        "filepath", sol::property([](const EditorCore::BufferInfo& info) -> sol::optional<std::string> {
+        "name", &BufferManager::BufferInfo::name, // Corrected
+        "size", &BufferManager::BufferInfo::size, // Corrected
+        "modified", &BufferManager::BufferInfo::modified, // Corrected
+        "mode", &BufferManager::BufferInfo::mode, // Corrected
+        "filepath", sol::property([](const BufferManager::BufferInfo& info) -> sol::optional<std::string> { // Corrected
             if (info.filepath.has_value()) {
                 return info.filepath->string();
             }
@@ -370,7 +421,7 @@ void LuaApi::register_types() {
             {"Cursor", ThemeElement::Cursor},
             {"Selection", ThemeElement::Selection},
             {"SearchMatch", ThemeElement::SearchMatch},
-            {"SearchFail", Theme::SearchFail},
+            {"SearchFail", ThemeElement::SearchFail}, // Corrected
             {"WindowBorder", ThemeElement::WindowBorder},
             {"WindowBorderActive", ThemeElement::WindowBorderActive},
             {"Background", ThemeElement::Background}
@@ -458,7 +509,7 @@ void LuaApi::register_types() {
         "split_horizontally", &EditorCore::split_horizontally,
         "split_vertically", &EditorCore::split_vertically,
         "close_window", &EditorCore::close_active_window,
-        "next_window", &EditorCore::next_window_safe,
+        "next_window", &EditorCore::next_window, // Corrected
         "set_active_window", &EditorCore::set_active_window,
         "undo", &EditorCore::undo,
         "redo", &EditorCore::redo,
@@ -499,10 +550,131 @@ void LuaApi::register_types() {
         "get_all_buffer_info", &EditorCore::get_all_buffer_info,
         // Theme management
         "theme_manager", sol::property([](EditorCore& e) -> ThemeManager& { return e.theme_manager(); }),
-        "set_theme", &EditorCore::set_theme,
-        "active_theme", &EditorCore::active_theme,
+        "set_theme", [](EditorCore& e, const std::string& theme_name) { e.theme_manager().set_active_theme(theme_name); }, // Corrected
+        "active_theme", sol::property([](EditorCore& e) -> const Theme& { return *e.theme_manager().active_theme(); }), // Corrected
         // Configuration
-        "config", sol::property([](EditorCore& e) -> Config& { return e.config(); })
+        "config", sol::property([](EditorCore& e) -> Config& { return e.config(); }),
+        
+        // Key binding (method on EditorCore)
+        "bind_key", [this](EditorCore& core, std::string key, sol::function callback, sol::optional<std::string> description) {
+            // Create a unique command name for the Lua function
+            std::string command_name = "lua_cmd_" + key; // Use key itself as part of name
+
+            // Register a C++ CommandFunction that wraps the Lua callback
+            core.command_system().register_command(command_name, 
+                [callback, this](CommandContext& context) -> CommandResult { // Added 'this' to capture
+                    try {
+                        // Pass args from context to Lua function
+                        sol::table args_table = get_lua_state().create_table(); // Use get_lua_state()
+                        const auto& args = context.get_args(); // Use getter
+                        for (size_t i = 0; i < args.size(); ++i) {
+                            args_table[i + 1] = args[i];
+                        }
+                        auto result = callback(args_table);
+                        if (result.valid()) {
+                            if (result.get_type() == sol::type::table) {
+                                sol::table res_table = result;
+                                CommandStatus status = CommandStatus::Success;
+                                std::string message = "";
+                                
+                                if (res_table["success"].valid()) {
+                                    status = res_table["success"].get<bool>() ? CommandStatus::Success : CommandStatus::Failure;
+                                }
+                                if (res_table["message"].valid()) {
+                                    message = res_table["message"];
+                                }
+                                return CommandResult{status, message};
+                            } else if (result.get_type() == sol::type::string) {
+                                std::string message = result.get<std::string>();
+                                return CommandResult{CommandStatus::Success, message};
+                            } else {
+                                return CommandResult{CommandStatus::Success, ""};
+                            }
+                        } else {
+                            return CommandResult{CommandStatus::Success, ""};
+                        }
+                    } catch (const sol::error& e) {
+                        return CommandResult{CommandStatus::Failure, "Lua error in key binding callback: " + std::string(e.what())};
+                    }
+                },
+                description.value_or(""), // Use original description
+                false,       // Not interactive directly via spec, Lua callback handles interactivity
+                ""           // No interactive spec for this wrapper command
+            );
+
+            // Now bind the key to the newly registered C++ command's name
+            core.keybinding_manager().bind(KeySequence(key), command_name, description.value_or(""));
+        },
+
+        // Register command (method on EditorCore)
+        "register_command", [this](EditorCore& core, std::string name, std::string description, sol::function func, sol::optional<sol::table> aliases_table, sol::optional<bool> interactive, sol::optional<std::string> interactive_spec) {
+            std::vector<std::string> aliases;
+            if (aliases_table) {
+                for (auto& pair : aliases_table.value()) {
+                    aliases.push_back(pair.second.as<std::string>());
+                }
+            }
+            
+            CommandFunction command_func = [this, func](CommandContext& context) -> CommandResult {
+                try {
+                    // Pass args from context to Lua function
+                    sol::table args_table = get_lua_state().create_table(); // Use get_lua_state()
+                    const auto& args = context.get_args(); // Use getter
+                    for (size_t i = 0; i < args.size(); ++i) {
+                        args_table[i + 1] = args[i];
+                    }
+                    auto result = func(args_table);
+                    if (result.valid()) {
+                        if (result.get_type() == sol::type::table) {
+                            sol::table res_table = result;
+                            CommandStatus status = CommandStatus::Success;
+                            std::string message = "";
+                            
+                            if (res_table["success"].valid()) {
+                                status = res_table["success"].get<bool>() ? CommandStatus::Success : CommandStatus::Failure;
+                            }
+                            if (res_table["message"].valid()) {
+                                message = res_table["message"];
+                            }
+                            return CommandResult{status, message};
+                        } else if (result.get_type() == sol::type::string) {
+                            std::string message = result.get<std::string>();
+                            return CommandResult{CommandStatus::Success, message};
+                        } else {
+                            return CommandResult{CommandStatus::Success, ""};
+                        }
+                    } else {
+                        return CommandResult{CommandStatus::Success, ""};
+                    }
+                } catch (const sol::error& e) {
+                    return CommandResult{CommandStatus::Failure, "Lua error: " + std::string(e.what())};
+                }
+            };
+            
+            core.command_system().register_command(name, command_func, description, interactive.value_or(true), interactive_spec.value_or(""), aliases);
+        },
+
+        // Message display
+        "message", &EditorCore::set_message,
+
+        // Define face (method on EditorCore)
+        "define_face", [](EditorCore& core, std::string name, const FaceAttributes& attrs) {
+            if (auto theme = core.theme_manager().active_theme()) {
+                theme->set_face(name, attrs);
+            }
+        },
+
+        // Command system (method on EditorCore)
+        "execute_command", [](EditorCore& core, std::string command_name, sol::optional<sol::table> args_table) {
+            std::vector<std::string> args;
+            if (args_table) {
+                for (auto& pair : args_table.value()) {
+                    args.push_back(pair.second.as<std::string>());
+                }
+            }
+            auto result = core.command_system().execute(command_name, args);
+            return std::make_tuple((bool)(result.status == CommandStatus::Success), result.message);
+        }
     );
 }
 
@@ -510,11 +682,6 @@ void LuaApi::register_functions() {
     // Global editor instance
     lua_["editor"] = std::ref(*core_);
 
-    // Key binding function
-    lua_["bind_key"] = [this](std::string key, sol::function callback, sol::optional<std::string> description) {
-        bind_key(std::move(key), std::move(callback), description.value_or(""));
-    };
-
     // Print function that goes to stderr (since stdout is used by TUI)
     lua_["print"] = [](sol::variadic_args args) {
         for (auto arg : args) {
@@ -523,92 +690,16 @@ void LuaApi::register_functions() {
         std::cerr << std::endl;
     };
 
-    // Message function for user feedback
-    lua_["message"] = [this](std::string msg) {
-        core_->set_message(std::move(msg));
-    };
-
-    // Define face global function (wrapper around active_theme()->set_face)
-    lua_["define_face"] = [this](std::string name, const FaceAttributes& attrs) {
-        if (auto theme = core_->theme_manager().active_theme()) {
-            theme->set_face(name, attrs);
-        }
-    };
-
     // Command system functions
-    lua_["execute_command"] = [this](std::string command_name, sol::optional<sol::table> args_table) {
-        std::vector<std::string> args;
-        if (args_table) {
-            for (auto& pair : args_table.value()) {
-                args.push_back(pair.second.as<std::string>());
-            }
-        }
-        auto result = core_->command_system().execute(command_name, args);
-        return std::make_tuple((bool)(result.status == CommandStatus::Success), result.message);
-    };
-
     lua_["execute_command_line"] = [this](std::string command_string) {
         auto result = core_->minibuffer_manager().parse_and_execute_command_string(command_string);
         return std::make_tuple((bool)(result.status == CommandStatus::Success), result.message);
     };
 
-    lua_["register_command"] = [this](std::string name, std::string description, sol::function func, sol::optional<sol::table> aliases_table, sol::optional<bool> interactive, sol::optional<std::string> interactive_spec) {
-        std::vector<std::string> aliases;
-        if (aliases_table) {
-            for (auto& pair : aliases_table.value()) {
-                aliases.push_back(pair.second.as<std::string>());
-            }
-        }
-        
-        CommandFunction command_func = [this, func](CommandContext& context) -> CommandResult {
-            try {
-                // Prepare arguments for Lua:
-                // CommandContext::args_ contains the already-gathered arguments.
-                // If the Lua function needs more interactive input, it would call a Lua binding
-                // to a CommandContext method (e.g., context:read_string()) which would
-                // internally trigger MinibufferManager and return PendingInput.
-                // For now, let's just pass the initial arguments from context.args_ as a table.
-                sol::table args_table = lua_.create_table();
-                for (size_t i = 0; i < context.args_.size(); ++i) { // Accessing args_ directly, implies friend or public
-                    args_table[i + 1] = context.args_[i];
-                }
-                auto result = func(args_table);
-                if (result.valid()) {
-                    if (result.get_type() == sol::type::table) {
-                        sol::table res_table = result;
-                        CommandStatus status = CommandStatus::Success;
-                        std::string message = "";
-                        
-                        if (res_table["success"].valid()) { // Lua boolean to CommandStatus
-                            status = res_table["success"].get<bool>() ? CommandStatus::Success : CommandStatus::Failure;
-                        }
-                        if (res_table["message"].valid()) {
-                            message = res_table["message"];
-                        }
-                        return CommandResult{status, message};
-                    } else if (result.get_type() == sol::type::string) {
-                        std::string message = result.get<std::string>();
-                        return CommandResult{CommandStatus::Success, message};
-                    } else {
-                        return CommandResult{CommandStatus::Success, ""};
-                    }
-                } else {
-                    return CommandResult{CommandStatus::Success, ""};
-                }
-            } catch (const sol::error& e) {
-                return CommandResult{CommandStatus::Failure, "Lua error: " + std::string(e.what())};
-            }
-        };
-        
-        core_->command_system().register_command(name, command_func, description, interactive.value_or(true), interactive_spec.value_or(""), aliases);
-    };
-
     lua_["get_command_names"] = [this]() {
         return core_->command_system().get_command_names();
     };
 
-    // lua_["get_interactive_command_names"] is not needed as Lua can check interactive spec.
-
     // New binding for executing interactive commands from Lua
     lua_["execute_interactive_command"] = [this](std::string command_name) {
         auto result = core_->command_system().execute_interactive(command_name);
@@ -623,7 +714,7 @@ void LuaApi::register_functions() {
     // Completion functions
     lua_["get_completion_candidates"] = [this](int mode_int, std::string input) {
         MinibufferMode mode = static_cast<MinibufferMode>(mode_int);
-        auto candidates = core_->completion_system().get_candidates_for_mode(mode, input);
+        auto candidates = (*core_).completion_system().get_candidates_for_mode(mode, input);
         sol::table result = lua_.create_table();
         for (size_t i = 0; i < candidates.size(); ++i) {
             sol::table candidate = lua_.create_table();
@@ -635,9 +726,9 @@ void LuaApi::register_functions() {
         return result;
     };
 
-    lua_["register_completion_provider"] = [this](int mode_int, sol::function provider_func) {
+    lua_["register_completion_provider"] = [this](int mode_int, sol::function provider_func) { // Added 'this' back to capture
         MinibufferMode mode = static_cast<MinibufferMode>(mode_int);
-        auto provider = [provider_func](const std::string& input) -> std::vector<CompletionCandidate> {
+        auto provider = [provider_func](const std::string& input) -> std::vector<CompletionCandidate> { // Removed 'this' from capture
             try {
                 auto result = provider_func(input);
                 std::vector<CompletionCandidate> candidates;
@@ -670,7 +761,7 @@ void LuaApi::register_functions() {
                 return {};
             }
         };
-        core_->completion_system().register_completion_provider(mode, provider);
+        (*core_).completion_system().register_source(mode, std::make_unique<LambdaCompletionSource>(provider));
     };
 
     // Plugin Manager functions

+ 1 - 1
src/macro_manager.cpp

@@ -45,7 +45,7 @@ void MacroManager::end_kbd_macro_or_call() {
         for (const auto& key : last_macro_) {
             // TODO: Execute the actual key binding for this key
             // This would require MacroManager to have access to core_->keybinding_manager()
-            core_.process_key(key); // Replay the key sequence
+            core_.keybinding_manager().process_key(key); // Replay the key sequence
             // std::cerr << "[MACRO] Would execute key: " << key << std::endl;
         }
     }

+ 220 - 2
src/minibuffer_manager.cpp

@@ -36,6 +36,18 @@ void MinibufferManager::activate_minibuffer(MinibufferMode mode, const std::stri
     // Set current history manager based on mode
     current_history_ = &histories_[mode];
 
+    // Initialize ISearch state
+    isearch_active_ = (mode == MinibufferMode::ISearch);
+    isearch_match_range_.reset();
+    isearch_failed_ = false;
+    if (mode == MinibufferMode::ISearch) {
+        isearch_start_cursor_ = core_.cursor(); // Save cursor position when ISearch starts
+        isearch_direction_forward_ = true; // Default to forward search
+    } else {
+        isearch_start_cursor_.reset();
+    }
+    last_search_query_.clear();
+
     update_completion_candidates(); 
 }
 
@@ -49,6 +61,13 @@ void MinibufferManager::deactivate_minibuffer() {
     completion_candidates_.clear();
     completion_index_ = 0;
     cursor_position_ = 0; // Reset cursor position
+
+    // Clear ISearch state
+    isearch_active_ = false;
+    isearch_match_range_.reset();
+    isearch_failed_ = false;
+    isearch_start_cursor_.reset();
+    last_search_query_.clear();
 }
 
 bool MinibufferManager::handle_key_event(const std::string& key_name) {
@@ -56,6 +75,48 @@ bool MinibufferManager::handle_key_event(const std::string& key_name) {
         return false; // Minibuffer not active
     }
 
+    if (current_mode_ == MinibufferMode::ISearch) {
+        if (key_name == "Return") {
+            stop_isearch(); // Deactivate ISearch, cursor stays at match
+            deactivate_minibuffer(); // End minibuffer session
+            return true;
+        } else if (key_name == "Escape") {
+            stop_isearch(); // Restores cursor, clears state
+            deactivate_minibuffer(); // End minibuffer session
+            return true;
+        } else if (key_name == "C-s") {
+            // Next match (forward)
+            isearch_direction_forward_ = true;
+            next_isearch_match();
+            return true;
+        } else if (key_name == "C-r") {
+            // Previous match (backward)
+            isearch_direction_forward_ = false;
+            previous_isearch_match();
+            return true;
+        } else if (key_name == "BackSpace") {
+            if (cursor_position_ > 0) {
+                input_buffer_.erase(cursor_position_ - 1, 1);
+                cursor_position_--;
+                update_isearch(input_buffer_); // Update search with new query
+            } else {
+                // If query is empty, cancel ISearch.
+                stop_isearch();
+                deactivate_minibuffer();
+            }
+            return true;
+        } else if (key_name.length() == 1) { // Regular character input
+            input_buffer_.insert(cursor_position_, key_name);
+            cursor_position_++;
+            update_isearch(input_buffer_); // Update search with new query
+            return true;
+        }
+        // If other keys are pressed in ISearch mode, they are usually treated as part of the query.
+        // For now, if not specifically handled, ignore them.
+        return false;
+    }
+
+    // --- Generic Minibuffer Handling (for other modes) ---
     if (key_name == "Return") {
         add_to_history();
         if (on_submit_callback_) {
@@ -128,8 +189,6 @@ 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();
@@ -210,4 +269,163 @@ CommandResult MinibufferManager::parse_and_execute_command_string(const std::str
     return core_.command_system().execute(command_name, args);
 }
 
+// === ISearch Implementations ===
+
+void MinibufferManager::start_isearch(bool forward) {
+    if (current_mode_ != MinibufferMode::ISearch) {
+        // This should be called only when ISearch mode is active
+        return;
+    }
+    isearch_direction_forward_ = forward;
+    // Initial search is triggered by `update_isearch` through `handle_key_event`
+}
+
+void MinibufferManager::update_isearch(const std::string& query) {
+    if (query.empty()) {
+        isearch_match_range_.reset();
+        isearch_failed_ = false;
+        core_.set_message(prompt_text_); // Reset prompt
+        return;
+    }
+
+    last_search_query_ = query; // Store last successful query
+
+    // Start search from current cursor, or from start_cursor_ if first search
+    Position search_start_pos = core_.cursor();
+
+    // Perform search
+    std::optional<Range> match;
+    if (isearch_direction_forward_) {
+        match = core_.buffer().find(query, search_start_pos);
+    } else {
+        match = core_.buffer().find_backward(query, search_start_pos);
+    }
+
+    if (match) {
+        isearch_match_range_ = match;
+        isearch_failed_ = false;
+        core_.set_cursor(match->start); // Move cursor to start of match
+        core_.set_message(prompt_text_ + " " + query); // Update message with query
+    } else {
+        isearch_match_range_.reset();
+        isearch_failed_ = true;
+        core_.set_message(prompt_text_ + " " + query + " (Failed)"); // Indicate failure
+    }
+}
+
+void MinibufferManager::next_isearch_match() {
+    if (!isearch_active_ || !isearch_match_range_) return;
+
+    Position search_start_pos;
+    if (isearch_direction_forward_) {
+        // Start search after current match
+        search_start_pos = isearch_match_range_->end;
+    } else {
+        // Start search before current match (need to go back two steps for previous match of same size)
+        search_start_pos = isearch_match_range_->start;
+        // If current match is at 0,0 and we search backward, we need to wrap around.
+        // For simplicity, let's just search from start of buffer for now if at start of buffer.
+        if (search_start_pos.column == 0 && search_start_pos.line == 0) {
+            // Wrap around to end of buffer
+            search_start_pos = {core_.buffer().line_count() - 1, core_.buffer().line(core_.buffer().line_count() - 1).size()};
+        } else if (search_start_pos.column > 0) {
+            search_start_pos.column -=1;
+        } else {
+            search_start_pos.line -=1;
+            search_start_pos.column = core_.buffer().line(search_start_pos.line).size();
+        }
+    }
+    
+    std::optional<Range> match;
+    if (!last_search_query_.empty()) {
+        if (isearch_direction_forward_) {
+            match = core_.buffer().find(last_search_query_, search_start_pos);
+            // If no match found after current, try wrapping to start of buffer
+            if (!match) {
+                match = core_.buffer().find(last_search_query_, {0,0});
+            }
+        } else {
+            match = core_.buffer().find_backward(last_search_query_, search_start_pos);
+            // If no match found before current, try wrapping to end of buffer
+            if (!match) {
+                match = core_.buffer().find_backward(last_search_query_, {core_.buffer().line_count() - 1, core_.buffer().line(core_.buffer().line_count() - 1).size()});
+            }
+        }
+    }
+
+    if (match) {
+        isearch_match_range_ = match;
+        isearch_failed_ = false;
+        core_.set_cursor(match->start);
+        core_.set_message(prompt_text_ + " " + last_search_query_);
+    } else {
+        isearch_failed_ = true;
+        core_.set_message(prompt_text_ + " " + last_search_query_ + " (No more matches)");
+    }
+}
+
+void MinibufferManager::previous_isearch_match() {
+    // Simply reverse direction and call next_isearch_match
+    // For now, next_isearch_match handles both, but needs to be smarter about wrap-around.
+    // This is essentially "next in reverse direction".
+    // For now, let's make it simple for initial implementation.
+    // If the direction is forward, it means we were searching forward. "Previous" means search backward.
+    // If the direction is backward, "Previous" means search forward.
+    // This is often complex in Emacs. Let's just toggle the direction and re-search from the current match start.
+    
+    if (!isearch_active_ || !isearch_match_range_) {
+        // If we have a query but no active match, search backward from current cursor
+        if (!last_search_query_.empty()) {
+            isearch_direction_forward_ = false;
+            update_isearch(last_search_query_);
+        }
+        return;
+    }
+    
+    Position search_start_pos = isearch_match_range_->start;
+    if (search_start_pos.column > 0) {
+        search_start_pos.column -=1;
+    } else {
+        search_start_pos.line -=1;
+        if (search_start_pos.line < 0) { // Wrap around
+            search_start_pos = {core_.buffer().line_count() - 1, core_.buffer().line(core_.buffer().line_count() - 1).size()};
+        } else {
+            search_start_pos.column = core_.buffer().line(search_start_pos.line).size();
+        }
+    }
+
+    std::optional<Range> match;
+    if (!last_search_query_.empty()) {
+        match = core_.buffer().find_backward(last_search_query_, search_start_pos);
+        if (!match) { // Wrap around
+            match = core_.buffer().find_backward(last_search_query_, {core_.buffer().line_count() - 1, core_.buffer().line(core_.buffer().line_count() - 1).size()});
+        }
+    }
+    
+    if (match) {
+        isearch_match_range_ = match;
+        isearch_failed_ = false;
+        core_.set_cursor(match->start);
+        core_.set_message(prompt_text_ + " " + last_search_query_);
+    } else {
+        isearch_failed_ = true;
+        core_.set_message(prompt_text_ + " " + last_search_query_ + " (No more matches backward)");
+    }
+}
+
+void MinibufferManager::stop_isearch() {
+    isearch_active_ = false;
+    isearch_match_range_.reset();
+    isearch_failed_ = false;
+    last_search_query_.clear();
+    // Do NOT deactivate minibuffer; ISearch needs to go into a normal minibuffer state usually.
+    // The caller of stop_isearch (e.g., handle_key_event) should handle deactivation or other transitions.
+    core_.set_message(""); // Clear message
+    // Restore cursor if it was moved by isearch
+    if (isearch_start_cursor_.has_value()) {
+        core_.set_cursor(isearch_start_cursor_.value());
+        isearch_start_cursor_.reset();
+    }
+}
+
 } // namespace lumacs

+ 2 - 2
src/plugin_manager.cpp

@@ -96,7 +96,7 @@ void PluginManager::execute_on_load(const Plugin& plugin) {
         sol::state_view lua = lua_api_.get_lua_state();
         if (lua["lumacs"].valid() && lua["lumacs"]["plugins"].valid() && lua["lumacs"]["plugins"][plugin.name].valid()) {
             sol::table plugin_table = lua["lumacs"]["plugins"][plugin.name];
-            sol::function_view on_load = plugin_table["on_load"];
+            sol::protected_function on_load = plugin_table["on_load"];
             if (on_load.valid()) {
                 on_load();
                 std::cerr << "[DEBUG] Executed on_load for plugin: " << plugin.name << std::endl;
@@ -113,7 +113,7 @@ void PluginManager::execute_on_unload(const Plugin& plugin) {
         sol::state_view lua = lua_api_.get_lua_state();
         if (lua["lumacs"].valid() && lua["lumacs"]["plugins"].valid() && lua["lumacs"]["plugins"][plugin.name].valid()) {
             sol::table plugin_table = lua["lumacs"]["plugins"][plugin.name];
-            sol::function_view on_unload = plugin_table["on_unload"];
+            sol::protected_function on_unload = plugin_table["on_unload"];
             if (on_unload.valid()) {
                 on_unload();
                 std::cerr << "[DEBUG] Executed on_unload for plugin: " << plugin.name << std::endl;

+ 59 - 8
src/tui_editor.cpp

@@ -45,6 +45,8 @@ private:
     void render_window(std::shared_ptr<Window> window, int x, int y, int width, int height);
     void render_window_modeline(std::shared_ptr<Window> window, int x, int y, int width, bool is_active);
     void render_message_line();
+    
+    int get_attributes_for_face(const std::string& face_name);
 };
 
 // --- TuiEditor Public Method Definitions ---
@@ -68,8 +70,8 @@ void TuiEditor::init() {
     getmaxyx(stdscr, height_, width_);
     
     // Initialize theme colors for ncurses
-    if (has_colors() && core_->active_theme()) {
-        core_->active_theme()->initialize_ncurses_colors();
+    if (has_colors() && core_->theme_manager().active_theme()) {
+        core_->theme_manager().active_theme()->initialize_ncurses_colors();
     }
     
     // Set initial viewport size (leave room for minibuffer/message line)
@@ -130,7 +132,7 @@ void TuiEditor::handle_editor_event(EditorEvent event) {
                 if (input == "q" || input == "quit") {
                     should_quit_ = true;
                 } else {
-                    auto result = core_->command_system().execute(input);
+                    auto result = core_->minibuffer_manager().parse_and_execute_command_string(input);
                     core_->set_message(result.message);
                 }
             },
@@ -182,7 +184,7 @@ void TuiEditor::handle_editor_event(EditorEvent event) {
                 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_->theme_manager().set_active_theme(input);
                     core_->set_message("Switched to theme: " + input);
                 } else {
                     core_->set_message("Theme not found: " + input);
@@ -214,11 +216,45 @@ void TuiEditor::set_core(EditorCore* core) {
 
 // --- TuiEditor Private Helper Method Definitions ---
 
+int TuiEditor::get_attributes_for_face(const std::string& face_name) {
+    auto theme = core_->theme_manager().active_theme();
+    if (theme) {
+        return theme->get_face_attributes_ncurses(face_name);
+    }
+    return 0; // A_NORMAL
+}
+
 /// Convert ncurses key code to our key name format
 std::string TuiEditor::resolve_key(int ch) {
     debug_log << "=== NCURSES INPUT DEBUG ===" << std::endl;
     debug_log << "Raw key code: " << ch << " (0x" << std::hex << ch << std::dec << ")" << std::endl;
     
+    if (ch == 27) return "Escape";
+    if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) return "Return";
+    if (ch == '\t') return "Tab";
+    if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') return "Backspace";
+    if (ch == KEY_UP) return "ArrowUp";
+    if (ch == KEY_DOWN) return "ArrowDown";
+    if (ch == KEY_LEFT) return "ArrowLeft";
+    if (ch == KEY_RIGHT) return "ArrowRight";
+    if (ch == KEY_HOME) return "Home";
+    if (ch == KEY_END) return "End";
+    if (ch == KEY_PPAGE) return "PageUp";
+    if (ch == KEY_NPAGE) return "PageDown";
+    if (ch == ' ') return "Space";
+    
+    // Control keys
+    if (ch > 0 && ch < 32) {
+        return "C-" + std::string(1, (char)('a' + ch - 1));
+    }
+
+    // Regular characters
+    if (ch >= 32 && ch < 127) {
+        return std::string(1, (char)ch);
+    }
+
+    return "";
+}
 
 bool TuiEditor::handle_input(int ch) {
     // Resolve key name
@@ -240,6 +276,21 @@ bool TuiEditor::handle_input(int ch) {
     if (result.command_result.has_value()) {
         core_->set_message(result.command_result->message);
     }
+    
+    if (result.type == KeyResult::Unbound) {
+        // Fallback: Self-insert for printable characters
+        // Check if key is a single character and not a control sequence
+        // The resolve_key function returns "C-x", "M-x", "Esc", "Return", etc.
+        // Printable characters are returned as single chars "a", "1", etc.
+        if (key_name.length() == 1) {
+            // We can assume it's printable if length is 1 and it's not a special key (which resolve_key handles)
+            // Actually, resolve_key might return special single chars? No, most are named.
+            // But let's be safe.
+            core_->command_system().execute("self-insert-command", {key_name});
+            return true;
+        }
+    }
+
     return result.type != KeyResult::Unbound;
 }
 
@@ -252,7 +303,7 @@ void TuiEditor::render() {
     getmaxyx(stdscr, height_, width_);
     
     // Set background color from theme
-    auto theme = core_->active_theme();
+    auto theme = core_->theme_manager().active_theme();
     if (theme) {
         int bg_color_pair = theme->get_color_pair(ThemeElement::Background);
         bkgd(bg_color_pair);
@@ -426,7 +477,7 @@ void TuiEditor::render_window(std::shared_ptr<Window> window, int x, int y, int
                     int match_x = x + line_number_width + match_start;
                     // Simple clipping check
                     if (match_x < x + width) {
-                        int attrs = get_attributes_for_face(core_->minibuffer_manager().is_isearch_failed() ? "isearch-fail" : "isearch");
+                        int attrs = get_attributes_for_face(core_->minibuffer_manager().is_isearch_failed() ? "SearchFail" : "SearchMatch");
                         if (attrs == 0) attrs = A_REVERSE; // Fallback
                         
                         attron(attrs);
@@ -541,11 +592,11 @@ void TuiEditor::render_message_line() {
         auto candidates = core_->minibuffer_manager().get_completion_candidates();
         if (!candidates.empty()) {
             std::string completion_display;
-            for (size_t i = 0; i < candidates.size() && completion_display.length() < width_ - 5; ++i) {
+            for (size_t i = 0; i < candidates.size() && static_cast<int>(completion_display.length()) < width_ - 5; ++i) {
                 if (!completion_display.empty()) completion_display += " ";
                 completion_display += candidates[i].display_text; // Use display_text
             }
-            if (completion_display.length() >= width_ - 5) {
+            if (static_cast<int>(completion_display.length()) >= width_ - 5) {
                 completion_display = completion_display.substr(0, width_ - 8) + "...";
             }
             // Move up one line to display completions above the current minibuffer line

+ 3 - 0
src/window.cpp

@@ -1,4 +1,5 @@
 #include "lumacs/window.hpp"
+#include <iostream>
 
 namespace lumacs {
 
@@ -14,6 +15,8 @@ void Window::set_buffer(std::shared_ptr<Buffer> buffer) {
     viewport_.horizontal_offset = 0;
 }
 
+Window::~Window() = default;
+
 void Window::set_cursor(Position pos) {
     cursor_ = buffer_->clamp_position(pos);
     adjust_scroll();

+ 2 - 2
src/window_manager.cpp

@@ -68,8 +68,8 @@ LayoutNode* WindowManager::find_node_with_window(LayoutNode* current, std::share
 
 WindowManager::WindowManager(EditorCore& core) : core_(core) {
     // Initial setup: create a default window with a scratch buffer
-    // The actual scratch buffer is managed by BufferManager, so we need to get it from there.
-    auto initial_buffer = core_.buffer_manager().active_buffer();
+    // Use create_buffer_no_window to avoid circular dependency with active_window()
+    auto initial_buffer = core_.buffer_manager().create_buffer_no_window("*scratch*");
     active_window_ = std::make_shared<Window>(initial_buffer);
     root_node_ = std::make_shared<LayoutNode>(active_window_);
 }

+ 16 - 16
themes.lua

@@ -3,7 +3,7 @@
 
 -- Theme switching functions
 function switch_to_theme(theme_name)
-    local success, message = execute_command("set-theme", {theme_name})
+    local success, message = editor:execute_command("set-theme", {theme_name})
     if not success then
         message("Failed to switch theme: " .. message)
     end
@@ -36,7 +36,7 @@ end
 
 -- Enhanced theme listing
 function list_themes()
-    local success, message = execute_command("list-themes")
+    local success, message = editor:execute_command("list-themes")
     if success then
         message(message)
     else
@@ -63,7 +63,7 @@ function get_current_theme()
 end
 
 -- Register theme management commands
-register_command("list-available-themes", "List all available themes with descriptions", function(args)
+editor:register_command("list-available-themes", "List all available themes with descriptions", function(args)
     local themes = editor.theme_manager:theme_names()
     if #themes == 0 then
         return {success = true, message = "No themes available"}
@@ -91,7 +91,7 @@ register_command("list-available-themes", "List all available themes with descri
     return {success = true, message = result}
 end)
 
-register_command("set-random-theme", "Switch to a random theme", function(args)
+editor:register_command("set-random-theme", "Switch to a random theme", function(args)
     local themes = editor.theme_manager:theme_names()
     if #themes == 0 then
         return {success = false, message = "No themes available"}
@@ -101,7 +101,7 @@ register_command("set-random-theme", "Switch to a random theme", function(args)
     local random_index = math.random(1, #themes)
     local random_theme = themes[random_index]
     
-    local success, message = execute_command("set-theme", {random_theme})
+    local success, message = editor:execute_command("set-theme", {random_theme})
     if success then
         return {success = true, message = "Switched to random theme: " .. random_theme}
     else
@@ -109,7 +109,7 @@ register_command("set-random-theme", "Switch to a random theme", function(args)
     end
 end)
 
-register_command("cycle-themes", "Cycle through available themes", function(args)
+editor:register_command("cycle-themes", "Cycle through available themes", function(args)
     local themes = editor.theme_manager:theme_names()
     if #themes <= 1 then
         return {success = false, message = "Need at least 2 themes to cycle"}
@@ -131,7 +131,7 @@ register_command("cycle-themes", "Cycle through available themes", function(args
     local next_index = (current_index % #themes) + 1
     local next_theme = themes[next_index]
     
-    local success, message = execute_command("set-theme", {next_theme})
+    local success, message = editor:execute_command("set-theme", {next_theme})
     if success then
         return {success = true, message = "Cycled to theme: " .. next_theme}
     else
@@ -140,14 +140,14 @@ register_command("cycle-themes", "Cycle through available themes", function(args
 end)
 
 -- Key bindings for theme management
-bind_key("C-x t e", switch_to_everforest, "Switch to Everforest theme")
-bind_key("C-x t d", switch_to_default, "Switch to default theme")  
-bind_key("C-x t v", switch_to_dracula, "Switch to Dracula theme")
-bind_key("C-x t s", switch_to_solarized, "Switch to Solarized Dark theme")
-bind_key("C-x t n", switch_to_nord, "Switch to Nord theme")
-bind_key("C-x t g", switch_to_gruvbox_light, "Switch to Gruvbox Light theme")
-bind_key("C-x t l", list_themes, "List all themes")
-bind_key("C-x t r", function() execute_command("set-random-theme") end, "Switch to random theme")
-bind_key("C-x t c", function() execute_command("cycle-themes") end, "Cycle through themes")
+editor:bind_key("C-x t e", switch_to_everforest, "Switch to Everforest theme")
+editor:bind_key("C-x t d", switch_to_default, "Switch to default theme")  
+editor:bind_key("C-x t v", switch_to_dracula, "Switch to Dracula theme")
+editor:bind_key("C-x t s", switch_to_solarized, "Switch to Solarized Dark theme")
+editor:bind_key("C-x t n", switch_to_nord, "Switch to Nord theme")
+editor:bind_key("C-x t g", switch_to_gruvbox_light, "Switch to Gruvbox Light theme")
+editor:bind_key("C-x t l", list_themes, "List all themes")
+editor:bind_key("C-x t r", function() editor:execute_command("set-random-theme") end, "Switch to random theme")
+editor:bind_key("C-x t c", function() editor:execute_command("cycle-themes") end, "Cycle through themes")
 
 print("Enhanced theme configuration loaded - try C-x t for theme commands or M-x set-theme")