Преглед изворни кода

refactor(core): Extract Buffer, Window, Kill Ring, Register, Macro, and Rectangle managers

This commit continues the decomposition of the monolithic  class by extracting several key functional areas into dedicated manager classes:

- **BufferManager:** Manages all buffer-related operations, including creation, loading, switching, and closing buffers.
- **WindowManager:** Handles window layout, splitting, closing, and cycling through active windows.
- **KillRingManager:** Manages the editor's kill ring (clipboard history) operations.
- **RegisterManager:** Provides functionality for storing and retrieving text from named registers.
- **MacroManager:** Responsible for recording and replaying keyboard macros.
- **RectangleManager:** Encapsulates logic for rectangle-based kill, yank, and string operations.

Each new manager class (, , , , , ) has its own header and implementation file ( and ). The  has been updated to hold  instances of these managers and delegate all relevant calls to them, significantly reducing its responsibilities and improving modularity. This refactoring aligns with Phase 1.1 of the development plan.
Bernardo Magri пре 1 месец
родитељ
комит
4e75dc8b89

+ 11 - 6
documentation/PLAN.md

@@ -65,9 +65,13 @@ Lumacs/
 
 ### Phase 1: Modularity and Decoupling (EditorCore Decomposition)
 
-*   **Subtask 1.1: Identify and Extract Sub-systems:**
-    *   Analyze `EditorCore`'s methods and member variables to identify distinct functional areas (e.g., buffer management, window layout, command execution, macro recording, kill ring management).
-    *   Create new classes or services for each identified sub-system (e.g., `BufferManager`, `WindowManager`, `KeybindingManager`, `MacroManager`, `KillRingManager`, `RegisterManager`).
+*   **Subtask 1.1: Identify and Extract Sub-systems:** ✅ Completed
+    *   ✅ **Buffer Management**: Extracted into `BufferManager` (class, header, and implementation). `EditorCore` now delegates buffer-related operations to `BufferManager`.
+    *   ✅ **Window Management**: Extracted into `WindowManager` (class, header, and implementation). `EditorCore` now delegates window-related operations to `WindowManager`.
+    *   ✅ **Kill Ring Management**: Extracted into `KillRingManager` (class, header, and implementation). `EditorCore` now delegates kill-ring-related operations to `KillRingManager`.
+    *   ✅ **Registers Management**: Extracted into `RegisterManager` (class, header, and implementation). `EditorCore` now delegates register-related operations to `RegisterManager`.
+    *   ✅ **Keyboard Macro Management**: Extracted into `MacroManager` (class, header, and implementation). `EditorCore` now delegates macro-related operations to `MacroManager`.
+    *   ✅ **Rectangle Operations Management**: Extracted into `RectangleManager` (class, header, and implementation). `EditorCore` now delegates rectangle-related operations to `RectangleManager`.
 *   **Subtask 1.2: Migrate Responsibilities:**
     *   Move relevant member variables and methods from `EditorCore` to their respective new manager classes.
     *   Update `EditorCore` to hold instances (preferably smart pointers) of these new manager classes.
@@ -160,9 +164,7 @@ This phase aimed to enhance the Command System to support robust, type-safe, and
 
 ## Current Development Roadmap
 
-This section outlines the immediate priorities for Lumacs development.
-
-1.  **Command Aliases**: Allow users to define custom command aliases in their configuration.
+(All immediate development roadmap items completed)
 
 ## Current State Summary
 
@@ -186,6 +188,9 @@ This section outlines the immediate priorities for Lumacs development.
 - ✅ **Phase 15 Polishing**: Successfully addressed GTK Cleanup and Modeline Refactor.
 - ✅ **Plugin Management**: Implemented dynamic loading and lifecycle management of Lua plugins.
 - ✅ **Lua Debugging**: Integrated basic remote debugging support for Lua scripts via MobDebug.
+- ✅ **Command Aliases**: Implemented support for user-defined command aliases.
+- ✅ **EditorCore Decomposition (Buffer Management)**: Extracted buffer management into a dedicated `BufferManager` class.
+- ✅ **EditorCore Decomposition (Window Management)**: Extracted window management into a dedicated `WindowManager` class.
 
 ## Technical Debt/Notes
 

+ 73 - 0
include/lumacs/buffer_manager.hpp

@@ -0,0 +1,73 @@
+#pragma once
+
+#include <vector>
+#include <string>
+#include <memory>
+#include <list>
+#include <filesystem>
+#include <optional>
+
+#include "lumacs/buffer.hpp" // Include the Buffer class definition
+#include "lumacs/window.hpp" // For shared_ptr<Window>
+
+namespace lumacs {
+
+class EditorCore; // Forward declaration
+
+/// @brief Manages the creation, loading, and manipulation of editor buffers.
+class BufferManager {
+public:
+    explicit BufferManager(EditorCore& core);
+
+    /// @brief Creates a new scratch buffer and makes it active in the current window.
+    /// @param name The name of the new buffer (defaults to "*scratch*").
+    void new_buffer(std::string name = "*scratch*");
+
+    /// @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.
+    /// @return True if the file was loaded successfully, false otherwise.
+    bool load_file(const std::filesystem::path& path);
+
+    /// @brief Switches the active buffer in the current window to the buffer with the given name.
+    /// @param name The name of the buffer to switch to.
+    /// @return True if the buffer was switched successfully, false otherwise.
+    bool switch_buffer_in_window(const std::string& name);
+
+    /// @brief Closes the buffer with the given name.
+    /// If the buffer is displayed in any window, that window's buffer will be changed.
+    /// Cannot close the last remaining buffer.
+    /// @param name The name of the buffer to close.
+    /// @return True if the buffer was closed successfully, false otherwise.
+    bool close_buffer(const std::string& name);
+
+    /// @brief Returns a shared pointer to the currently active buffer.
+    /// @return The active buffer.
+    [[nodiscard]] std::shared_ptr<Buffer> active_buffer();
+
+    /// @brief Returns a shared pointer to the buffer with the given name.
+    /// @param name The name of the buffer to retrieve.
+    /// @return A shared pointer to the buffer, or nullptr if not found.
+    [[nodiscard]] std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name) const;
+
+    /// @brief Returns a list of names of all currently managed buffers.
+    [[nodiscard]] std::vector<std::string> get_buffer_names() const;
+    
+    /// @brief Structure containing summary information about a buffer.
+    struct BufferInfo {
+        std::string name;
+        size_t size;
+        bool modified;
+        std::string mode;
+        std::optional<std::filesystem::path> filepath;
+    };
+
+    /// @brief Get detailed information about all buffers.
+    [[nodiscard]] std::vector<BufferInfo> get_all_buffer_info() const;
+
+private:
+    EditorCore& core_;
+    std::list<std::shared_ptr<Buffer>> buffers_; // Owns the buffers
+};
+
+} // namespace lumacs

+ 76 - 105
include/lumacs/editor_core.hpp

@@ -2,7 +2,7 @@
 
 #include "lumacs/buffer.hpp"
 #include "lumacs/window.hpp"
-#include "lumacs/kill_ring.hpp"
+// #include "lumacs/kill_ring.hpp" // Now using KillRingManager
 #include "lumacs/theme.hpp"
 #include "lumacs/config.hpp"
 #include "lumacs/keybinding.hpp"
@@ -12,6 +12,12 @@
 #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 <memory>
 #include <functional>
 #include <vector>
@@ -25,24 +31,7 @@ namespace lumacs {
 class LuaApi; // Forward declaration
 class CommandSystem; // Forward declaration
 class PluginManager; // Forward declaration
-
-/// @brief Represents a node in the window layout tree.
-struct LayoutNode {
-    enum class Type { Leaf, HorizontalSplit, VerticalSplit };
-    Type type;
-    
-    // If Leaf
-    std::shared_ptr<Window> window;
-    
-    // If Split
-    std::shared_ptr<LayoutNode> child1;
-    std::shared_ptr<LayoutNode> child2;
-    float ratio = 0.5f; // Split ratio (0.0 to 1.0)
-    
-    LayoutNode(std::shared_ptr<Window> w) : type(Type::Leaf), window(w) {}
-    LayoutNode(Type t, std::shared_ptr<LayoutNode> c1, std::shared_ptr<LayoutNode> c2)
-        : type(t), child1(c1), child2(c2) {}
-};
+// class WindowManager; // Forward declaration already in window_manager.hpp
 
 /// @brief Core logic of the Lumacs editor, independent of the UI framework.
 ///
@@ -64,23 +53,23 @@ public:
     // === ICommandTarget Implementation ===
     // These methods implement the ICommandTarget interface.
 
-    // Buffer Management
-    [[nodiscard]] const Buffer& buffer() const noexcept override;
-    [[nodiscard]] Buffer& buffer() noexcept override;
-    bool load_file(const std::filesystem::path& path) override;
-    void new_buffer(std::string name = "*scratch*") override;
-    [[nodiscard]] std::vector<std::string> get_buffer_names() const override;
-    [[nodiscard]] std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name) override;
-    bool switch_buffer_in_window(const std::string& name) override;
-    bool close_buffer(const std::string& name) override;
+    // 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(); }
+    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(); }
+    [[nodiscard]] std::shared_ptr<Buffer> get_buffer_by_name(const std::string& name) override { return buffer_manager_->get_buffer_by_name(name); }
+    bool switch_buffer_in_window(const std::string& name) override { return buffer_manager_->switch_buffer_in_window(name); }
+    bool close_buffer(const std::string& name) override { return buffer_manager_->close_buffer(name); }
     
-    // Window Management
-    [[nodiscard]] std::shared_ptr<Window> active_window() const override { return active_window_; }
-    bool set_active_window(std::shared_ptr<Window> window) override;
-    void split_horizontally() override;
-    void split_vertically() override;
-    void close_active_window() override;
-    void next_window() override; 
+    // Window Management (Delegated to WindowManager)
+    [[nodiscard]] std::shared_ptr<Window> active_window() const override { return window_manager_->active_window(); }
+    bool set_active_window(std::shared_ptr<Window> window) override { return window_manager_->set_active_window(window); }
+    void split_horizontally() override { window_manager_->split_horizontally(); }
+    void split_vertically() override { window_manager_->split_vertically(); }
+    void close_active_window() override { window_manager_->close_active_window(); }
+    void next_window() override { window_manager_->next_window(); } 
     
     // Cursor Management
     [[nodiscard]] Position cursor() const noexcept override;
@@ -142,20 +131,11 @@ public:
     void enter_isearch_mode();
     void enter_isearch_backward_mode();
 
-    /// @brief Structure containing summary information about a buffer.
-    struct BufferInfo {
-        std::string name;
-        size_t size;
-        bool modified;
-        std::string mode;
-        std::optional<std::filesystem::path> filepath;
-    };
-
     /// @brief Get detailed information about all buffers.
-    [[nodiscard]] std::vector<BufferInfo> get_all_buffer_info() const;
+    [[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 root_node_; }
+    std::shared_ptr<LayoutNode> root_layout() const { return window_manager_->root_layout(); }
 
     // === Viewport Management (Proxies to active window) ===
     
@@ -186,49 +166,50 @@ public:
     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]] KillRing& kill_ring() noexcept { return kill_ring_; }
-    [[nodiscard]] const KillRing& kill_ring() const noexcept { return kill_ring_; }
+    [[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 Save text to a named register.
-    void copy_to_register(char register_name, const std::string& text);
-    
-    /// @brief Insert text from a named register.
-    bool insert_register(char register_name);
-    
-    /// @brief Copy the active region to a register (C-x r s).
-    void copy_region_to_register(char register_name);
-    
-    /// @brief Insert text from a register (C-x r i).
-    bool yank_from_register(char register_name);
+    /// @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 Start recording a keyboard macro (F3).
-    void start_kbd_macro();
-
-    /// @brief Stop recording or execute the last macro (F4).
-    void end_kbd_macro_or_call();
-
-    /// @brief Record a key sequence if macro recording is active.
-    void record_key_sequence(const std::string& key_sequence);
-
-    /// @brief Check if macro recording is currently active.
-    [[nodiscard]] bool is_recording_macro() const noexcept { return 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 Kill (cut) a rectangular region (C-x r k).
-    void kill_rectangle();
-
-    /// @brief Yank (paste) the last killed rectangle (C-x r y).
-    void yank_rectangle();
-
-    /// @brief Replace a rectangular region with a string (C-x r t).
-    void string_rectangle(const std::string& text);
+    /// @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_; }                                                                
@@ -257,40 +238,30 @@ public:
     [[nodiscard]] PluginManager& plugin_manager() noexcept { return *plugin_manager_; }
     [[nodiscard]] const PluginManager& plugin_manager() const noexcept { return *plugin_manager_; }
 
-private:
-    std::list<std::shared_ptr<Buffer>> buffers_;
+    // === Buffer Manager ===
+    [[nodiscard]] BufferManager& buffer_manager() noexcept { return *buffer_manager_; }
+    [[nodiscard]] const BufferManager& buffer_manager() const noexcept { return *buffer_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); }
+
 
+private:
     // Word movement helpers
     Position calculate_forward_word_pos(Position start_pos);
     Position calculate_backward_word_pos(Position start_pos);
 
-    // Window layout
-    std::shared_ptr<LayoutNode> root_node_;
-    std::shared_ptr<Window> active_window_;
-
     std::string last_message_;
     std::optional<std::chrono::steady_clock::time_point> message_clear_time_;
 
     std::vector<EventCallback> event_callbacks_;
 
-    // Kill ring for cut/copy/paste
-    KillRing kill_ring_;
-
     // Last yank position (for yank-pop)
     std::optional<Position> last_yank_start_;
     std::optional<Position> last_yank_end_;
 
-    // Registers for storing text (a-z, A-Z, 0-9)
-    std::unordered_map<char, std::string> registers_;
-
-    // Keyboard macros
-    std::vector<std::string> current_macro_;
-    std::vector<std::string> last_macro_;
-    bool recording_macro_ = false;
-
-    // Rectangle storage
-    std::vector<std::string> rectangle_kill_ring_;
-
     // Subsystems
     ThemeManager theme_manager_;
     Config config_;
@@ -301,14 +272,14 @@ private:
     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);
-    
-    // Helper to find a node containing the active window
-    LayoutNode* find_parent_node(LayoutNode* root, std::shared_ptr<Window> target);
-    
-    // Helper to collect all windows in traversal order
-    void collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows);
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 43 - 0
include/lumacs/kill_ring_manager.hpp

@@ -0,0 +1,43 @@
+#pragma once
+
+#include <string>
+#include <deque>
+#include <optional>
+
+#include "lumacs/buffer.hpp" // For Position and Range
+
+namespace lumacs {
+
+/// @brief Implements an Emacs-like kill ring (clipboard history).
+/// The kill ring stores killed (cut) text in a circular buffer.
+class KillRingManager {
+public:
+    explicit KillRingManager(size_t max_size = 60);
+
+    /// @brief Pushes text onto the kill ring. If the last entry was also a kill
+    /// (not a yank), it appends to that entry. Otherwise, it creates a new entry.
+    /// @param text The text to push.
+    void push(const std::string& text);
+
+    /// @brief Retrieves the current (most recently pushed) entry in the kill ring.
+    /// @return The current entry. Returns empty string if ring is empty.
+    [[nodiscard]] std::string current() const;
+
+    /// @brief Rotates the kill ring and retrieves the previous entry.
+    /// Used for `yank-pop`.
+    /// @return The previous entry. Returns empty string if ring is empty.
+    [[nodiscard]] std::string previous();
+
+    /// @brief Checks if the kill ring is empty.
+    [[nodiscard]] bool empty() const { return ring_.empty(); }
+
+    /// @brief Clears the kill ring.
+    void clear() { ring_.clear(); }
+
+private:
+    std::deque<std::string> ring_;
+    size_t max_size_;
+    bool last_action_was_kill_ = false; // Tracks if the last push was a kill or append
+};
+
+} // namespace lumacs

+ 37 - 0
include/lumacs/macro_manager.hpp

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace lumacs {
+
+class EditorCore; // Forward declaration
+
+/// @brief Manages the recording and playback of keyboard macros.
+class MacroManager {
+public:
+    explicit MacroManager(EditorCore& core);
+
+    /// @brief Starts recording a new keyboard macro. Clears any existing macro.
+    void start_kbd_macro();
+
+    /// @brief Stops recording the current macro or executes the last recorded macro.
+    void end_kbd_macro_or_call();
+
+    /// @brief Records a key sequence if macro recording is currently active.
+    /// @param key_sequence The key sequence to record.
+    void record_key_sequence(const std::string& key_sequence);
+
+    /// @brief Checks if macro recording is currently active.
+    [[nodiscard]] bool is_recording_macro() const noexcept { return recording_macro_; }
+
+private:
+    EditorCore& core_;
+
+    std::vector<std::string> current_macro_;
+    std::vector<std::string> last_macro_;
+    bool recording_macro_ = false;
+};
+
+} // namespace lumacs

+ 33 - 0
include/lumacs/rectangle_manager.hpp

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <optional>
+
+#include "lumacs/buffer.hpp" // For Position and Range
+
+namespace lumacs {
+
+class EditorCore; // Forward declaration
+
+/// @brief Manages Emacs-like rectangle operations (kill, yank, string).
+class RectangleManager {
+public:
+    explicit RectangleManager(EditorCore& core);
+
+    /// @brief Kills (cuts) a rectangular region of text.
+    void kill_rectangle();
+
+    /// @brief Yanks (pastes) the last killed rectangular region.
+    void yank_rectangle();
+
+    /// @brief Replaces a rectangular region with a given string, repeating it as necessary.
+    void string_rectangle(const std::string& text);
+
+private:
+    EditorCore& core_;
+    std::vector<std::string> rectangle_kill_ring_; // Stores the last killed rectangle
+};
+
+} // namespace lumacs

+ 29 - 0
include/lumacs/register_manager.hpp

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <string>
+#include <unordered_map>
+#include <optional>
+
+namespace lumacs {
+
+/// @brief Manages Emacs-like registers for storing text.
+/// Registers are named storage locations (typically a single character).
+class RegisterManager {
+public:
+    RegisterManager() = default;
+
+    /// @brief Saves text to a named register.
+    /// @param register_name The character name of the register (e.g., 'a', 'B', '1').
+    /// @param text The string content to save.
+    void copy_to_register(char register_name, const std::string& text);
+
+    /// @brief Retrieves text from a named register.
+    /// @param register_name The character name of the register.
+    /// @return The optional string content if the register exists, `std::nullopt` otherwise.
+    [[nodiscard]] std::optional<std::string> get_from_register(char register_name) const;
+
+private:
+    std::unordered_map<char, std::string> registers_;
+};
+
+} // namespace lumacs

+ 61 - 0
include/lumacs/window_manager.hpp

@@ -0,0 +1,61 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <list> // For std::list
+#include "lumacs/window.hpp" // For Window and LayoutNode
+#include "lumacs/buffer.hpp" // For shared_ptr<Buffer>
+
+namespace lumacs {
+
+class EditorCore; // Forward declaration
+
+/// @brief Manages the layout and interaction of editor windows.
+class WindowManager {
+public:
+    explicit WindowManager(EditorCore& core);
+
+    /// @brief Get the currently active window.
+    /// @return A shared pointer to the active window.
+    [[nodiscard]] std::shared_ptr<Window> active_window() const;
+
+    /// @brief Set the currently active window.
+    /// @param window A shared pointer to the window to make active.
+    /// @return True if the window was set as active, false otherwise.
+    bool set_active_window(std::shared_ptr<Window> window);
+
+    /// @brief Splits the active window horizontally, creating a new window below it.
+    void split_horizontally();
+
+    /// @brief Splits the active window vertically, creating a new window to its right.
+    void split_vertically();
+
+    /// @brief Closes the active window. If it's the last window, it's not closed.
+    void close_active_window();
+
+    /// @brief Cycles focus to the next window in the layout.
+    void next_window();
+
+    /// @brief Get the root node of the window layout tree.
+    [[nodiscard]] std::shared_ptr<LayoutNode> root_layout() const { return root_node_; }
+
+    /// @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);
+
+private:
+    EditorCore& core_;
+
+    std::shared_ptr<LayoutNode> root_node_;
+    std::shared_ptr<Window> active_window_;
+
+    // Helper functions for tree manipulation
+    LayoutNode* find_parent_of_node(LayoutNode* current, LayoutNode* child_target);
+    LayoutNode* find_node_with_window(LayoutNode* current, std::shared_ptr<Window> target);
+    bool replace_window_node(std::shared_ptr<LayoutNode> node, 
+                            std::shared_ptr<Window> target, 
+                            std::shared_ptr<LayoutNode> replacement);
+};
+
+} // namespace lumacs

+ 162 - 0
src/buffer_manager.cpp

@@ -0,0 +1,162 @@
+#include "lumacs/buffer_manager.hpp"
+#include "lumacs/editor_core.hpp" // For EditorCore access
+#include <algorithm>
+#include <iostream> // TODO: Replace with proper logging
+
+namespace lumacs {
+
+BufferManager::BufferManager(EditorCore& core) : core_(core) {
+}
+
+void BufferManager::new_buffer(std::string name) {
+    auto new_buffer = std::make_shared<Buffer>(std::move(name));
+    buffers_.push_back(new_buffer);
+    
+    // Set this new buffer in the active window
+    if (core_.active_window()) {
+        core_.active_window()->set_buffer(new_buffer);
+    } else {
+        // This case should ideally not happen if EditorCore initializes properly
+        // For robustness, create a default window if none exists
+        std::cerr << "[ERROR] No active window to set new buffer. This should not happen." << std::endl;
+    }
+
+    core_.emit_event(EditorEvent::BufferModified);
+    core_.emit_event(EditorEvent::CursorMoved);
+    core_.emit_event(EditorEvent::ViewportChanged);
+}
+
+bool BufferManager::load_file(const std::filesystem::path& path) {
+    std::filesystem::path abs_path = std::filesystem::absolute(path);
+
+    // Check if already open
+    for (const auto& buf : buffers_) {
+        if (buf->file_path() && std::filesystem::equivalent(*buf->file_path(), abs_path)) {
+            if (core_.active_window()) {
+                core_.active_window()->set_buffer(buf);
+            }
+            core_.emit_event(EditorEvent::BufferModified);
+            core_.emit_event(EditorEvent::CursorMoved);
+            core_.emit_event(EditorEvent::ViewportChanged);
+            return true;
+        }
+    }
+
+    auto new_buffer_opt = Buffer::from_file(abs_path);
+    if (!new_buffer_opt) {
+        return false;
+    }
+    
+    auto new_buffer = std::make_shared<Buffer>(std::move(*new_buffer_opt));
+    buffers_.push_back(new_buffer);
+
+    if (core_.active_window()) {
+        core_.active_window()->set_buffer(new_buffer);
+    }
+
+    core_.emit_event(EditorEvent::BufferModified);
+    core_.emit_event(EditorEvent::CursorMoved);
+    core_.emit_event(EditorEvent::ViewportChanged);
+    return true;
+}
+
+bool BufferManager::switch_buffer_in_window(const std::string& name) {
+    auto buf = get_buffer_by_name(name);
+    if (!buf) {
+        return false;
+    }
+
+    if (core_.active_window()) {
+        core_.active_window()->set_buffer(buf);
+    }
+    core_.emit_event(EditorEvent::BufferModified);
+    core_.emit_event(EditorEvent::CursorMoved);
+    core_.emit_event(EditorEvent::ViewportChanged);
+    return true;
+}
+
+bool BufferManager::close_buffer(const std::string& name) {
+    auto buf_to_close = get_buffer_by_name(name);
+    if (!buf_to_close) {
+        return false;
+    }
+
+    // Cannot close buffer if it's the only one
+    if (buffers_.size() <= 1) {
+        core_.set_message("Cannot close last buffer");
+        return false;
+    }
+
+    // Check if buffer is displayed in any window and switch it
+    std::vector<std::shared_ptr<Window>> windows;
+    // Need a way for BufferManager to access all windows.
+    // EditorCore should provide this functionality.
+    core_.collect_windows(core_.root_layout().get(), windows); // Assuming EditorCore has this helper
+
+    for (const auto& win : windows) {
+        if (win->buffer_ptr() == buf_to_close) {
+            // Find another buffer to switch to
+            auto other_buf_it = std::find_if(buffers_.begin(), buffers_.end(),
+                                             [&](const std::shared_ptr<Buffer>& b) { return b != buf_to_close; });
+            if (other_buf_it != buffers_.end()) {
+                win->set_buffer(*other_buf_it);
+            } else {
+                // This scenario should be prevented by the buffers_.size() <= 1 check
+                std::cerr << "[ERROR] No other buffer found to switch to after closing." << std::endl;
+                return false; 
+            }
+        }
+    }
+
+    // Remove buffer from list
+    buffers_.remove(buf_to_close);
+
+    core_.emit_event(EditorEvent::BufferModified);
+    core_.emit_event(EditorEvent::CursorMoved);
+    core_.emit_event(EditorEvent::ViewportChanged);
+    return true;
+}
+
+std::shared_ptr<Buffer> BufferManager::active_buffer() {
+    if (core_.active_window()) {
+        return core_.active_window()->buffer_ptr();
+    }
+    return nullptr; // Should not happen in a well-initialized editor
+}
+
+std::shared_ptr<Buffer> BufferManager::get_buffer_by_name(const std::string& name) const {
+    for (const auto& buf : buffers_) {
+        if (buf->name() == name) {
+            return buf;
+        }
+    }
+    return nullptr;
+}
+
+std::vector<std::string> BufferManager::get_buffer_names() const {
+    std::vector<std::string> names;
+    names.reserve(buffers_.size());
+    for (const auto& buf : buffers_) {
+        names.push_back(buf->name());
+    }
+    return names;
+}
+
+std::vector<BufferManager::BufferInfo> BufferManager::get_all_buffer_info() const {
+    std::vector<BufferInfo> info;
+    info.reserve(buffers_.size());
+
+    for (const auto& buf : buffers_) {
+        BufferInfo bi;
+        bi.name = buf->name();
+        bi.size = buf->line_count();
+        bi.modified = buf->is_modified();
+        bi.mode = "fundamental-mode"; // TODO: Get actual mode from buffer
+        bi.filepath = buf->file_path();
+        info.push_back(bi);
+    }
+
+    return info;
+}
+
+} // namespace lumacs

+ 141 - 615
src/editor_core.cpp

@@ -4,26 +4,31 @@
 #include "lumacs/completion_system.hpp" // Include CompletionSystem header
 #include "lumacs/minibuffer_manager.hpp" // Include MinibufferManager header
 #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 <algorithm>
 #include <iostream>
 
 namespace lumacs {
 
 EditorCore::EditorCore() :
-    buffers_(),
-    root_node_(),
-    active_window_(),
+    // root_node_(), // Managed by WindowManager
+    // active_window_(), // Managed by WindowManager
     last_message_(),
     message_clear_time_(),
     event_callbacks_(),
-    kill_ring_(),
+    // kill_ring_(), // Managed by KillRingManager
     last_yank_start_(),
     last_yank_end_(),
-    registers_(),
-    current_macro_(),
-    last_macro_(),
-    recording_macro_(false),
-    rectangle_kill_ring_(),
+    // 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
@@ -32,18 +37,20 @@ EditorCore::EditorCore() :
     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
+    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
 {
     // LuaApi needs core_ pointer to be valid, so set it after constructor body starts
     lua_api_->set_core(*this);
 
-    // Create initial buffer
-    auto buffer = std::make_shared<Buffer>();
-    buffers_.push_back(buffer);
-    
-    // Create initial window
-    active_window_ = std::make_shared<Window>(buffer);
-    root_node_ = std::make_shared<LayoutNode>(active_window_);
+    // 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();
@@ -55,393 +62,83 @@ EditorCore::EditorCore() :
 
 EditorCore::~EditorCore() = default;
 
-// === Buffer Management ===
-
-const Buffer& EditorCore::buffer() const noexcept {
-    return active_window_->buffer();
-}
-
-Buffer& EditorCore::buffer() noexcept {
-    return active_window_->buffer();
-}
-
-bool EditorCore::load_file(const std::filesystem::path& path) {
-    std::filesystem::path abs_path = std::filesystem::absolute(path);
-
-    // Check if already open
-    for (const auto& buf : buffers_) {
-        if (buf->file_path() && std::filesystem::equivalent(*buf->file_path(), abs_path)) {
-            active_window_->set_buffer(buf);
-            emit_event(EditorEvent::BufferModified);
-            emit_event(EditorEvent::CursorMoved);
-            emit_event(EditorEvent::ViewportChanged);
-            return true;
-        }
-    }
-
-    auto new_buffer_opt = Buffer::from_file(abs_path);
-    if (!new_buffer_opt) {
-        return false;
-    }
-    
-    auto new_buffer = std::make_shared<Buffer>(std::move(*new_buffer_opt));
-    buffers_.push_back(new_buffer);
-
-    active_window_->set_buffer(new_buffer);
-
-    emit_event(EditorEvent::BufferModified);
-    emit_event(EditorEvent::CursorMoved);
-    emit_event(EditorEvent::ViewportChanged);
-    return true;
-}
-
-void EditorCore::new_buffer(std::string name) {
-    auto new_buffer = std::make_shared<Buffer>(std::move(name));
-    buffers_.push_back(new_buffer);
-    active_window_->set_buffer(new_buffer);
-
-    emit_event(EditorEvent::BufferModified);
-    emit_event(EditorEvent::CursorMoved);
-    emit_event(EditorEvent::ViewportChanged);
-}
-
-std::vector<std::string> EditorCore::get_buffer_names() const {
-    std::vector<std::string> names;
-    names.reserve(buffers_.size());
-    for (const auto& buf : buffers_) {
-        names.push_back(buf->name());
-    }
-    return names;
-}
-
-std::shared_ptr<Buffer> EditorCore::get_buffer_by_name(const std::string& name) {
-    for (const auto& buf : buffers_) {
-        if (buf->name() == name) {
-            return buf;
-        }
-    }
-    return nullptr;
-}
-
-bool EditorCore::switch_buffer_in_window(const std::string& name) {
-    auto buf = get_buffer_by_name(name);
-    if (!buf) {
-        return false;
-    }
-
-    active_window_->set_buffer(buf);
-    emit_event(EditorEvent::BufferModified);
-    emit_event(EditorEvent::CursorMoved);
-    emit_event(EditorEvent::ViewportChanged);
-    return true;
-}
-
-bool EditorCore::close_buffer(const std::string& name) {
-    auto buf = get_buffer_by_name(name);
-    if (!buf) {
-        return false;
-    }
-
-    // Cannot close buffer if it's the only one
-    if (buffers_.size() <= 1) {
-        set_message("Cannot close last buffer");
-        return false;
-    }
-
-    // Check if buffer is displayed in any window
-    std::vector<std::shared_ptr<Window>> windows;
-    collect_windows(root_node_.get(), windows);
-
-    for (const auto& win : windows) {
-        if (win->buffer_ptr() == buf) {
-            // Buffer is displayed, switch to another buffer first
-            // Find another buffer
-            auto other_buf = buffers_.front() == buf ? *(++buffers_.begin()) : buffers_.front();
-            win->set_buffer(other_buf);
-        }
-    }
-
-    // Remove buffer from list
-    buffers_.remove(buf);
+// === Window Management (Delegated to WindowManager) ===
 
-    emit_event(EditorEvent::BufferModified);
-    emit_event(EditorEvent::CursorMoved);
-    emit_event(EditorEvent::ViewportChanged);
-    return true;
-}
+// These are still in EditorCore because they are part of the ICommandTarget interface
+// but their implementation delegates to WindowManager.
 
-std::vector<EditorCore::BufferInfo> EditorCore::get_all_buffer_info() const {
-    std::vector<BufferInfo> info;
-    info.reserve(buffers_.size());
-
-    for (const auto& buf : buffers_) {
-        BufferInfo bi;
-        bi.name = buf->name();
-        bi.size = buf->line_count();
-        bi.modified = buf->is_modified();
-        bi.mode = "fundamental-mode"; // TODO: Get actual mode from buffer
-        bi.filepath = buf->file_path();
-        info.push_back(bi);
-    }
+/*
+// Helper to recursively replace a window in the tree (Moved to WindowManager)
+bool replace_window_node(...) { ... }
 
-    return info;
-}
+// Recursive parent finder (Moved to WindowManager)
+LayoutNode* find_parent_of_node(...) { ... }
 
-// === Window Management ===
-
-// Helper to recursively replace a window in the tree
-bool replace_window_node(std::shared_ptr<LayoutNode> node, 
-                        std::shared_ptr<Window> target, 
-                        std::shared_ptr<LayoutNode> replacement) {
-    if (node->type == LayoutNode::Type::Leaf) {
-        return false;
-    }
-    
-    // Check immediate children
-    if (node->child1->type == LayoutNode::Type::Leaf) {
-        if (node->child1->window == target) {
-            node->child1 = replacement;
-            return true;
-        }
-    }
-    
-    if (node->child2->type == LayoutNode::Type::Leaf) {
-        if (node->child2->window == target) {
-            node->child2 = replacement;
-            return true;
-        }
-    }
-    
-    // Recurse
-    bool found = false;
-    if (node->child1->type != LayoutNode::Type::Leaf) {
-        found = replace_window_node(node->child1, target, replacement);
-    }
-    
-    if (!found && node->child2->type != LayoutNode::Type::Leaf) {
-        found = replace_window_node(node->child2, target, replacement);
-    }
-    
-    return found;
-}
+// Recursive leaf finder (Moved to WindowManager)
+LayoutNode* find_node_with_window(...) { ... }
+*/
 
 void EditorCore::split_horizontally() {
-    std::cerr << "[DEBUG] split_horizontally() called" << std::endl;
-
-    // New window sharing same buffer
-    auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
-    new_window->set_cursor(active_window_->cursor()); // Start at same position
-
-    // Create split node
-    auto new_leaf = std::make_shared<LayoutNode>(new_window);
-    auto current_leaf = std::make_shared<LayoutNode>(active_window_);
-
-    auto split = std::make_shared<LayoutNode>(
-        LayoutNode::Type::HorizontalSplit,
-        current_leaf, // Top
-        new_leaf      // Bottom
-    );
-
-    if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
-        std::cerr << "[DEBUG] Replacing root node" << std::endl;
-        root_node_ = split;
-    } else {
-        std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
-        replace_window_node(root_node_, active_window_, split);
-    }
-
-    active_window_ = new_window; // Focus new window
-    emit_event(EditorEvent::WindowLayoutChanged);
-    std::cerr << "[DEBUG] split_horizontally() completed" << std::endl;
+    window_manager_->split_horizontally();
 }
 
 void EditorCore::split_vertically() {
-    std::cerr << "[DEBUG] split_vertically() called" << std::endl;
-
-    // New window sharing same buffer
-    auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
-    new_window->set_cursor(active_window_->cursor());
-
-    // Create split node
-    auto new_leaf = std::make_shared<LayoutNode>(new_window);
-    auto current_leaf = std::make_shared<LayoutNode>(active_window_);
-
-    // Vertical Split = Left/Right division
-    auto split = std::make_shared<LayoutNode>(
-        LayoutNode::Type::VerticalSplit,
-        current_leaf, // Left
-        new_leaf      // Right
-    );
-
-    if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
-        std::cerr << "[DEBUG] Replacing root node" << std::endl;
-        root_node_ = split;
-    } else {
-        std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
-        replace_window_node(root_node_, active_window_, split);
-    }
-    
-    active_window_ = new_window;
-    emit_event(EditorEvent::WindowLayoutChanged);
-    std::cerr << "[DEBUG] split_vertically() completed" << std::endl;
-}
-
-// Recursive parent finder
-LayoutNode* find_parent_of_node(LayoutNode* current, LayoutNode* child_target) {
-    if (current->type == LayoutNode::Type::Leaf) return nullptr;
-    
-    if (current->child1.get() == child_target || current->child2.get() == child_target) {
-        return current;
-    }
-    
-    auto left = find_parent_of_node(current->child1.get(), child_target);
-    if (left) return left;
-    
-    return find_parent_of_node(current->child2.get(), child_target);
-}
-
-// Recursive leaf finder
-LayoutNode* find_node_with_window(LayoutNode* current, std::shared_ptr<Window> target) {
-    if (current->type == LayoutNode::Type::Leaf) {
-        return (current->window == target) ? current : nullptr;
-    }
-    auto left = find_node_with_window(current->child1.get(), target);
-    if (left) return left;
-    return find_node_with_window(current->child2.get(), target);
+    window_manager_->split_vertically();
 }
 
-
 void EditorCore::close_active_window() {
-    // Cannot close last window
-    if (root_node_->type == LayoutNode::Type::Leaf) {
-        return;
-    }
-    
-    // Find the node containing active_window
-    LayoutNode* target_node = find_node_with_window(root_node_.get(), active_window_);
-    if (!target_node) return; // Should not happen 
-    
-    LayoutNode* parent = find_parent_of_node(root_node_.get(), target_node);
-    if (!parent) return; // Should not happen if not root
-    
-    // Identify sibling
-    std::shared_ptr<LayoutNode> sibling;
-    if (parent->child1.get() == target_node) {
-        sibling = parent->child2;
-    } else {
-        sibling = parent->child1;
-    }
-    
-    // Replace parent with sibling
-    // If parent is root, root becomes sibling
-    if (parent == root_node_.get()) {
-        root_node_ = sibling;
-    } else {
-        // Find grandparent
-        LayoutNode* grandparent = find_parent_of_node(root_node_.get(), parent);
-        if (grandparent->child1.get() == parent) {
-            grandparent->child1 = sibling;
-        } else {
-            grandparent->child2 = sibling;
-        }
-    }
-    
-    // Focus a new window (first leaf in sibling)
-    std::vector<std::shared_ptr<Window>> windows;
-    collect_windows(sibling.get(), windows);
-    if (!windows.empty()) {
-        active_window_ = windows[0];
-    }
-    
-    emit_event(EditorEvent::WindowLayoutChanged);
+    window_manager_->close_active_window();
 }
 
 void EditorCore::collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) {
-    if (node->type == LayoutNode::Type::Leaf) {
-        windows.push_back(node->window);
-    } else {
-        collect_windows(node->child1.get(), windows);
-        collect_windows(node->child2.get(), windows);
-    }
+    window_manager_->collect_windows(node, windows);
 }
 
 void EditorCore::next_window() {
-    // Cycle to the next window in the window tree
-    // Note: Focus jumping bug was fixed in GTK frontend by caching active_window during redraws
-    std::vector<std::shared_ptr<Window>> windows;
-    collect_windows(root_node_.get(), windows);
-
-    if (windows.size() <= 1) return;
-
-    auto it = std::find(windows.begin(), windows.end(), active_window_);
-    if (it != windows.end()) {
-        auto next = it + 1;
-        if (next == windows.end()) {
-            active_window_ = windows[0];
-        } else {
-            active_window_ = *next;
-        }
-        emit_event(EditorEvent::WindowFocused);
-    }
+    window_manager_->next_window();
 }
 
 void EditorCore::next_window_safe() {
-    // Deprecated: Use next_window() instead. Kept for backwards compatibility.
     next_window();
 }
 
 bool EditorCore::set_active_window(std::shared_ptr<Window> window) {
-    if (!window) return false;
-    
-    // Verify that the window exists in the current window tree
-    std::vector<std::shared_ptr<Window>> windows;
-    collect_windows(root_node_.get(), windows);
-    
-    auto it = std::find(windows.begin(), windows.end(), window);
-    if (it != windows.end()) {
-        if (active_window_ != window) {
-            active_window_ = window;
-            emit_event(EditorEvent::WindowFocused);
-        }
-        return true;
-    }
-    
-    return false; // Window not found in tree
+    return window_manager_->set_active_window(window);
 }
 
 // === Cursor Proxies ===
 
 Position EditorCore::cursor() const noexcept {
-    return active_window_->cursor();
+    return active_window()->cursor(); // Uses delegated active_window()
 }
 
 void EditorCore::set_cursor(Position pos) {
-    active_window_->set_cursor(pos);
+    active_window()->set_cursor(pos); // Uses delegated active_window()
     emit_event(EditorEvent::CursorMoved);
 }
 
 void EditorCore::move_up() {
-    active_window_->move_up();
+    active_window()->move_up();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_down() {
-    active_window_->move_down();
+    active_window()->move_down();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_left() {
-    active_window_->move_left();
+    active_window()->move_left();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_right() {
-    active_window_->move_right();
+    active_window()->move_right();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_to_line_start() {
-    active_window_->move_to_line_start();
+    active_window()->move_to_line_start();
     emit_event(EditorEvent::CursorMoved);
 }
 void EditorCore::move_to_line_end() {
-    active_window_->move_to_line_end();
+    active_window()->move_to_line_end();
     emit_event(EditorEvent::CursorMoved);
 }
 
@@ -452,19 +149,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(active_window()->cursor());
+    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(active_window()->cursor());
+    active_window()->set_cursor(new_pos);
     emit_event(EditorEvent::CursorMoved);
 }
 
 Position EditorCore::calculate_forward_word_pos(Position start_pos) {
-    auto& buf = active_window_->buffer();
+    // Delegates to active buffer
+    auto& buf = buffer(); 
     auto cursor = start_pos;
     
     // Check if we are at the end of buffer
@@ -505,7 +203,8 @@ Position EditorCore::calculate_forward_word_pos(Position start_pos) {
 }
 
 Position EditorCore::calculate_backward_word_pos(Position start_pos) {
-    auto& buf = active_window_->buffer();
+    // Delegates to active buffer
+    auto& buf = buffer(); 
     auto cursor = start_pos;
 
     // Skip whitespace and punctuation backwards
@@ -538,8 +237,8 @@ Position EditorCore::calculate_backward_word_pos(Position start_pos) {
 }
 
 void EditorCore::page_up() {
-    auto& viewport = active_window_->viewport();
-    auto cursor = active_window_->cursor();
+    auto& viewport = active_window()->viewport(); // Uses delegated active_window()
+    auto cursor = active_window()->cursor(); // Uses delegated active_window()
 
     // Move up by viewport height (minus 2 for overlap)
     int page_size = std::max(1, viewport.height - 2);
@@ -551,20 +250,20 @@ void EditorCore::page_up() {
     }
 
     // Keep column position if possible
-    auto& buf = active_window_->buffer();
+    auto& buf = buffer();
     const auto& line = buf.line(cursor.line);
     cursor.column = std::min(cursor.column, line.size());
 
-    active_window_->set_cursor(cursor);
-    active_window_->adjust_scroll();
+    active_window()->set_cursor(cursor); // Uses delegated active_window()
+    active_window()->adjust_scroll(); // Uses delegated active_window()
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::page_down() {
-    auto& viewport = active_window_->viewport();
-    auto cursor = active_window_->cursor();
-    auto& buf = active_window_->buffer();
+    auto& viewport = active_window()->viewport(); // Uses delegated active_window()
+    auto cursor = active_window()->cursor(); // Uses delegated active_window()
+    auto& buf = buffer();
 
     // Move down by viewport height (minus 2 for overlap)
     int page_size = std::max(1, viewport.height - 2);
@@ -575,72 +274,72 @@ void EditorCore::page_down() {
     const auto& line = buf.line(cursor.line);
     cursor.column = std::min(cursor.column, line.size());
 
-    active_window_->set_cursor(cursor);
-    active_window_->adjust_scroll();
+    active_window()->set_cursor(cursor); // Uses delegated active_window()
+    active_window()->adjust_scroll(); // Uses delegated active_window()
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::goto_beginning() {
     Position pos = {0, 0};
-    active_window_->set_cursor(pos);
-    active_window_->adjust_scroll();
+    active_window()->set_cursor(pos); // Uses delegated active_window()
+    active_window()->adjust_scroll(); // Uses delegated active_window()
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::goto_end() {
-    auto& buf = active_window_->buffer();
+    auto& buf = 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);
-    active_window_->adjust_scroll();
+    active_window()->set_cursor(pos); // Uses delegated active_window()
+    active_window()->adjust_scroll(); // Uses delegated active_window()
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::goto_line(size_t line) {
-    auto& buf = active_window_->buffer();
+    auto& buf = 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);
-    active_window_->adjust_scroll();
+    active_window()->set_cursor(pos); // Uses delegated active_window()
+    active_window()->adjust_scroll(); // Uses delegated active_window()
     emit_event(EditorEvent::CursorMoved);
     emit_event(EditorEvent::ViewportChanged);
 }
 
-// === Viewport Proxies ===
+// === Viewport Management (Proxies to active window) ===
 
 const Viewport& EditorCore::viewport() const noexcept {
-    return active_window_->viewport();
+    return active_window()->viewport(); // Uses delegated active_window()
 }
 
 void EditorCore::set_viewport_size(int width, int height) {
-    active_window_->set_viewport_size(width, height);
+    active_window()->set_viewport_size(width, height); // Uses delegated active_window()
     emit_event(EditorEvent::ViewportChanged);
 }
 
 void EditorCore::adjust_scroll() {
-    active_window_->adjust_scroll();
+    active_window()->adjust_scroll(); // Uses delegated active_window()
     emit_event(EditorEvent::ViewportChanged);
 }
 
 std::pair<size_t, size_t> EditorCore::visible_line_range() const {
-    return active_window_->visible_line_range();
+    return active_window()->visible_line_range(); // Uses delegated active_window()
 }
 
 // === Undo/Redo ===
 
 bool EditorCore::undo() {
-    auto& buf = active_window_->buffer();
-    buf.save_undo_state(active_window_->cursor()); // Save state before
+    auto& buf = buffer();
+    buf.save_undo_state(active_window()->cursor()); // Uses delegated active_window()
 
-    Position new_cursor = active_window_->cursor();
+    Position new_cursor = active_window()->cursor(); // Uses delegated active_window()
     if (buf.undo(new_cursor)) {
-        active_window_->set_cursor(new_cursor);
+        active_window()->set_cursor(new_cursor); // Uses delegated active_window()
         emit_event(EditorEvent::CursorMoved);
         emit_event(EditorEvent::BufferModified);
         return true;
@@ -649,10 +348,10 @@ bool EditorCore::undo() {
 }
 
 bool EditorCore::redo() {
-    auto& buf = active_window_->buffer();
-    Position new_cursor = active_window_->cursor();
+    auto& buf = buffer();
+    Position new_cursor = active_window()->cursor(); // Uses delegated active_window()
     if (buf.redo(new_cursor)) {
-        active_window_->set_cursor(new_cursor);
+        active_window()->set_cursor(new_cursor); // Uses delegated active_window()
         emit_event(EditorEvent::CursorMoved);
         emit_event(EditorEvent::BufferModified);
         return true;
@@ -661,18 +360,18 @@ bool EditorCore::redo() {
 }
 
 bool EditorCore::can_undo() const {
-    return active_window_->buffer().can_undo();
+    return buffer().can_undo();
 }
 
 bool EditorCore::can_redo() const {
-    return active_window_->buffer().can_redo();
+    return buffer().can_redo();
 }
 
-// === Kill Ring ===
+// === Kill Ring (Delegated to KillRingManager) ===
 
 void EditorCore::kill_line() {
-    auto& buf = active_window_->buffer();
-    auto cursor = active_window_->cursor();
+    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)
@@ -683,8 +382,7 @@ void EditorCore::kill_line() {
             Position end = {cursor.line + 1, 0};
             Range range = {start, end};
 
-            std::string killed_text = "\n";
-            kill_ring_.push(killed_text);
+            kill_ring_manager_->push("\n"); // Delegate to manager
 
             buf.erase(range);
             emit_event(EditorEvent::BufferModified);
@@ -701,7 +399,7 @@ void EditorCore::kill_line() {
 
     std::string killed_text = buf.get_text_in_range(range);
     if (!killed_text.empty()) {
-        kill_ring_.push(killed_text);
+        kill_ring_manager_->push(killed_text); // Delegate to manager
         buf.erase(range);
         emit_event(EditorEvent::BufferModified);
 
@@ -710,8 +408,8 @@ void EditorCore::kill_line() {
 }
 
 void EditorCore::kill_region() {
-    auto& buf = active_window_->buffer();
-    auto cursor = active_window_->cursor();
+    auto& buf = buffer();
+    auto cursor = active_window()->cursor();
 
     auto region = buf.get_region(cursor);
     if (!region.has_value()) {
@@ -721,11 +419,11 @@ void EditorCore::kill_region() {
 
     std::string killed_text = buf.get_text_in_range(region.value());
     if (!killed_text.empty()) {
-        kill_ring_.push(killed_text);
+        kill_ring_manager_->push(killed_text); // Delegate to manager
         buf.deactivate_mark();
 
         // Move cursor to start of killed region
-        active_window_->set_cursor(region.value().start);
+        active_window()->set_cursor(region.value().start);
 
         emit_event(EditorEvent::BufferModified);
         emit_event(EditorEvent::CursorMoved);
@@ -735,17 +433,17 @@ void EditorCore::kill_region() {
 }
 
 void EditorCore::kill_word() {
-    auto cursor = active_window_->cursor();
+    auto cursor = active_window()->cursor();
     auto end_pos = calculate_forward_word_pos(cursor);
     
-    if (cursor == end_pos) return;
+    if (cursor == end_pos) return; 
     
     Range range = {cursor, end_pos};
-    auto& buf = active_window_->buffer();
+    auto& buf = buffer();
     std::string text = buf.get_text_in_range(range);
     
     if (!text.empty()) {
-        kill_ring_.push(text);
+        kill_ring_manager_->push(text); // Delegate to manager
         buf.erase(range);
         emit_event(EditorEvent::BufferModified);
         std::cerr << "[DEBUG] Killed word: '" << text << "'" << std::endl;
@@ -753,19 +451,19 @@ void EditorCore::kill_word() {
 }
 
 void EditorCore::backward_kill_word() {
-    auto cursor = active_window_->cursor();
+    auto cursor = active_window()->cursor();
     auto start_pos = calculate_backward_word_pos(cursor);
     
-    if (cursor == start_pos) return;
+    if (cursor == start_pos) return; 
     
     Range range = {start_pos, cursor};
-    auto& buf = active_window_->buffer();
+    auto& buf = buffer();
     std::string text = buf.get_text_in_range(range);
     
     if (!text.empty()) {
-        kill_ring_.push(text);
+        kill_ring_manager_->push(text); // Delegate to manager
         buf.erase(range);
-        active_window_->set_cursor(start_pos);
+        active_window()->set_cursor(start_pos);
         emit_event(EditorEvent::BufferModified);
         emit_event(EditorEvent::CursorMoved);
         std::cerr << "[DEBUG] Backward killed word: '" << text << "'" << std::endl;
@@ -774,8 +472,8 @@ void EditorCore::backward_kill_word() {
 
 
 void EditorCore::copy_region_as_kill() {
-    auto& buf = active_window_->buffer();
-    auto cursor = active_window_->cursor();
+    auto& buf = buffer();
+    auto cursor = active_window()->cursor();
 
     auto region = buf.get_region(cursor);
     if (!region.has_value()) {
@@ -785,7 +483,7 @@ void EditorCore::copy_region_as_kill() {
 
     std::string copied_text = buf.get_text_in_range(region.value());
     if (!copied_text.empty()) {
-        kill_ring_.push(copied_text);
+        kill_ring_manager_->push(copied_text); // Delegate to manager
         buf.deactivate_mark();
 
         set_message("Region copied");
@@ -794,18 +492,18 @@ void EditorCore::copy_region_as_kill() {
 }
 
 void EditorCore::yank() {
-    if (kill_ring_.empty()) {
+    if (kill_ring_manager_->empty()) { // Delegate to manager
         set_message("Kill ring is empty");
         return;
     }
 
-    std::string text = kill_ring_.current();
+    std::string text = kill_ring_manager_->current(); // Delegate to manager
     if (text.empty()) {
         return;
     }
 
-    auto& buf = active_window_->buffer();
-    auto cursor = active_window_->cursor();
+    auto& buf = buffer();
+    auto cursor = active_window()->cursor();
 
     // Save yank start position
     last_yank_start_ = cursor;
@@ -830,7 +528,7 @@ void EditorCore::yank() {
     }
 
     last_yank_end_ = new_cursor;
-    active_window_->set_cursor(new_cursor);
+    active_window()->set_cursor(new_cursor);
 
     emit_event(EditorEvent::BufferModified);
     emit_event(EditorEvent::CursorMoved);
@@ -839,7 +537,7 @@ void EditorCore::yank() {
 }
 
 void EditorCore::yank_pop() {
-    if (kill_ring_.empty()) {
+    if (kill_ring_manager_->empty()) { // Delegate to manager
         set_message("Kill ring is empty");
         return;
     }
@@ -850,16 +548,16 @@ void EditorCore::yank_pop() {
     }
 
     // Delete the previously yanked text
-    auto& buf = active_window_->buffer();
+    auto& buf = buffer();
     Range yank_range = {last_yank_start_.value(), last_yank_end_.value()};
     buf.erase(yank_range);
 
     // Get previous entry in kill ring
-    std::string text = kill_ring_.previous();
+    std::string text = kill_ring_manager_->previous(); // Delegate to manager
 
     // Restore cursor to yank start
     auto cursor = last_yank_start_.value();
-    active_window_->set_cursor(cursor);
+    active_window()->set_cursor(cursor);
 
     // Insert new text
     buf.insert(cursor, text);
@@ -877,7 +575,7 @@ void EditorCore::yank_pop() {
     }
 
     last_yank_end_ = new_cursor;
-    active_window_->set_cursor(new_cursor);
+    active_window()->set_cursor(new_cursor);
 
     emit_event(EditorEvent::BufferModified);
     emit_event(EditorEvent::CursorMoved);
@@ -885,7 +583,7 @@ void EditorCore::yank_pop() {
     std::cerr << "[DEBUG] Yank-pop: '" << text << "'" << std::endl;
 }
 
-// === Registers ===
+// === Registers (Delegated to RegisterManager) ===
 
 void EditorCore::copy_to_register(char register_name, const std::string& text) {
     // Validate register name (a-z, A-Z, 0-9)
@@ -893,8 +591,7 @@ void EditorCore::copy_to_register(char register_name, const std::string& text) {
         set_message("Invalid register name");
         return;
     }
-    
-    registers_[register_name] = text;
+    register_manager_->copy_to_register(register_name, text);
     set_message(std::string("Saved text to register ") + register_name);
 }
 
@@ -905,27 +602,28 @@ bool EditorCore::insert_register(char register_name) {
         return false;
     }
     
-    auto it = registers_.find(register_name);
-    if (it == registers_.end()) {
+    std::optional<std::string> text_opt = register_manager_->get_from_register(register_name);
+    if (!text_opt.has_value()) {
         set_message(std::string("Register ") + register_name + " is empty");
         return false;
     }
+    std::string text = text_opt.value();
     
     auto& buf = buffer();
-    Position cursor = active_window_->cursor();
-    buf.insert(cursor, it->second);
+    Position cursor = active_window()->cursor();
+    buf.insert(cursor, text);
     
     // Move cursor to end of inserted text
-    size_t newline_count = std::count(it->second.begin(), it->second.end(), '\n');
+    size_t newline_count = std::count(text.begin(), text.end(), '\n');
     if (newline_count > 0) {
         cursor.line += newline_count;
-        size_t last_newline = it->second.rfind('\n');
-        cursor.column = (last_newline != std::string::npos) ? (it->second.size() - last_newline - 1) : 0;
+        size_t last_newline = text.rfind('\n');
+        cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0;
     } else {
-        cursor.column += it->second.size();
+        cursor.column += text.size();
     }
     
-    active_window_->set_cursor(cursor);
+    active_window()->set_cursor(cursor);
     emit_event(EditorEvent::BufferModified);
     emit_event(EditorEvent::CursorMoved);
     
@@ -935,7 +633,7 @@ bool EditorCore::insert_register(char register_name) {
 
 void EditorCore::copy_region_to_register(char register_name) {
     auto& buf = buffer();
-    Position cursor = active_window_->cursor();
+    Position cursor = active_window()->cursor();
     
     auto region = buf.get_region(cursor);
     if (!region) {
@@ -951,204 +649,32 @@ bool EditorCore::yank_from_register(char register_name) {
     return insert_register(register_name);
 }
 
-// === Keyboard Macros ===
+// === Keyboard Macros (Delegated to MacroManager) ===
 
 void EditorCore::start_kbd_macro() {
-    if (recording_macro_) {
-        set_message("Already recording macro");
-        return;
-    }
-    
-    recording_macro_ = true;
-    current_macro_.clear();
-    set_message("Recording macro...");
+    macro_manager_->start_kbd_macro();
 }
 
 void EditorCore::end_kbd_macro_or_call() {
-    if (recording_macro_) {
-        // End recording
-        recording_macro_ = false;
-        last_macro_ = current_macro_;
-        current_macro_.clear();
-        
-        if (last_macro_.empty()) {
-            set_message("Macro recorded (empty)");
-        } else {
-            set_message(std::string("Macro recorded (") + std::to_string(last_macro_.size()) + " keys)");
-        }
-    } else {
-        // Call last macro
-        if (last_macro_.empty()) {
-            set_message("No macro recorded");
-            return;
-        }
-        
-        set_message(std::string("Executing macro (") + std::to_string(last_macro_.size()) + " keys)");
-        
-        // This is a simplified approach - in a real implementation, you'd need
-        // to replay the actual commands through the key binding system
-        // For now, we just show that the macro system is set up
-        for (const auto& key : last_macro_) {
-            // TODO: Execute the actual key binding for this key
-            // This would require access to the LuaApi from EditorCore
-            std::cerr << "[MACRO] Would execute key: " << key << std::endl;
-        }
-    }
+    macro_manager_->end_kbd_macro_or_call();
 }
 
 void EditorCore::record_key_sequence(const std::string& key_sequence) {
-    if (recording_macro_) {
-        current_macro_.push_back(key_sequence);
-    }
+    macro_manager_->record_key_sequence(key_sequence);
 }
 
-// === Rectangles ===
+// === Rectangles (Delegated to RectangleManager) ===
 
 void EditorCore::kill_rectangle() {
-    auto& buf = buffer();
-    Position cursor = active_window_->cursor();
-    
-    auto region = buf.get_region(cursor);
-    if (!region) {
-        set_message("No active region");
-        return;
-    }
-    
-    // Ensure start is top-left, end is bottom-right
-    Position start = region->start;
-    Position end = region->end;
-    
-    if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
-        std::swap(start, end);
-    }
-    
-    rectangle_kill_ring_.clear();
-    
-    // Extract rectangle line by line
-    for (size_t line = start.line; line <= end.line; ++line) {
-        if (line >= buf.line_count()) break;
-        
-        std::string line_text = buf.line(line);
-        size_t start_col = (line == start.line) ? start.column : std::min(start.column, end.column);
-        size_t end_col = (line == end.line) ? end.column : std::max(start.column, end.column);
-        
-        // Ensure we don't go beyond the line length
-        start_col = std::min(start_col, line_text.size());
-        end_col = std::min(end_col, line_text.size());
-        
-        if (start_col < end_col) {
-            rectangle_kill_ring_.push_back(line_text.substr(start_col, end_col - start_col));
-        } else {
-            rectangle_kill_ring_.push_back("");
-        }
-    }
-    
-    // Now delete the rectangle content (from bottom to top to preserve line indices)
-    for (int line = static_cast<int>(end.line); line >= static_cast<int>(start.line); --line) {
-        if (line >= static_cast<int>(buf.line_count())) continue;
-        
-        std::string line_text = buf.line(line);
-        size_t start_col = (line == static_cast<int>(start.line)) ? start.column : std::min(start.column, end.column);
-        size_t end_col = (line == static_cast<int>(end.line)) ? end.column : std::max(start.column, end.column);
-        
-        start_col = std::min(start_col, line_text.size());
-        end_col = std::min(end_col, line_text.size());
-        
-        if (start_col < end_col) {
-            Range del_range{Position(line, start_col), Position(line, end_col)};
-            buf.erase(del_range);
-        }
-    }
-    
-    buf.deactivate_mark();
-    emit_event(EditorEvent::BufferModified);
-    
-    set_message(std::string("Rectangle killed (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
+    rectangle_manager_->kill_rectangle();
 }
 
 void EditorCore::yank_rectangle() {
-    if (rectangle_kill_ring_.empty()) {
-        set_message("No rectangle in kill ring");
-        return;
-    }
-    
-    auto& buf = buffer();
-    Position cursor = active_window_->cursor();
-    
-    // Insert rectangle starting at cursor position
-    for (size_t i = 0; i < rectangle_kill_ring_.size(); ++i) {
-        Position insert_pos{cursor.line + i, cursor.column};
-        
-        // Ensure we have enough lines
-        while (buf.line_count() <= insert_pos.line) {
-            buf.insert_newline(Position{buf.line_count() - 1, buf.line(buf.line_count() - 1).size()});
-        }
-        
-        // Pad line with spaces if necessary
-        std::string current_line = buf.line(insert_pos.line);
-        if (current_line.size() < insert_pos.column) {
-            std::string padding(insert_pos.column - current_line.size(), ' ');
-            buf.insert(Position{insert_pos.line, current_line.size()}, padding);
-        }
-        
-        buf.insert(insert_pos, rectangle_kill_ring_[i]);
-    }
-    
-    emit_event(EditorEvent::BufferModified);
-    set_message(std::string("Rectangle yanked (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
+    rectangle_manager_->yank_rectangle();
 }
 
 void EditorCore::string_rectangle(const std::string& text) {
-    auto& buf = buffer();
-    Position cursor = active_window_->cursor();
-    
-    auto region = buf.get_region(cursor);
-    if (!region) {
-        set_message("No active region");
-        return;
-    }
-    
-    Position start = region->start;
-    Position end = region->end;
-    
-    if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
-        std::swap(start, end);
-    }
-    
-    // Fill rectangle with the given text
-    for (size_t line = start.line; line <= end.line; ++line) {
-        if (line >= buf.line_count()) break;
-        
-        std::string line_text = buf.line(line);
-        size_t start_col = std::min(start.column, end.column);
-        size_t end_col = std::max(start.column, end.column);
-        
-        // Pad line if necessary
-        if (line_text.size() < end_col) {
-            std::string padding(end_col - line_text.size(), ' ');
-            buf.insert(Position{line, line_text.size()}, padding);
-            line_text = buf.line(line); // Refresh
-        }
-        
-        // Replace rectangle content with text
-        if (start_col < line_text.size()) {
-            end_col = std::min(end_col, line_text.size());
-            if (start_col < end_col) {
-                Range replace_range{Position(line, start_col), Position(line, end_col)};
-                std::string fill_text = text;
-                if (fill_text.size() > end_col - start_col) {
-                    fill_text = fill_text.substr(0, end_col - start_col);
-                } else if (fill_text.size() < end_col - start_col) {
-                    fill_text += std::string(end_col - start_col - fill_text.size(), ' ');
-                }
-                buf.replace(replace_range, fill_text);
-            }
-        }
-    }
-    
-    buf.deactivate_mark();
-    emit_event(EditorEvent::BufferModified);
-    set_message("Rectangle filled");
+    rectangle_manager_->string_rectangle(text);
 }
 
 // === Private ===

+ 51 - 0
src/kill_ring_manager.cpp

@@ -0,0 +1,51 @@
+#include "lumacs/kill_ring_manager.hpp"
+#include <iostream> // For debug output, consider replacing with logging
+
+namespace lumacs {
+
+KillRingManager::KillRingManager(size_t max_size) : max_size_(max_size) {
+    if (max_size_ == 0) {
+        max_size_ = 1; // Ensure minimum size
+    }
+}
+
+void KillRingManager::push(const std::string& text) {
+    if (text.empty()) {
+        return;
+    }
+
+    if (!ring_.empty() && last_action_was_kill_) {
+        // If the last action was a kill, append to the last entry
+        ring_.front().append(text);
+    } else {
+        // Otherwise, create a new entry
+        ring_.push_front(text);
+        if (ring_.size() > max_size_) {
+            ring_.pop_back(); // Remove oldest entry if max size exceeded
+        }
+    }
+    last_action_was_kill_ = true;
+}
+
+std::string KillRingManager::current() const {
+    if (ring_.empty()) {
+        return "";
+    }
+    return ring_.front();
+}
+
+std::string KillRingManager::previous() {
+    if (ring_.empty()) {
+        return "";
+    }
+    
+    // Rotate the ring: move front to back
+    std::string front_item = ring_.front();
+    ring_.pop_front();
+    ring_.push_back(front_item);
+
+    last_action_was_kill_ = false; // Reset state for push logic
+    return ring_.front();
+}
+
+} // namespace lumacs

+ 60 - 0
src/macro_manager.cpp

@@ -0,0 +1,60 @@
+#include "lumacs/macro_manager.hpp"
+#include "lumacs/editor_core.hpp" // For EditorCore access
+#include <iostream> // For debug output, consider replacing with logging
+
+namespace lumacs {
+
+MacroManager::MacroManager(EditorCore& core) : core_(core) {
+}
+
+void MacroManager::start_kbd_macro() {
+    if (recording_macro_) {
+        core_.set_message("Already recording macro");
+        return;
+    }
+    
+    recording_macro_ = true;
+    current_macro_.clear();
+    core_.set_message("Recording macro...");
+}
+
+void MacroManager::end_kbd_macro_or_call() {
+    if (recording_macro_) {
+        // End recording
+        recording_macro_ = false;
+        last_macro_ = current_macro_;
+        current_macro_.clear();
+        
+        if (last_macro_.empty()) {
+            core_.set_message("Macro recorded (empty)");
+        } else {
+            core_.set_message(std::string("Macro recorded (") + std::to_string(last_macro_.size()) + " keys)");
+        }
+    } else {
+        // Call last macro
+        if (last_macro_.empty()) {
+            core_.set_message("No macro recorded");
+            return;
+        }
+        
+        core_.set_message(std::string("Executing macro (") + std::to_string(last_macro_.size()) + " keys)");
+        
+        // This is a simplified approach - in a real implementation, you'd need
+        // to replay the actual commands through the key binding system
+        // For now, we just show that the macro system is set up
+        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
+            // std::cerr << "[MACRO] Would execute key: " << key << std::endl;
+        }
+    }
+}
+
+void MacroManager::record_key_sequence(const std::string& key_sequence) {
+    if (recording_macro_) {
+        current_macro_.push_back(key_sequence);
+    }
+}
+
+} // namespace lumacs

+ 158 - 0
src/rectangle_manager.cpp

@@ -0,0 +1,158 @@
+#include "lumacs/rectangle_manager.hpp"
+#include "lumacs/editor_core.hpp" // For EditorCore access
+#include <algorithm>
+#include <iostream> // TODO: Replace with proper logging
+
+namespace lumacs {
+
+RectangleManager::RectangleManager(EditorCore& core) : core_(core) {
+}
+
+void RectangleManager::kill_rectangle() {
+    auto& buf = core_.buffer(); // Delegated access
+    Position cursor = core_.active_window()->cursor(); // Delegated access
+    
+    auto region = buf.get_region(cursor);
+    if (!region) {
+        core_.set_message("No active region");
+        return;
+    }
+    
+    // Ensure start is top-left, end is bottom-right
+    Position start = region->start;
+    Position end = region->end;
+    
+    if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
+        std::swap(start, end);
+    }
+    
+    rectangle_kill_ring_.clear();
+    
+    // Extract rectangle line by line
+    for (size_t line = start.line; line <= end.line; ++line) {
+        if (line >= buf.line_count()) break;
+        
+        std::string line_text = buf.line(line);
+        size_t start_col = (line == start.line) ? start.column : std::min(start.column, end.column);
+        size_t end_col = (line == end.line) ? end.column : std::max(start.column, end.column);
+        
+        // Ensure we don't go beyond the line length
+        start_col = std::min(start_col, line_text.size());
+        end_col = std::min(end_col, line_text.size());
+        
+        if (start_col < end_col) {
+            rectangle_kill_ring_.push_back(line_text.substr(start_col, end_col - start_col));
+        } else {
+            rectangle_kill_ring_.push_back("");
+        }
+    }
+    
+    // Now delete the rectangle content (from bottom to top to preserve line indices)
+    for (int line = static_cast<int>(end.line); line >= static_cast<int>(start.line); --line) {
+        if (line >= static_cast<int>(buf.line_count())) continue;
+        
+        std::string line_text = buf.line(line);
+        size_t start_col = (line == static_cast<int>(start.line)) ? start.column : std::min(start.column, end.column);
+        size_t end_col = (line == static_cast<int>(end.line)) ? end.column : std::max(start.column, end.column);
+        
+        start_col = std::min(start_col, line_text.size());
+        end_col = std::min(end_col, line_text.size());
+        
+        if (start_col < end_col) {
+            Range del_range{Position(line, start_col), Position(line, end_col)};
+            buf.erase(del_range);
+        }
+    }
+    
+    buf.deactivate_mark();
+    core_.emit_event(EditorEvent::BufferModified);
+    
+    core_.set_message(std::string("Rectangle killed (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
+}
+
+void RectangleManager::yank_rectangle() {
+    if (rectangle_kill_ring_.empty()) {
+        core_.set_message("No rectangle in kill ring");
+        return;
+    }
+    
+    auto& buf = core_.buffer(); // Delegated access
+    Position cursor = core_.active_window()->cursor(); // Delegated access
+    
+    // Insert rectangle starting at cursor position
+    for (size_t i = 0; i < rectangle_kill_ring_.size(); ++i) {
+        Position insert_pos{cursor.line + i, cursor.column};
+        
+        // Ensure we have enough lines
+        while (buf.line_count() <= insert_pos.line) {
+            buf.insert_newline(Position{buf.line_count() - 1, buf.line(buf.line_count() - 1).size()});
+        }
+        
+        // Pad line with spaces if necessary
+        std::string current_line = buf.line(insert_pos.line);
+        if (current_line.size() < insert_pos.column) {
+            std::string padding(insert_pos.column - current_line.size(), ' ');
+            buf.insert(Position{insert_pos.line, current_line.size()}, padding);
+        }
+        
+        buf.insert(insert_pos, rectangle_kill_ring_[i]);
+    }
+    
+    core_.emit_event(EditorEvent::BufferModified);
+    core_.set_message(std::string("Rectangle yanked (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
+}
+
+void RectangleManager::string_rectangle(const std::string& text) {
+    auto& buf = core_.buffer(); // Delegated access
+    Position cursor = core_.active_window()->cursor(); // Delegated access
+    
+    auto region = buf.get_region(cursor);
+    if (!region) {
+        core_.set_message("No active region");
+        return;
+    }
+    
+    Position start = region->start;
+    Position end = region->end;
+    
+    if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
+        std::swap(start, end);
+    }
+    
+    // Fill rectangle with the given text
+    for (size_t line = start.line; line <= end.line; ++line) {
+        if (line >= buf.line_count()) break;
+        
+        std::string line_text = buf.line(line);
+        size_t start_col = std::min(start.column, end.column);
+        size_t end_col = std::max(start.column, end.column);
+        
+        // Pad line if necessary
+        if (line_text.size() < end_col) {
+            std::string padding(end_col - line_text.size(), ' ');
+            buf.insert(Position{line, line_text.size()}, padding);
+            line_text = buf.line(line); // Refresh
+        }
+        
+        // Replace rectangle content with text
+        if (start_col < line_text.size()) {
+            end_col = std::min(end_col, line_text.size());
+            if (start_col < end_col) {
+                Range replace_range{Position(line, start_col), Position(line, end_col)};
+                std::string fill_text = text;
+                if (fill_text.size() > end_col - start_col) {
+                    fill_text = fill_text.substr(0, end_col - start_col);
+                } else if (fill_text.size() < end_col - start_col) {
+                    fill_text += std::string(end_col - start_col - fill_text.size(), ' ');
+                }
+                buf.replace(replace_range, fill_text);
+            }
+        }
+    }
+    
+    buf.deactivate_mark();
+    core_.emit_event(EditorEvent::BufferModified);
+    core_.set_message("Rectangle filled");
+}
+
+} // namespace lumacs

+ 22 - 0
src/register_manager.cpp

@@ -0,0 +1,22 @@
+#include "lumacs/register_manager.hpp"
+
+namespace lumacs {
+
+void RegisterManager::copy_to_register(char register_name, const std::string& text) {
+    // Basic validation, EditorCore will handle messages
+    if (std::isalnum(static_cast<unsigned char>(register_name))) {
+        registers_[register_name] = text;
+    }
+}
+
+std::optional<std::string> RegisterManager::get_from_register(char register_name) const {
+    if (std::isalnum(static_cast<unsigned char>(register_name))) {
+        auto it = registers_.find(register_name);
+        if (it != registers_.end()) {
+            return it->second;
+        }
+    }
+    return std::nullopt;
+}
+
+} // namespace lumacs

+ 235 - 0
src/window_manager.cpp

@@ -0,0 +1,235 @@
+#include "lumacs/window_manager.hpp"
+#include "lumacs/editor_core.hpp" // For EditorCore access
+#include <algorithm>
+#include <iostream> // TODO: Replace with proper logging
+
+namespace lumacs {
+
+// Helper to recursively replace a window in the tree
+bool WindowManager::replace_window_node(std::shared_ptr<LayoutNode> node, 
+                                        std::shared_ptr<Window> target, 
+                                        std::shared_ptr<LayoutNode> replacement) {
+    if (node->type == LayoutNode::Type::Leaf) {
+        return false;
+    }
+    
+    // Check immediate children
+    if (node->child1->type == LayoutNode::Type::Leaf) {
+        if (node->child1->window == target) {
+            node->child1 = replacement;
+            return true;
+        }
+    }
+    
+    if (node->child2->type == LayoutNode::Type::Leaf) {
+        if (node->child2->window == target) {
+            node->child2 = replacement;
+            return true;
+        }
+    }
+    
+    // Recurse
+    bool found = false;
+    if (node->child1->type != LayoutNode::Type::Leaf) {
+        found = replace_window_node(node->child1, target, replacement);
+    }
+    
+    if (!found && node->child2->type != LayoutNode::Type::Leaf) {
+        found = replace_window_node(node->child2, target, replacement);
+    }
+    
+    return found;
+}
+
+// Recursive parent finder
+LayoutNode* WindowManager::find_parent_of_node(LayoutNode* current, LayoutNode* child_target) {
+    if (current->type == LayoutNode::Type::Leaf) return nullptr;
+    
+    if (current->child1.get() == child_target || current->child2.get() == child_target) {
+        return current;
+    }
+    
+    auto left = find_parent_of_node(current->child1.get(), child_target);
+    if (left) return left;
+    
+    return find_parent_of_node(current->child2.get(), child_target);
+}
+
+// Recursive leaf finder
+LayoutNode* WindowManager::find_node_with_window(LayoutNode* current, std::shared_ptr<Window> target) {
+    if (current->type == LayoutNode::Type::Leaf) {
+        return (current->window == target) ? current : nullptr;
+    }
+    auto left = find_node_with_window(current->child1.get(), target);
+    if (left) return left;
+    return find_node_with_window(current->child2.get(), target);
+}
+
+
+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();
+    active_window_ = std::make_shared<Window>(initial_buffer);
+    root_node_ = std::make_shared<LayoutNode>(active_window_);
+}
+
+std::shared_ptr<Window> WindowManager::active_window() const {
+    return active_window_;
+}
+
+bool WindowManager::set_active_window(std::shared_ptr<Window> window) {
+    if (!window) return false;
+    
+    // Verify that the window exists in the current window tree
+    std::vector<std::shared_ptr<Window>> windows;
+    collect_windows(root_node_.get(), windows);
+    
+    auto it = std::find(windows.begin(), windows.end(), window);
+    if (it != windows.end()) {
+        if (active_window_ != window) {
+            active_window_ = window;
+            core_.emit_event(EditorEvent::WindowFocused);
+        }
+        return true;
+    }
+    
+    return false; // Window not found in tree
+}
+
+void WindowManager::split_horizontally() {
+    std::cerr << "[DEBUG] split_horizontally() called" << std::endl;
+
+    // New window sharing same buffer as active window
+    auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
+    new_window->set_cursor(active_window_->cursor()); // Start at same position
+
+    // Create split node
+    auto new_leaf = std::make_shared<LayoutNode>(new_window);
+    auto current_leaf = std::make_shared<LayoutNode>(active_window_);
+
+    auto split = std::make_shared<LayoutNode>(
+        LayoutNode::Type::HorizontalSplit,
+        current_leaf, // Top
+        new_leaf      // Bottom
+    );
+
+    if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
+        std::cerr << "[DEBUG] Replacing root node" << std::endl;
+        root_node_ = split;
+    } else {
+        std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
+        replace_window_node(root_node_, active_window_, split);
+    }
+
+    active_window_ = new_window; // Focus new window
+    core_.emit_event(EditorEvent::WindowLayoutChanged);
+    std::cerr << "[DEBUG] split_horizontally() completed" << std::endl;
+}
+
+void WindowManager::split_vertically() {
+    std::cerr << "[DEBUG] split_vertically() called" << std::endl;
+
+    // New window sharing same buffer as active window
+    auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
+    new_window->set_cursor(active_window_->cursor());
+
+    // Create split node
+    auto new_leaf = std::make_shared<LayoutNode>(new_window);
+    auto current_leaf = std::make_shared<LayoutNode>(active_window_);
+
+    // Vertical Split = Left/Right division
+    auto split = std::make_shared<LayoutNode>(
+        LayoutNode::Type::VerticalSplit,
+        current_leaf, // Left
+        new_leaf      // Right
+    );
+
+    if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
+        std::cerr << "[DEBUG] Replacing root node" << std::endl;
+        root_node_ = split;
+    } else {
+        std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
+        replace_window_node(root_node_, active_window_, split);
+    }
+    
+    active_window_ = new_window;
+    core_.emit_event(EditorEvent::WindowLayoutChanged);
+    std::cerr << "[DEBUG] split_vertically() completed" << std::endl;
+}
+
+void WindowManager::close_active_window() {
+    // Cannot close last window
+    if (root_node_->type == LayoutNode::Type::Leaf) {
+        return;
+    }
+    
+    // Find the node containing active_window
+    LayoutNode* target_node = find_node_with_window(root_node_.get(), active_window_);
+    if (!target_node) return; // Should not happen 
+    
+    LayoutNode* parent = find_parent_of_node(root_node_.get(), target_node);
+    if (!parent) return; // Should not happen if not root
+    
+    // Identify sibling
+    std::shared_ptr<LayoutNode> sibling;
+    if (parent->child1.get() == target_node) {
+        sibling = parent->child2;
+    } else {
+        sibling = parent->child1;
+    }
+    
+    // Replace parent with sibling
+    // If parent is root, root becomes sibling
+    if (parent == root_node_.get()) {
+        root_node_ = sibling;
+    } else {
+        // Find grandparent
+        LayoutNode* grandparent = find_parent_of_node(root_node_.get(), parent);
+        if (grandparent->child1.get() == parent) {
+            grandparent->child1 = sibling;
+        } else {
+            grandparent->child2 = sibling;
+        }
+    }
+    
+    // Focus a new window (first leaf in sibling)
+    std::vector<std::shared_ptr<Window>> windows;
+    collect_windows(sibling.get(), windows);
+    if (!windows.empty()) {
+        active_window_ = windows[0];
+    }
+    
+    core_.emit_event(EditorEvent::WindowLayoutChanged);
+}
+
+void WindowManager::next_window() {
+    // Cycle to the next window in the window tree
+    // Note: Focus jumping bug was fixed in GTK frontend by caching active_window during redraws
+    std::vector<std::shared_ptr<Window>> windows;
+    collect_windows(root_node_.get(), windows);
+
+    if (windows.size() <= 1) return;
+
+    auto it = std::find(windows.begin(), windows.end(), active_window_);
+    if (it != windows.end()) {
+        auto next = it + 1;
+        if (next == windows.end()) {
+            active_window_ = windows[0];
+        } else {
+            active_window_ = *next;
+        }
+        core_.emit_event(EditorEvent::WindowFocused);
+    }
+}
+
+void WindowManager::collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) {
+    if (node->type == LayoutNode::Type::Leaf) {
+        windows.push_back(node->window);
+    } else {
+        collect_windows(node->child1.get(), windows);
+        collect_windows(node->child2.get(), windows);
+    }
+}
+
+} // namespace lumacs