Bladeren bron

refactor(minibuffer): Centralize minibuffer logic (Phase Z)

Refactors the minibuffer system to be UI-agnostic and extensible.

This commit completes Phase Z of the refactoring roadmap by:
- Creating a centralized  to handle minibuffer state, input, and orchestration.
- Decoupling history management into a reusable  component.
- Implementing an extensible  with  for dynamic completion candidates.
- Externalizing minibuffer prompts and messages, separating interactive input from transient display.
- Ensuring  and  delegate key events to  for input handling.
- Simplifying minibuffer rendering in UI frontends by querying  for content.
- Removing legacy  usage from minibuffer rendering.

This significantly reduces coupling, removes code duplication across UIs, and enhances extensibility for future features.

The  enum definition has been moved to  and  definition has been correctly placed in  to resolve circular dependencies.
Bernardo Magri 1 maand geleden
bovenliggende
commit
4e487f1539

+ 2 - 0
CMakeLists.txt

@@ -55,6 +55,8 @@ add_library(lumacs_core STATIC
     src/keybinding.cpp
     src/command_system.cpp
     src/modeline.cpp
+    src/minibuffer_manager.cpp
+    src/completion_system.cpp
 )
 
 target_include_directories(lumacs_core PUBLIC

+ 77 - 0
GEMINI.md

@@ -0,0 +1,77 @@
+Lumacs Developer System Instructions
+1. Role & Objective
+You are the Lead C++ Systems Architect for Lumacs, a modern, Emacs-like text editor engine written in C++20 with Lua 5.4 scripting.
+
+Your goal is to execute the Refactoring Roadmap found in documentation/PLAN.md while maintaining strict memory safety and architectural purity.
+
+2. The "Non-Thinking" Compensator Protocol (RCI)
+CRITICAL INSTRUCTION: Because you are a fast-inference model, you must Simulate Reasoning before generating code. You are forbidden from outputting C++ code immediately.
+
+For every complex task, you must follow the RCI (Recursive Criticism and Improvement) loop:
+
+Step 1: Analysis & Plan
+Context: Identify which files (@filename) are involved.
+
+Draft Plan: Briefly outline how you intend to solve the problem.
+
+Architectural Check: Does this violate the "Core vs. View" separation? (e.g., logic in GtkEditor instead of EditorCore).
+
+Step 2: Critique (The "Thinking" Phase)
+Self-Correction: Critically analyze your own Draft Plan. Look for:
+
+Memory Safety: Are there dangling pointers? (Use std::shared_ptr/std::weak_ptr).
+
+Lua/C++ Boundary: Did I expose this new feature to lua_api.cpp? If not, it's useless to the user.
+
+GTK Threading: Am I updating UI widgets from a non-main thread?
+
+Cycle Detection: Will EditorCore -> Window -> EditorCore cause a dependency cycle?
+
+Refinement: Update the plan based on these findings.
+
+Step 3: Execution
+Only after Step 2, generate the C++ implementation.
+
+3. Project Architecture
+Core (src/core/): EditorCore, Buffer (Gap Buffer), Window. NO GTK CODE HERE.
+
+UI (src/ui/): GtkEditor (GUI), TuiEditor (Ncurses). Implements IEditorView.
+
+Scripting: Lua 5.4 via sol2. This is the source of truth for configuration.
+
+Build: CMake.
+
+4. Coding Standards (Strict C++20)
+Pointers: Never use raw pointers for ownership. Use std::unique_ptr by default, std::shared_ptr for shared resources (Buffers).
+
+Lua Bindings:
+
+If you add a method KillRing::yank(), you MUST immediately add the binding in LuaApi::initialize_lua_state.
+
+Use sol::state_view over sol::state where possible.
+
+Headers: Use #pragma once.
+
+Logging: Do not use std::cout/std::cerr. Use the project's logger (or standard error if logger not implemented yet, but mark with // TODO: Log).
+
+5. The PLAN.md Protocol
+documentation/PLAN.md is the Single Source of Truth.
+
+Read First: Before writing code, check the "Current Focus" in PLAN.md.
+
+Write Last: After finishing a task, output a modified version of PLAN.md checking off the task ( [x] ) and adding notes to the "Status Update" section.
+
+6. Output Format
+Always provide shell commands for file creation/updates. Use Conventional Commits for git messages.
+
+```
+Bash
+
+# Example Output format
+cat << 'EOF' > src/new_file.cpp
+... code ...
+EOF
+
+git add src/new_file.cpp
+git commit -m "feat(core): implement gap buffer resize logic"
+```

+ 4 - 2
documentation/PLAN.md

@@ -59,7 +59,7 @@ Lumacs/
 **Phase 15: Polishing and Refactoring**:
 - ✅ Removed unwanted GTK context menus and tooltips
 - ✅ Implemented Modeline Framework (Phase Y)
-- ▶️ Centralizing Minibuffer Logic (Phase Z)
+-  Centralizing Minibuffer Logic (Phase Z)
 
 ## Core Refactoring Goals
 
@@ -217,7 +217,9 @@ This phase aims to centralize and modularize the minibuffer logic, making it UI-
 
 ---
 **Status Update: Minibuffer Framework Refactoring**
-(Reviewed: All granular items have been reviewed. The system provides functional minibuffer polish (tab completion, history, kill-buffer), a robust C++ command registry with fuzzy completion, an enhanced minibuffer with autocompletion and visual feedback, a comprehensive Lua Command API, and a robust File System Completion. However, throughout these reviews, significant architectural issues have been identified, including high coupling and code duplication in minibuffer logic across UIs, lack of a formal `InteractiveSpec` for command arguments, untyped command arguments, duplicated command string parsing, mixed completion approach, and tight coupling of CommandSystem with EditorCore. These findings strongly reinforce the necessity of refactoring efforts outlined in PLAN.md Phases Z and C.)
+(Reviewed: All subtasks for Phase Z (Z.1 to Z.7) have been completed. The minibuffer logic is now centralized in `MinibufferManager`, decoupled from UI frontends. This includes history management via `HistoryManager`, an extensible completion system with `ICompletionSource` implementations, and externalized prompts and message display. UI frontends (GtkEditor and TuiEditor) now delegate minibuffer input handling and query `MinibufferManager` for rendering content, significantly reducing coupling and code duplication. Legacy `ThemeElement` usage has been removed from minibuffer rendering, aligning with the unified Face system. This marks the full completion of Phase Z.)
+
+### Phase A: GTK Frontend Refactoring
 
 ### Phase A: GTK Frontend Refactoring
 

+ 2 - 15
include/lumacs/command_system.hpp

@@ -7,6 +7,8 @@
 #include <unordered_map>
 #include <filesystem>
 
+#include "lumacs/completion_common.hpp" // For CompletionCandidate
+
 namespace lumacs {
 
 class EditorCore; // Forward declaration
@@ -38,21 +40,6 @@ struct Command {
           function(std::move(func)), aliases(std::move(alias)), interactive(inter) {}
 };
 
-/// @brief A possible completion match for user input.
-struct CompletionCandidate {
-    std::string text;       ///< The completion text.
-    int score;              ///< Matching score (higher is better).
-    std::string description;///< Extra info (e.g., file type, command desc).
-    
-    CompletionCandidate(std::string t, int s = 0, std::string desc = "")
-        : text(std::move(t)), score(s), description(std::move(desc)) {}
-        
-    bool operator<(const CompletionCandidate& other) const {
-        if (score != other.score) return score > other.score; // Higher score first
-        return text < other.text; // Alphabetical for same score
-    }
-};
-
 /// @brief Provider function for generating completions.
 using CompletionProvider = std::function<std::vector<CompletionCandidate>(const std::string& input)>;
 

+ 29 - 0
include/lumacs/completion_common.hpp

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace lumacs {
+
+/// @brief Represents a single completion candidate.
+struct CompletionCandidate {
+    std::string text; ///< The completed text.
+    std::string display_text; ///< Optional: Text to display to the user (can be richer).
+    std::string description; ///< Optional: A short description of the candidate.
+    int score; ///< Matching score (higher is better).
+
+    CompletionCandidate(std::string text, int score = 0, std::string display = "", std::string desc = "")
+        : text(std::move(text)), display_text(std::move(display)), description(std::move(desc)), score(score) {
+        if (display_text.empty()) {
+            display_text = this->text;
+        }
+    }
+
+    bool operator<(const CompletionCandidate& other) const {
+        if (score != other.score) return score > other.score; // Higher score first
+        return text < other.text; // Alphabetical for same score
+    }
+};
+
+} // namespace lumacs

+ 43 - 0
include/lumacs/completion_system.hpp

@@ -0,0 +1,43 @@
+#include <string>
+#include <vector>
+#include <memory>
+#include <unordered_map> // For std::unordered_map
+
+#include "lumacs/minibuffer_mode.hpp" // For MinibufferMode enum
+#include "lumacs/minibuffer_mode_hash.hpp" // For MinibufferMode hash specialization
+#include "lumacs/completion_common.hpp" // For CompletionCandidate struct
+
+namespace lumacs {
+
+// Forward declaration
+class EditorCore; 
+
+/// @brief Interface for a completion source.
+/// Different minibuffer modes will use different implementations of this interface.
+class ICompletionSource {
+public:
+    virtual ~ICompletionSource() = default;
+
+    /// @brief Retrieves completion candidates based on the current input.
+    /// @param input_text The current text entered by the user in the minibuffer.
+    /// @return A vector of matching completion candidates.
+    virtual std::vector<CompletionCandidate> get_candidates(const std::string& input_text) = 0;
+};
+
+/// @brief Manages various completion sources and provides candidates for the minibuffer.
+class CompletionSystem {
+public:
+    explicit CompletionSystem(EditorCore& core);
+
+    /// @brief Registers a completion source for a specific minibuffer mode.
+    void register_source(MinibufferMode mode, std::unique_ptr<ICompletionSource> source);
+
+    /// @brief Retrieves completion candidates for a given mode and input.
+    std::vector<CompletionCandidate> get_candidates_for_mode(MinibufferMode mode, const std::string& input_text);
+
+private:
+    EditorCore& core_;
+    std::unordered_map<MinibufferMode, std::unique_ptr<ICompletionSource>> sources_;
+};
+
+} // namespace lumacs

+ 43 - 39
include/lumacs/editor_core.hpp

@@ -7,41 +7,41 @@
 #include "lumacs/config.hpp"
 #include "lumacs/keybinding.hpp"
 #include "lumacs/modeline.hpp"
+// #include "lumacs/minibuffer_manager.hpp" // Changed to forward declaration below
+#include "lumacs/ui_interface.hpp" // Include for EditorEvent
 #include <memory>
 #include <functional>
 #include <vector>
 #include <list>
 #include <unordered_map>
+#include <chrono> // For std::chrono::steady_clock
+#include <optional> // For std::optional
 
 namespace lumacs {
 
 class LuaApi; // Forward declaration
 class CommandSystem; // Forward declaration
+class CompletionSystem; // Forward declaration
+class MinibufferManager; // Forward declaration
 
-/// @brief Editor state change events triggered by core actions.
-/// Used by the UI layer to react to model updates.
-enum class EditorEvent {
-    BufferModified,       ///< The content of the active buffer has changed.
-    CursorMoved,          ///< The cursor position has changed.
-    ViewportChanged,      ///< The visible area (scroll/size) has changed.
-    WindowLayoutChanged,  ///< The window tree structure (splits) has changed.
-    WindowFocused,        ///< A different window has gained focus.
-    Message,              ///< A transient message should be displayed (e.g., in minibuffer).
+/// @brief Represents a node in the window layout tree.
+struct LayoutNode {
+    enum class Type { Leaf, HorizontalSplit, VerticalSplit };
+    Type type;
     
-    // Modal Interaction Events
-    CommandMode,          ///< Enter command input mode (M-x).
-    BufferSwitchMode,     ///< Enter buffer switching mode (C-x b).
-    KillBufferMode,       ///< Enter kill buffer mode (C-x k).
-    FindFileMode,         ///< Enter file finding mode (C-x C-f).
-    ThemeSelectionMode,   ///< Enter theme selection mode.
-    ISearchMode,          ///< Enter incremental search mode (C-s).
-    ISearchBackwardMode,  ///< Enter backward incremental search mode (C-r).
+    // If Leaf
+    std::shared_ptr<Window> window;
     
-    Quit                  ///< The application should exit.
+    // 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) {}
 };
 
-struct LayoutNode;
-
 /// @brief Core logic of the Lumacs editor, independent of the UI framework.
 /// 
 /// This class acts as the central controller/facade for the editor's logic.
@@ -65,12 +65,23 @@ public:
     /// @param msg The message string to display.
     void set_message(std::string msg) {
         last_message_ = std::move(msg);
-        emit_event(EditorEvent::Message);
+        message_clear_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(5); // Clear after 5 seconds
+        emit_event(EditorEvent::Message); // Use this for transient messages
     }
 
     /// @brief Get the last set message.
     const std::string& last_message() const { return last_message_; }
 
+    /// @brief Check if a message is set and if its display time has expired.
+    /// Clears the message if expired.
+    void check_and_clear_message() {
+        if (message_clear_time_.has_value() && std::chrono::steady_clock::now() > message_clear_time_.value()) {
+            last_message_.clear();
+            message_clear_time_.reset();
+            emit_event(EditorEvent::TransientMessageCleared); // Notify UI
+        }
+    }
+
     // === Actions ===
     // These methods trigger specific input modes in the UI.
 
@@ -307,6 +318,14 @@ public:
     [[nodiscard]] ModelineManager& modeline_manager() noexcept { return modeline_manager_; }
     [[nodiscard]] const ModelineManager& modeline_manager() const noexcept { return modeline_manager_; }
 
+    // === Minibuffer Manager ===
+    [[nodiscard]] MinibufferManager& minibuffer_manager() noexcept { return *minibuffer_manager_; }
+    [[nodiscard]] const MinibufferManager& minibuffer_manager() const noexcept { return *minibuffer_manager_; }
+
+    // === Completion System ===
+    [[nodiscard]] CompletionSystem& completion_system() noexcept { return *completion_system_; }
+    [[nodiscard]] const CompletionSystem& completion_system() const noexcept { return *completion_system_; }
+
 private:
     std::list<std::shared_ptr<Buffer>> buffers_;
 
@@ -319,6 +338,7 @@ private:
     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_;
 
@@ -347,6 +367,8 @@ private:
     std::unique_ptr<LuaApi> lua_api_;
     std::unique_ptr<CommandSystem> command_system_;
     ModelineManager modeline_manager_;
+    std::unique_ptr<CompletionSystem> completion_system_; // Declared first
+    std::unique_ptr<MinibufferManager> minibuffer_manager_;
 
     void emit_event(EditorEvent event);
     
@@ -357,22 +379,4 @@ private:
     void collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows);
 };
 
-/// @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) {}
-};
-
 } // namespace lumacs

+ 67 - 0
include/lumacs/history_manager.hpp

@@ -0,0 +1,67 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <numeric> // For std::iota
+
+namespace lumacs {
+
+/// @brief Manages history for a specific minibuffer type (e.g., commands, file paths).
+class HistoryManager {
+public:
+    HistoryManager() : history_index_(0) {}
+
+    /// @brief Adds an item to the history.
+    /// Only adds if the item is not empty and not a duplicate of the last item.
+    void add_item(const std::string& item) {
+        if (!item.empty() && (history_.empty() || history_.back() != item)) {
+            history_.push_back(item);
+        }
+        history_index_ = history_.size(); // Reset index to end after adding
+    }
+
+    /// @brief Navigates to the previous item in history.
+    /// @return The previous item, or empty string if no more history.
+    std::string previous() {
+        if (!history_.empty()) {
+            if (history_index_ > 0) {
+                history_index_--;
+            }
+            return history_[history_index_];
+        }
+        return "";
+    }
+
+    /// @brief Navigates to the next item in history.
+    /// @return The next item, or empty string if at the end of history.
+    std::string next() {
+        if (!history_.empty()) {
+            if (history_index_ < history_.size() - 1) {
+                history_index_++;
+                return history_[history_index_];
+            } else if (history_index_ == history_.size() - 1) {
+                // If at the end, and pressing next, clear input (or original input before history nav)
+                history_index_++; // Move past the last valid index
+                return "";
+            }
+        }
+        return "";
+    }
+    
+    /// @brief Clears the history.
+    void clear() {
+        history_.clear();
+        history_index_ = 0;
+    }
+
+    /// @brief Returns true if history is empty, false otherwise.
+    bool empty() const {
+        return history_.empty();
+    }
+
+private:
+    std::vector<std::string> history_;
+    size_t history_index_; // Current position in history
+};
+
+} // namespace lumacs

+ 79 - 0
include/lumacs/minibuffer_manager.hpp

@@ -0,0 +1,79 @@
+#include <functional>
+#include <optional>
+#include <unordered_map> // For std::unordered_map
+
+#include "lumacs/history_manager.hpp" // New include
+#include "lumacs/minibuffer_mode_hash.hpp" // Include for MinibufferMode hash specialization
+#include "lumacs/minibuffer_mode.hpp" // Include for MinibufferMode enum definition
+#include "lumacs/completion_common.hpp" // Include for CompletionCandidate struct
+
+namespace lumacs {
+
+// Forward declarations to avoid circular dependencies
+class EditorCore; 
+class LuaApi;
+class CompletionSystem; // Forward declaration
+
+/// @brief Manages the state and logic for the minibuffer, independent of the UI.
+class MinibufferManager {
+public:
+    MinibufferManager(EditorCore& core, LuaApi& lua_api, CompletionSystem& completion_system);
+
+    /// @brief Sets the minibuffer into a specific mode and displays a prompt.
+    void activate_minibuffer(MinibufferMode mode, const std::string& prompt, 
+                             std::function<void(const std::string&)> on_submit,
+                             std::function<void()> on_cancel = nullptr);
+
+    /// @brief Deactivates the minibuffer and clears its state.
+    void deactivate_minibuffer();
+
+    /// @brief Handles a key event when the minibuffer is active.
+    /// @return True if the key was handled, false otherwise.
+    bool handle_key_event(const std::string& key_name);
+
+    /// @brief Returns the current prompt string for display.
+    std::string get_prompt() const;
+
+    /// @brief Returns the current input buffer content for display.
+    std::string get_input_buffer() const;
+
+    /// @brief Returns whether the minibuffer is currently active.
+    bool is_active() const { return current_mode_ != MinibufferMode::None; }
+
+    /// @brief Returns the current minibuffer mode.
+    MinibufferMode get_current_mode() const { return current_mode_; }
+
+    // History navigation
+    void history_previous();
+    void history_next();
+
+    // Completion related methods (will be expanded in Subtask Z.3)
+    void update_completion_candidates();
+    std::vector<CompletionCandidate> get_completion_candidates() const;
+    std::optional<std::string> get_current_completion() const;
+    void complete(); // For tab completion
+
+private:
+    EditorCore& core_;
+    LuaApi& lua_api_;
+    CompletionSystem& completion_system_;
+
+    MinibufferMode current_mode_ = MinibufferMode::None;
+    std::string prompt_text_;
+    std::string input_buffer_;
+    std::function<void(const std::string&)> on_submit_callback_;
+    std::function<void()> on_cancel_callback_;
+
+    // History
+    std::unordered_map<MinibufferMode, HistoryManager> histories_;
+    HistoryManager* current_history_ = nullptr; // Pointer to the active history manager
+
+    // Completion
+    std::vector<CompletionCandidate> completion_candidates_;
+    size_t completion_index_ = 0;
+
+    /// @brief Helper to add current input to history if it's new.
+    void add_to_history();
+};
+
+} // namespace lumacs

+ 25 - 0
include/lumacs/minibuffer_mode.hpp

@@ -0,0 +1,25 @@
+#pragma once
+
+namespace lumacs {
+
+/// @brief Defines the different modes the minibuffer can be in.
+enum class MinibufferMode {
+    /// @brief No active minibuffer prompt.
+    None,
+    /// @brief Prompting for a command (e.g., M-x).
+    Command,
+    /// @brief Prompting for a file path (e.g., C-x C-f).
+    FilePath,
+    /// @brief Prompting for a buffer name (e.g., C-x b).
+    BufferName,
+    /// @brief Prompting for a theme name.
+    ThemeName,
+    /// @brief Prompting for an incremental search query.
+    ISearch,
+    /// @brief A generic prompt for a string.
+    ystring,
+    /// @brief A generic prompt for a boolean.
+    y_or_n_p,
+};
+
+} // namespace lumacs

+ 13 - 0
include/lumacs/minibuffer_mode_hash.hpp

@@ -0,0 +1,13 @@
+#pragma once
+
+#include "lumacs/minibuffer_mode.hpp" // For MinibufferMode enum
+
+// Custom hash for MinibufferMode to use it as a key in std::unordered_map
+namespace std {
+    template <>
+    struct hash<lumacs::MinibufferMode> {
+        size_t operator()(const lumacs::MinibufferMode& mode) const {
+            return static_cast<size_t>(mode);
+        }
+    };
+} // namespace std

+ 25 - 2
include/lumacs/ui_interface.hpp

@@ -1,12 +1,35 @@
 #pragma once
 
-#include "lumacs/editor_core.hpp" // For EditorEvent and other core types
+#include <functional> // Required for EventCallback
 
 namespace lumacs {
 
-// Forward declaration to avoid circular dependency
+// Forward declarations
 class EditorCore; 
 
+/// @brief Editor state change events triggered by core actions.
+/// Used by the UI layer to react to model updates.
+enum class EditorEvent {
+    BufferModified,       ///< The content of the active buffer has changed.
+    CursorMoved,          ///< The cursor position has changed.
+    ViewportChanged,      ///< The visible area (scroll/size) has changed.
+    WindowLayoutChanged,  ///< The window tree structure (splits) has changed.
+    WindowFocused,        ///< A different window has gained focus.
+    Message,              ///< A transient message should be displayed (e.g., in minibuffer).
+    
+    // Modal Interaction Events
+    CommandMode,          ///< Enter command input mode (M-x).
+    BufferSwitchMode,     ///< Enter buffer switching mode (C-x b).
+    KillBufferMode,       ///< Enter kill buffer mode (C-x k).
+    FindFileMode,         ///< Enter file finding mode (C-x C-f).
+    ThemeSelectionMode,   ///< Enter theme selection mode.
+    ISearchMode,          ///< Enter incremental search mode (C-s).
+    ISearchBackwardMode,  ///< Enter backward incremental search mode (C-r).
+    
+    TransientMessageCleared, ///< A transient message has been cleared.
+    Quit                  ///< The application should exit.
+};
+
 /// @brief Abstract interface for a Lumacs editor UI frontend.
 /// 
 /// All editor UIs (ncurses, GTK, etc.) must implement this interface.

+ 144 - 0
src/completion_system.cpp

@@ -0,0 +1,144 @@
+#include "lumacs/completion_system.hpp"
+#include "lumacs/editor_core.hpp" // For accessing EditorCore for completion data
+#include <algorithm> // For std::remove_if
+#include <filesystem> // For file path completion
+
+namespace lumacs {
+
+// --- Concrete CompletionSource Implementations ---
+
+/// @brief Completion source for editor commands.
+class CommandCompletionSource : public ICompletionSource {
+public:
+    CommandCompletionSource(EditorCore& core) : core_(core) {}
+
+    std::vector<CompletionCandidate> get_candidates(const std::string& input_text) override {
+        std::vector<CompletionCandidate> candidates;
+        // This is a placeholder. Real implementation would query core_.command_system()
+        // For now, hardcode some commands
+        std::vector<std::string> all_commands = {"find-file", "switch-buffer", "kill-buffer", "set-theme", "write-file", "quit", "save-buffer", "undo", "redo"};
+        for (const auto& cmd : all_commands) {
+            if (cmd.rfind(input_text, 0) == 0) { // Check if command starts with input_text
+                candidates.emplace_back(cmd);
+            }
+        }
+        return candidates;
+    }
+
+private:
+    EditorCore& core_;
+};
+
+/// @brief Completion source for buffer names.
+class BufferNameCompletionSource : public ICompletionSource {
+public:
+    BufferNameCompletionSource(EditorCore& core) : core_(core) {}
+
+    std::vector<CompletionCandidate> get_candidates(const std::string& input_text) override {
+        std::vector<CompletionCandidate> candidates;
+        std::vector<std::string> all_buffer_names = core_.get_buffer_names(); // Get actual buffer names
+        for (const auto& name : all_buffer_names) {
+            if (name.rfind(input_text, 0) == 0) {
+                candidates.emplace_back(name);
+            }
+        }
+        return candidates;
+    }
+
+private:
+    EditorCore& core_;
+};
+
+/// @brief Completion source for file paths.
+class FilePathCompletionSource : public ICompletionSource {
+public:
+    FilePathCompletionSource(EditorCore& core) : core_(core) {}
+
+    std::vector<CompletionCandidate> get_candidates(const std::string& input_text) override {
+        std::vector<CompletionCandidate> candidates;
+        std::filesystem::path current_path = input_text;
+        
+        // If input ends with /, list contents of that directory
+        if (input_text.empty() || input_text.back() == '/') {
+            try {
+                for (const auto& entry : std::filesystem::directory_iterator(current_path)) {
+                    std::string path_str = entry.path().string();
+                    if (entry.is_directory()) {
+                        path_str += "/";
+                    }
+                    candidates.emplace_back(path_str);
+                }
+            } catch (const std::filesystem::filesystem_error& e) {
+                // Ignore errors like "No such file or directory"
+            }
+        } else {
+            // Find files/dirs that start with the input_text in the parent directory
+            std::filesystem::path parent_path = current_path.parent_path();
+            std::string stem = current_path.filename().string();
+            
+            try {
+                for (const auto& entry : std::filesystem::directory_iterator(parent_path)) {
+                    std::string entry_name = entry.path().filename().string();
+                    if (entry_name.rfind(stem, 0) == 0) {
+                        std::string full_path = entry.path().string();
+                        if (entry.is_directory()) {
+                            full_path += "/";
+                        }
+                        candidates.emplace_back(full_path);
+                    }
+                }
+            } catch (const std::filesystem::filesystem_error& e) {
+                // Ignore errors
+            }
+        }
+        return candidates;
+    }
+
+private:
+    EditorCore& core_;
+};
+
+/// @brief Completion source for theme names.
+class ThemeNameCompletionSource : public ICompletionSource {
+public:
+    ThemeNameCompletionSource(EditorCore& core) : core_(core) {}
+
+    std::vector<CompletionCandidate> get_candidates(const std::string& input_text) override {
+        std::vector<CompletionCandidate> candidates;
+        std::vector<std::string> all_theme_names = core_.theme_manager().theme_names();
+        for (const auto& name : all_theme_names) {
+            if (name.rfind(input_text, 0) == 0) {
+                candidates.emplace_back(name);
+            }
+        }
+        return candidates;
+    }
+
+private:
+    EditorCore& core_;
+};
+
+
+// --- CompletionSystem Implementation ---
+
+CompletionSystem::CompletionSystem(EditorCore& core) : core_(core) {
+    // Register default completion sources
+    register_source(MinibufferMode::Command, std::make_unique<CommandCompletionSource>(core));
+    register_source(MinibufferMode::BufferName, std::make_unique<BufferNameCompletionSource>(core));
+    register_source(MinibufferMode::FilePath, std::make_unique<FilePathCompletionSource>(core));
+    register_source(MinibufferMode::ThemeName, std::make_unique<ThemeNameCompletionSource>(core));
+}
+
+void CompletionSystem::register_source(MinibufferMode mode, std::unique_ptr<ICompletionSource> source) {
+    sources_[mode] = std::move(source);
+}
+
+std::vector<CompletionCandidate> CompletionSystem::get_candidates_for_mode(MinibufferMode mode, const std::string& input_text) {
+    auto it = sources_.find(mode);
+    if (it != sources_.end()) {
+        return it->second->get_candidates(input_text);
+    }
+    return {}; // No completion source registered for this mode
+}
+
+} // namespace lumacs

+ 5 - 1
src/editor_core.cpp

@@ -1,6 +1,8 @@
 #include "lumacs/editor_core.hpp"
 #include "lumacs/lua_api.hpp" // Include LuaApi header
 #include "lumacs/command_system.hpp"
+#include "lumacs/completion_system.hpp" // Include CompletionSystem header
+#include "lumacs/minibuffer_manager.hpp" // Include MinibufferManager header
 #include <algorithm>
 #include <iostream>
 
@@ -24,7 +26,9 @@ EditorCore::EditorCore() :
     config_(), // 15. Config
     keybinding_manager_(), // 16. KeyBindingManager
     lua_api_(std::make_unique<LuaApi>()), // 17. std::unique_ptr
-    command_system_(std::make_unique<CommandSystem>(*this)) // 18. std::unique_ptr
+    command_system_(std::make_unique<CommandSystem>(*this)), // 18. std::unique_ptr
+    completion_system_(std::make_unique<CompletionSystem>(*this)), // 19. std::unique_ptr, initialized first
+    minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)) // 20. std::unique_ptr
 {
     // LuaApi needs core_ pointer to be valid, so set it after constructor body starts
     lua_api_->set_core(*this);

+ 124 - 306
src/gtk_editor.cpp

@@ -3,6 +3,7 @@
 #include "lumacs/lua_api.hpp"
 #include "lumacs/keybinding.hpp"
 #include "lumacs/command_system.hpp"
+#include "lumacs/minibuffer_manager.hpp" // Include for MinibufferManager and MinibufferMode
 #include <iostream>
 #include <filesystem>
 #include <vector>
@@ -78,27 +79,71 @@ public:
             queue_redraw_all_windows(content_widget_);
         }
 
-        bool mode_changed = false;
-
-        // Handle mode switching events
+        bool minibuffer_activated = false;
         if (event == EditorEvent::CommandMode) {
-            mode_ = Mode::Command;
-            mode_changed = true;
+            core_->minibuffer_manager().activate_minibuffer(
+                MinibufferMode::Command, "M-x ",
+                [this](const std::string& input) {
+                    if (input == "quit" || input == "q") {
+                        app_->quit();
+                    } else {
+                        auto result = core_->command_system().execute(input);
+                        core_->set_message(result.message);
+                    }
+                }, nullptr
+            );
+            minibuffer_activated = true;
         } else if (event == EditorEvent::FindFileMode) {
-            mode_ = Mode::FindFile;
-            mode_changed = true;
+            core_->minibuffer_manager().activate_minibuffer(
+                MinibufferMode::FilePath, "Find file: ",
+                [this](const std::string& input) {
+                    if (core_->load_file(input)) core_->set_message("Loaded");
+                    else core_->set_message("Failed to load");
+                }, nullptr
+            );
+            minibuffer_activated = true;
         } else if (event == EditorEvent::BufferSwitchMode) {
-            mode_ = Mode::BufferSwitch;
-            mode_changed = true;
+            core_->minibuffer_manager().activate_minibuffer(
+                MinibufferMode::BufferName, "Switch to buffer: ",
+                [this](const std::string& input) {
+                    if (core_->switch_buffer_in_window(input)) core_->set_message("Switched");
+                    else core_->set_message("Buffer not found");
+                }, nullptr
+            );
+            minibuffer_activated = true;
         } else if (event == EditorEvent::KillBufferMode) {
-            mode_ = Mode::KillBuffer;
-            mode_changed = true;
+            core_->minibuffer_manager().activate_minibuffer(
+                MinibufferMode::BufferName, "Kill buffer: ",
+                [this](const std::string& input) {
+                    if (core_->close_buffer(input)) core_->set_message("Killed buffer");
+                    else core_->set_message("Buffer not found");
+                }, nullptr
+            );
+            minibuffer_activated = true;
         } else if (event == EditorEvent::ThemeSelectionMode) {
-            mode_ = Mode::ThemeSelection;
-            mode_changed = true;
+            core_->minibuffer_manager().activate_minibuffer(
+                MinibufferMode::ThemeName, "Set theme: ",
+                [this](const std::string& input) {
+                    auto theme_names = core_->theme_manager().theme_names();
+                    auto it = std::find(theme_names.begin(), theme_names.end(), input);
+                    if (it != theme_names.end()) {
+                        core_->set_theme(input);
+                        core_->set_message("Switched to theme: " + input);
+                    } else {
+                        core_->set_message("Theme not found: " + input);
+                    }
+                }, nullptr
+            );
+            minibuffer_activated = true;
         } else if (event == EditorEvent::ISearchMode) {
-            mode_ = Mode::ISearch;
-            mode_changed = true;
+            core_->minibuffer_manager().activate_minibuffer(
+                MinibufferMode::ISearch, "I-search: ",
+                [this](const std::string& input) { /* TODO: Implement actual isearch logic */ core_->set_message("I-search: " + input); }, nullptr
+            );
+            minibuffer_activated = true;
+        } else if (event == EditorEvent::TransientMessageCleared) {
+            // Force redraw to clear the message
+            if (content_widget_) queue_redraw_all_windows(content_widget_);
         } else if (event == EditorEvent::Quit) {
             // Disconnect timer before quitting to prevent segfault
             if (cursor_timer_connection_.connected()) {
@@ -112,10 +157,7 @@ public:
             });
         }
         
-        if (mode_changed) {
-            command_buffer_.clear();
-            message_line_.clear();
-            history_index_ = minibuffer_history_.size(); // Reset history index to end (new input)
+        if (minibuffer_activated) {
             if (content_widget_) queue_redraw_all_windows(content_widget_);
         }
     }
@@ -201,69 +243,6 @@ private:
 
     Glib::RefPtr<Gtk::Application> app_;
     Gtk::Window* window_ = nullptr; // Store window pointer for widget access only (not lifetime management)
-    // Input Mode State
-    enum class Mode {
-        Normal,
-        Command,
-        FindFile,
-        BufferSwitch,
-        KillBuffer,
-        ThemeSelection,
-        ConfirmKill,
-        ISearch
-    };
-    Mode mode_ = Mode::Normal;
-    std::string command_buffer_;
-    std::string message_line_;
-    std::vector<std::string> minibuffer_history_;
-    size_t history_index_ = 0;
-    // Completion state
-    size_t completion_index_ = 0;
-    std::string last_completion_input_;
-
-    // Helper to run Lua completion
-    std::vector<std::string> run_completion(const std::string& mode, const std::string& input) {
-        if (!core_) return {};
-        
-        try {
-            std::vector<CompletionCandidate> candidates;
-            
-            if (mode == "Command") {
-                candidates = core_->command_system().complete_command(input);
-            } else if (mode == "BufferSwitch" || mode == "KillBuffer") {
-                candidates = core_->command_system().complete_buffer_name(input);
-            } else if (mode == "FindFile") {
-                candidates = core_->command_system().complete_file_path(input);
-            } else if (mode == "ThemeSelection") {
-                candidates = core_->command_system().complete_theme_name(input);
-            } else {
-                // Fallback to Lua completion for custom modes
-                if (core_->lua_api()) {
-                    sol::function func = core_->lua_api()->state()["get_completion_candidates"];
-                    if (func.valid()) {
-                        std::vector<std::string> old_candidates = func(mode, input);
-                        std::vector<std::string> result;
-                        for (const auto& candidate : old_candidates) {
-                            result.push_back(candidate);
-                        }
-                        return result;
-                    }
-                }
-            }
-            
-            // Convert CompletionCandidate to string vector
-            std::vector<std::string> result;
-            for (const auto& candidate : candidates) {
-                result.push_back(candidate.text);
-            }
-            return result;
-            
-        } catch (const std::exception& e) {
-            std::cerr << "Completion error: " << e.what() << std::endl;
-        }
-        return {};
-    }
-
     // Member variables
     Gtk::DrawingArea* drawing_area_ = nullptr; // For single-window compatibility
     Gtk::Widget* content_widget_ = nullptr; // Will be either drawing_area_ or a split container
@@ -462,6 +441,7 @@ protected:
         
         try {
             cursor_visible_ = !cursor_visible_;
+            core_->check_and_clear_message(); // Check and clear messages
             drawing_area_->queue_draw();
         } catch (...) {
             return false; // Stop timer on any exception
@@ -962,6 +942,11 @@ protected:
     void render_minibuffer(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height, 
                           const Glib::RefPtr<Pango::Layout>& layout) {
         if (!core_) return;
+
+        // Only render if minibuffer is active or a message is set
+        if (!core_->minibuffer_manager().is_active() && core_->last_message().empty()) {
+            return;
+        }
         
         // Calculate minibuffer position (bottom line with padding)
         double minibuffer_y = height - line_height_ - PADDING_BOTTOM;
@@ -986,61 +971,39 @@ protected:
         
         // Prepare minibuffer text
         std::string minibuffer_text;
-        if (mode_ != Mode::Normal) {
-            // Show appropriate prompt based on mode
-            switch (mode_) {
-                case Mode::Command:
-                    minibuffer_text = "M-x " + command_buffer_;
-                    break;
-                case Mode::FindFile:
-                    minibuffer_text = "Find file: " + command_buffer_;
-                    break;
-                case Mode::BufferSwitch:
-                    minibuffer_text = "Switch to buffer: " + command_buffer_;
-                    break;
-                case Mode::KillBuffer:
-                    minibuffer_text = "Kill buffer: " + command_buffer_;
-                    break;
-                case Mode::ThemeSelection:
-                    minibuffer_text = "Set theme: " + command_buffer_;
-                    break;
-                case Mode::ISearch:
-                    minibuffer_text = "I-search: " + command_buffer_;
-                    break;
-                default:
-                    minibuffer_text = command_buffer_;
-                    break;
-            }
-        } else if (!message_line_.empty()) {
-            // Show message in minibuffer
-            minibuffer_text = message_line_;
+        std::string prompt_part;
+        std::string input_part;
+        
+        if (core_->minibuffer_manager().is_active()) {
+            prompt_part = core_->minibuffer_manager().get_prompt();
+            input_part = core_->minibuffer_manager().get_input_buffer();
+            minibuffer_text = prompt_part + input_part;
+        } else if (!core_->last_message().empty()) {
+            minibuffer_text = core_->last_message();
         }
         
         // Render minibuffer text
         if (!minibuffer_text.empty()) {
             cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
             layout->set_text(minibuffer_text);
+            
+            // Apply face attributes for prompt if active
+            if (core_->minibuffer_manager().is_active()) {
+                Pango::AttrList attr_list;
+                if (auto face = theme->get_face("minibuffer-prompt")) {
+                    apply_face_attributes(attr_list, *face, 0, prompt_part.length());
+                }
+                layout->set_attributes(attr_list);
+            }
+
             cr->move_to(minibuffer_x, minibuffer_y);
             layout->show_in_cairo_context(cr);
         }
         
-        // Render minibuffer cursor if in interactive mode
-        if (mode_ != Mode::Normal) {
+        // Render minibuffer cursor if active and visible
+        if (core_->minibuffer_manager().is_active() && cursor_visible_) {
             // Calculate cursor position in minibuffer
-            std::string prompt_text;
-            switch (mode_) {
-                case Mode::Command: prompt_text = "M-x "; break;
-                case Mode::FindFile: prompt_text = "Find file: "; break;
-                case Mode::BufferSwitch: prompt_text = "Switch to buffer: "; break;
-                case Mode::KillBuffer: prompt_text = "Kill buffer: "; break;
-                case Mode::ThemeSelection: prompt_text = "Set theme: "; break;
-                case Mode::ISearch: prompt_text = "I-search: "; break;
-                default: break;
-            }
-            
-            // Measure prompt + command buffer to position cursor
-            std::string text_to_cursor = prompt_text + command_buffer_;
-            layout->set_text(text_to_cursor);
+            layout->set_text(minibuffer_text); // Measure full text
             Pango::Rectangle ink_rect, logical_rect;
             layout->get_pixel_extents(ink_rect, logical_rect);
             double cursor_x = minibuffer_x + logical_rect.get_width();
@@ -1049,6 +1012,37 @@ protected:
             cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
             cr->rectangle(cursor_x, minibuffer_y, 2.0, line_height_);
             cr->fill();
+
+            // Render completion overlay if applicable
+            auto current_completion = core_->minibuffer_manager().get_current_completion();
+            if (current_completion && input_part != *current_completion) {
+                std::string completion_suffix = current_completion->substr(input_part.length());
+                if (!completion_suffix.empty()) {
+                    auto completion_layout = Pango::Layout::create(cr);
+                    completion_layout->set_font_description(font_desc_);
+                    completion_layout->set_text(minibuffer_text + completion_suffix);
+                    
+                    Pango::AttrList completion_attr_list;
+                    if (auto face = theme->get_face("minibuffer-completion")) { // Assuming a face for completion
+                        apply_face_attributes(completion_attr_list, *face, minibuffer_text.length(), completion_suffix.length());
+                    } else {
+                        // Fallback: dimmed foreground
+                        Color dim_fg = fg;
+                        dim_fg.r = static_cast<unsigned char>(dim_fg.r * 0.7);
+                        dim_fg.g = static_cast<unsigned char>(dim_fg.g * 0.7);
+                        dim_fg.b = static_cast<unsigned char>(dim_fg.b * 0.7);
+                        auto attr = Pango::Attribute::create_attr_foreground(dim_fg.r * 257, dim_fg.g * 257, dim_fg.b * 257);
+                        attr.set_start_index(minibuffer_text.length());
+                        attr.set_end_index(minibuffer_text.length() + completion_suffix.length());
+                        completion_attr_list.insert(attr);
+                    }
+                    completion_layout->set_attributes(completion_attr_list);
+
+                    cr->set_source_rgb(fg.r / 255.0, fg.g / 255.0, fg.b / 255.0);
+                    cr->move_to(minibuffer_x, minibuffer_y);
+                    completion_layout->show_in_cairo_context(cr);
+                }
+            }
         }
     }
 
@@ -1141,186 +1135,10 @@ protected:
         bool is_lumacs_meta = is_alt || is_meta;
 
         // 3. Handle Minibuffer Input Logic (Command/Buffer/File modes)
-        // If in a special mode, we might consume the key directly instead of passing to Lua bindings,
-        // UNLESS it's a control sequence like C-g or Return.
-        if (mode_ != Mode::Normal && mode_ != Mode::ISearch) { 
-            if (key_name == "Escape" || (is_control && key_name == "g")) { // C-g
-                mode_ = Mode::Normal;
-                command_buffer_.clear();
-                message_line_ = "Cancelled";
-                if (content_widget_) queue_redraw_all_windows(content_widget_);
-                return true;
-            }
-            
-            if (key_name == "Tab") {
-                std::string mode_str;
-                switch (mode_) {
-                    case Mode::Command: mode_str = "Command"; break;
-                    case Mode::BufferSwitch: mode_str = "BufferSwitch"; break;
-                    case Mode::KillBuffer: mode_str = "KillBuffer"; break;
-                    case Mode::FindFile: mode_str = "FindFile"; break;
-                    case Mode::ThemeSelection: mode_str = "ThemeSelection"; break;
-                    default: break;
-                }
-
-                if (!mode_str.empty()) {
-                    // Reset cycling if input changed (naive check, ideally tracked elsewhere)
-                    if (command_buffer_ != last_completion_input_) {
-                        completion_index_ = 0;
-                    }
-
-                    std::vector<std::string> matches = run_completion(mode_str, command_buffer_);
-                    
-                    // Fallback for FindFile if Lua returns nothing (temporary until Lua impl is complete)
-                    if (matches.empty() && mode_ == Mode::FindFile) {
-                         // Simple C++ fallback logic for FindFile could go here, or we just rely on Lua.
-                         // For now, if Lua returns empty, we do nothing.
-                    }
-
-                    if (matches.empty()) {
-                        message_line_ = "No match";
-                    } else if (matches.size() == 1) {
-                        command_buffer_ = matches[0];
-                        last_completion_input_ = command_buffer_; // Update last input to match current
-                        message_line_ = "Sole match";
-                    } else {
-                        // Multiple matches
-                        // 1. Find common prefix
-                        std::string common = matches[0];
-                        for (size_t i = 1; i < matches.size(); ++i) {
-                            const std::string& s = matches[i];
-                            size_t j = 0;
-                            while (j < common.size() && j < s.size() && common[j] == s[j]) {
-                                j++;
-                            }
-                            common = common.substr(0, j);
-                        }
-
-                        // 2. Logic: 
-                        // If current input is shorter than prefix, complete to prefix.
-                        // If current input IS the prefix (or longer/different), cycle.
-                        
-                        if (command_buffer_.length() < common.length()) {
-                            command_buffer_ = common;
-                            completion_index_ = 0; // Reset cycling
-                            
-                            // Show first few completions in message
-                            std::string candidates_preview;
-                            for (size_t i = 0; i < std::min(size_t(5), matches.size()); ++i) {
-                                if (i > 0) candidates_preview += ", ";
-                                candidates_preview += matches[i];
-                            }
-                            if (matches.size() > 5) {
-                                candidates_preview += "...";
-                            }
-                            message_line_ = std::to_string(matches.size()) + " matches: " + candidates_preview;
-                        } else {
-                            // Cycle
-                            command_buffer_ = matches[completion_index_ % matches.size()];
-                            completion_index_++;
-                            
-                            // Show current match with context
-                            std::string current_match = matches[(completion_index_ - 1) % matches.size()];
-                            message_line_ = "Match " + std::to_string((completion_index_ - 1) % matches.size() + 1) + "/" + std::to_string(matches.size()) + ": " + current_match;
-                        }
-                        last_completion_input_ = command_buffer_;
-                    }
-                    
-                    if (content_widget_) queue_redraw_all_windows(content_widget_);
-                    return true;
-                }
-            }
-
-            
-            if (key_name == "Return") {
-                // Add to history
-                if (!command_buffer_.empty() && (minibuffer_history_.empty() || minibuffer_history_.back() != command_buffer_)) {
-                    minibuffer_history_.push_back(command_buffer_);
-                }
-                history_index_ = minibuffer_history_.size();
-
-                // Execute command logic
-                if (mode_ == Mode::Command) {
-                    if (command_buffer_ == "quit" || command_buffer_ == "q") {
-                        app_->quit();
-                    } else {
-                        // Use new command system
-                        auto result = core_->command_system().execute(command_buffer_);
-                        if (result.success) {
-                            if (!result.message.empty()) {
-                                message_line_ = result.message;
-                            }
-                        } else {
-                            message_line_ = result.message;
-                        }
-                    }
-                } else if (mode_ == Mode::FindFile) {
-                    if (core_->load_file(command_buffer_)) message_line_ = "Loaded";
-                    else message_line_ = "Failed to load";
-                } else if (mode_ == Mode::BufferSwitch) {
-                    if (core_->switch_buffer_in_window(command_buffer_)) message_line_ = "Switched";
-                    else message_line_ = "Buffer not found";
-                } else if (mode_ == Mode::KillBuffer) {
-                    if (core_->close_buffer(command_buffer_)) message_line_ = "Killed buffer";
-                    else message_line_ = "Buffer not found";
-                } else if (mode_ == Mode::ThemeSelection) {
-                    auto theme_names = core_->theme_manager().theme_names();
-                    auto it = std::find(theme_names.begin(), theme_names.end(), command_buffer_);
-                    if (it != theme_names.end()) {
-                        core_->set_theme(command_buffer_);
-                        message_line_ = "Switched to theme: " + command_buffer_;
-                    } else {
-                        message_line_ = "Theme not found: " + command_buffer_;
-                    }
-                }
-
-                mode_ = Mode::Normal;
-                command_buffer_.clear();
-                if (content_widget_) queue_redraw_all_windows(content_widget_);
-                return true;
-            }
-            
-            // History Navigation
-            if (key_name == "ArrowUp") {
-                if (history_index_ > 0) {
-                    history_index_--;
-                    command_buffer_ = minibuffer_history_[history_index_];
-                    if (content_widget_) queue_redraw_all_windows(content_widget_);
-                }
-                return true;
-            }
-
-            if (key_name == "ArrowDown") {
-                if (history_index_ < minibuffer_history_.size()) {
-                    history_index_++;
-                    if (history_index_ == minibuffer_history_.size()) {
-                         command_buffer_.clear(); 
-                    } else {
-                         command_buffer_ = minibuffer_history_[history_index_];
-                    }
-                    if (content_widget_) queue_redraw_all_windows(content_widget_);
-                }
-                return true;
-            }
-            
-            if (key_name == "Backspace") {
-                if (!command_buffer_.empty()) command_buffer_.pop_back();
-                if (content_widget_) queue_redraw_all_windows(content_widget_);
-                return true;
-            }
-            
-            // Simple character input
-            if (key_name.length() == 1 && !is_control && !is_lumacs_meta) {
-                command_buffer_ += key_name;
-                if (content_widget_) queue_redraw_all_windows(content_widget_);
-                return true;
-            }
-            
-            // If it's a control key (like C-n, C-p in minibuffer), we might want to pass it through 
-            // or handle it (history navigation). For now, pass through if not handled above? 
-            // Or strictly consume? TUI consumed everything. Let's strictly consume printable.
-            // But we want to allow C-q etc? No, minibuffer usually modal.
-            // We'll return true to consume unless we want to allow global keys.
+        if (core_->minibuffer_manager().is_active()) {
+            // Pass the key event to the MinibufferManager
+            core_->minibuffer_manager().handle_key_event(key_name);
+            if (content_widget_) queue_redraw_all_windows(content_widget_);
             return true;
         }
 

+ 156 - 0
src/minibuffer_manager.cpp

@@ -0,0 +1,156 @@
+#include "lumacs/minibuffer_manager.hpp"
+#include "lumacs/editor_core.hpp" // For EditorCore interaction
+#include "lumacs/lua_api.hpp"     // For LuaApi interaction
+#include "lumacs/minibuffer_mode_hash.hpp" // New include for MinibufferMode hash
+#include "lumacs/completion_system.hpp" // Include for CompletionSystem
+#include <functional> // Required for std::function
+
+#include <iostream> // TODO: Replace with proper logging
+
+namespace lumacs {
+
+MinibufferManager::MinibufferManager(EditorCore& core, LuaApi& lua_api, CompletionSystem& completion_system)
+    : core_(core), lua_api_(lua_api), completion_system_(completion_system) {
+    // Initialize history managers for each mode
+    histories_[MinibufferMode::Command] = HistoryManager();
+    histories_[MinibufferMode::FilePath] = HistoryManager();
+    histories_[MinibufferMode::BufferName] = HistoryManager();
+    histories_[MinibufferMode::ThemeName] = HistoryManager();
+    histories_[MinibufferMode::ISearch] = HistoryManager();
+    histories_[MinibufferMode::ystring] = HistoryManager();
+    histories_[MinibufferMode::y_or_n_p] = HistoryManager();
+}
+
+void MinibufferManager::activate_minibuffer(MinibufferMode mode, const std::string& prompt,
+                                             std::function<void(const std::string&)> on_submit,
+                                             std::function<void()> on_cancel) {
+    current_mode_ = mode;
+    prompt_text_ = prompt;
+    input_buffer_ = ""; // Clear previous input
+    on_submit_callback_ = on_submit;
+    on_cancel_callback_ = on_cancel;
+    
+    // Set current history manager based on mode
+    current_history_ = &histories_[mode];
+
+    // TODO: Update completion candidates based on mode
+    update_completion_candidates(); 
+}
+
+void MinibufferManager::deactivate_minibuffer() {
+    current_mode_ = MinibufferMode::None;
+    prompt_text_ = "";
+    input_buffer_ = "";
+    on_submit_callback_ = nullptr;
+    on_cancel_callback_ = nullptr;
+    current_history_ = nullptr; // Clear current history pointer
+    completion_candidates_.clear();
+    completion_index_ = 0;
+}
+
+bool MinibufferManager::handle_key_event(const std::string& key_name) {
+    if (current_mode_ == MinibufferMode::None) {
+        return false; // Minibuffer not active
+    }
+
+    if (key_name == "Return") {
+        add_to_history();
+        if (on_submit_callback_) {
+            on_submit_callback_(input_buffer_);
+        }
+        deactivate_minibuffer();
+        return true;
+    } else if (key_name == "Escape") {
+        if (on_cancel_callback_) {
+            on_cancel_callback_();
+        }
+        deactivate_minibuffer();
+        return true;
+    } else if (key_name == "BackSpace") {
+        if (!input_buffer_.empty()) {
+            input_buffer_.pop_back();
+            update_completion_candidates(); // Update completions on backspace
+        }
+        return true;
+    } else if (key_name == "Tab") {
+        complete();
+        return true;
+    } else if (key_name == "C-p" || key_name == "ArrowUp") { // Previous history
+        history_previous();
+        return true;
+    } else if (key_name == "C-n" || key_name == "ArrowDown") { // Next history
+        history_next();
+        return true;
+    } else if (key_name.length() == 1) { // Regular character input
+        input_buffer_ += key_name;
+        update_completion_candidates(); // Update completions on new input
+        return true;
+    }
+    // TODO: Handle other special keys like Left/Right arrows, Home/End, Delete
+    // For now, if not handled, let the UI decide (though it shouldn't for minibuffer active)
+    return false; 
+}
+
+std::string MinibufferManager::get_prompt() const {
+    // In a real scenario, this might combine prompt_text_ with current completions
+    // and possibly a visual indicator for current completion index.
+    return prompt_text_;
+}
+
+std::string MinibufferManager::get_input_buffer() const {
+    return input_buffer_;
+}
+
+void MinibufferManager::add_to_history() {
+    if (current_history_) {
+        current_history_->add_item(input_buffer_);
+    }
+}
+
+void MinibufferManager::history_previous() {
+    if (current_history_) {
+        input_buffer_ = current_history_->previous();
+        // TODO: Update completion candidates based on history item
+    }
+}
+
+void MinibufferManager::history_next() {
+    if (current_history_) {
+        input_buffer_ = current_history_->next();
+        // TODO: Update completion candidates based on history item
+    }
+}
+
+void MinibufferManager::update_completion_candidates() {
+    completion_candidates_.clear();
+    completion_index_ = 0;
+
+    if (input_buffer_.empty()) {
+        return;
+    }
+
+    completion_candidates_ = completion_system_.get_candidates_for_mode(current_mode_, input_buffer_);
+}
+
+std::vector<CompletionCandidate> MinibufferManager::get_completion_candidates() const {
+    return completion_candidates_;
+}
+
+std::optional<std::string> MinibufferManager::get_current_completion() const {
+    if (!completion_candidates_.empty()) {
+        return completion_candidates_[completion_index_].text;
+    }
+    return std::nullopt;
+}
+
+void MinibufferManager::complete() {
+    if (!completion_candidates_.empty()) {
+        input_buffer_ = completion_candidates_[completion_index_].text;
+        completion_index_ = (completion_index_ + 1) % completion_candidates_.size();
+        // If we cycle, reset the index so next tab starts from the top.
+        // Or, more Emacs-like, cycle through the list and if we get back to original,
+        // stay there until new input. For now, simple cycle.
+    }
+}
+
+} // namespace lumacs

+ 164 - 701
src/tui_editor.cpp

@@ -32,44 +32,11 @@ public:
     void set_core(EditorCore* core) override;
 
 private:
-    enum class Mode {
-        Normal,
-        Command,       // Minibuffer entry
-        FindFile,      // Find file prompt
-        BufferSwitch,  // Buffer switching with completion
-        KillBuffer,    // Kill buffer with completion
-        ConfirmKill,   // Confirm killing modified buffer
-        ISearch        // Incremental search
-    };
-    
     EditorCore* core_ = nullptr; // Raw pointer to EditorCore, not owned
     bool should_quit_ = false;
     std::string message_line_;
     int height_ = 0, width_ = 0;
     
-    // Input state
-    Mode mode_ = Mode::Normal;
-    std::string command_buffer_;
-
-    // ISearch state
-    std::string isearch_query_;
-    bool isearch_forward_ = true;
-    Position isearch_start_pos_;
-    std::optional<Range> isearch_match_;
-    bool isearch_failed_ = false;
-
-    // Completion state
-    std::vector<std::string> completion_candidates_;
-    size_t completion_index_ = 0;
-    std::string completion_prefix_;
-
-    // Minibuffer history
-    std::vector<std::string> command_history_;
-    std::vector<std::string> buffer_switch_history_;
-    std::vector<std::string> kill_buffer_history_;
-    std::vector<std::string> isearch_history_;
-    size_t history_index_ = 0;
-
     // Meta key handling
     bool waiting_for_meta_ = false;
     std::chrono::steady_clock::time_point meta_time_;
@@ -77,23 +44,13 @@ private:
     
     // Private helper method declarations
     std::string resolve_key(int ch);
-    std::vector<std::string>& get_current_history();
-    void add_to_history(const std::string& entry);
-    void previous_history();
-    void next_history();
-    void reset_history_navigation();
-    void update_completion_candidates(const std::string& prefix);
-    void reset_completion();
-    void perform_search(bool find_next);
-    bool handle_input(int ch);
+    bool handle_input(int ch); // This will be updated
     bool process_key(const std::string& key_name);
-    void execute_command(const std::string& cmd);
     int get_attributes_for_face(const std::string& face_name);
     void render();
     void render_layout_node(std::shared_ptr<LayoutNode> node, int x, int y, int width, int height);
     void render_window(std::shared_ptr<Window> window, int x, int y, int width, int height);
     void render_window_modeline(std::shared_ptr<Window> window, int x, int y, int width, bool is_active);
-    void render_status_line();
     void render_message_line();
 };
 
@@ -122,8 +79,9 @@ void TuiEditor::init() {
         core_->active_theme()->initialize_ncurses_colors();
     }
     
-    // Set initial viewport size (leave room for status and message lines)
-    int content_height = height_ - 2; // -1 for status, -1 for message
+    // Set initial viewport size (leave room for minibuffer/message line)
+    int minibuffer_lines = 1; // Always reserve 1 line for minibuffer/message
+    int content_height = height_ - minibuffer_lines;
     bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
     int line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
     int content_width = width_ - line_number_width;
@@ -145,7 +103,8 @@ void TuiEditor::run() {
         if (new_height != height_ || new_width != width_) {
             height_ = new_height;
             width_ = new_width;
-            int content_height = height_ - 2;
+            int minibuffer_lines = 1; // Always reserve 1 line for minibuffer/message
+            int content_height = height_ - minibuffer_lines;
             bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
             int line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
             int content_width = width_ - line_number_width;
@@ -166,45 +125,100 @@ void TuiEditor::run() {
     }
 }
 
-void TuiEditor::handle_editor_event(EditorEvent event) { // existing method
+void TuiEditor::handle_editor_event(EditorEvent event) {
     if (event == EditorEvent::Quit) {
         should_quit_ = true;
     } else if (event == EditorEvent::Message) {
-        message_line_ = core_->last_message();
+        message_line_ = core_->last_message(); // Still update local message_line_ for rendering
     } else if (event == EditorEvent::CommandMode) {
-        mode_ = Mode::Command;
-        command_buffer_.clear();
-        reset_completion();
-        reset_history_navigation();
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::Command, ":",
+            [this](const std::string& input) {
+                if (input == "q" || input == "quit") {
+                    should_quit_ = true;
+                } else {
+                    auto result = core_->command_system().execute(input);
+                    core_->set_message(result.message);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
     } else if (event == EditorEvent::BufferSwitchMode) {
-        mode_ = Mode::BufferSwitch;
-        command_buffer_.clear();
-        reset_completion();
-        reset_history_navigation();
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::BufferName, "Switch to buffer: ",
+            [this](const std::string& input) {
+                if (core_->switch_buffer_in_window(input)) {
+                    core_->set_message("Switched to: " + input);
+                    core_->lua_api()->execute("auto_activate_major_mode()");
+                } else {
+                    core_->set_message("Buffer not found: " + input);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
     } else if (event == EditorEvent::KillBufferMode) {
-        mode_ = Mode::KillBuffer;
-        command_buffer_.clear();
-        reset_completion();
-        reset_history_navigation();
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::BufferName, "Kill buffer: ",
+            [this](const std::string& input) {
+                auto buf = core_->get_buffer_by_name(input);
+                if (buf && buf->is_modified()) {
+                    // Enter a confirmation sub-mode
+                    mode_ = Mode::ConfirmKill; // Temporarily use local mode for confirmation
+                    command_buffer_ = input; // Store buffer name for confirmation
+                    core_->set_message("Buffer modified! Kill anyway? (y/n)");
+                } else {
+                    if (core_->close_buffer(input)) {
+                        core_->set_message("Closed buffer: " + input);
+                    } else {
+                        core_->set_message("Failed to close buffer: " + input);
+                    }
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
     } else if (event == EditorEvent::FindFileMode) {
-        mode_ = Mode::FindFile;
-        command_buffer_.clear();
-        reset_completion();
-        reset_history_navigation();
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::FilePath, "Find file: ",
+            [this](const std::string& input) {
+                if (core_->load_file(input)) {
+                    core_->set_message("Loaded: " + input);
+                    core_->lua_api()->execute("auto_activate_major_mode()");
+                } else {
+                    core_->set_message("Failed to load: " + input);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
+    } else if (event == EditorEvent::ThemeSelectionMode) {
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ThemeName, "Set theme: ",
+            [this](const std::string& input) {
+                auto theme_names = core_->theme_manager().theme_names();
+                auto it = std::find(theme_names.begin(), theme_names.end(), input);
+                if (it != theme_names.end()) {
+                    core_->set_theme(input);
+                    core_->set_message("Switched to theme: " + input);
+                } else {
+                    core_->set_message("Theme not found: " + input);
+                }
+            },
+            [this]() { core_->set_message("Cancelled"); }
+        );
     } else if (event == EditorEvent::ISearchMode) {
-        mode_ = Mode::ISearch;
-        isearch_query_.clear();
-        isearch_forward_ = true;
-        isearch_start_pos_ = core_->cursor();
-        isearch_match_ = std::nullopt;
-        isearch_failed_ = false;
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ISearch, "I-search: ",
+            [this](const std::string& input) { /* TODO: Implement actual isearch logic */ core_->set_message("I-search query: " + input); },
+            [this]() { core_->set_message("Cancelled I-search"); }
+        );
     } else if (event == EditorEvent::ISearchBackwardMode) {
-        mode_ = Mode::ISearch;
-        isearch_query_.clear();
-        isearch_forward_ = false;
-        isearch_start_pos_ = core_->cursor();
-        isearch_match_ = std::nullopt;
-        isearch_failed_ = false;
+        core_->minibuffer_manager().activate_minibuffer(
+            MinibufferMode::ISearch, "I-search backward: ",
+            [this](const std::string& input) { /* TODO: Implement actual isearch logic */ core_->set_message("I-search backward query: " + input); },
+            [this]() { core_->set_message("Cancelled I-search backward"); }
+        );
+    } else if (event == EditorEvent::TransientMessageCleared) {
+        // Redraw to clear the message from the screen
+        render();
     }
 }
 
@@ -219,623 +233,90 @@ std::string TuiEditor::resolve_key(int ch) {
     debug_log << "=== NCURSES INPUT DEBUG ===" << std::endl;
     debug_log << "Raw key code: " << ch << " (0x" << std::hex << ch << std::dec << ")" << std::endl;
     
-    std::string key_name;
-    
-    // Handle special ncurses key codes first
-    if (ch >= KEY_MIN) {
-        switch (ch) {
-            case KEY_UP: key_name = "ArrowUp"; break;
-            case KEY_DOWN: key_name = "ArrowDown"; break;
-            case KEY_LEFT: key_name = "ArrowLeft"; break;
-            case KEY_RIGHT: key_name = "ArrowRight"; break;
-            case KEY_HOME: key_name = "Home"; break;
-            case KEY_END: key_name = "End"; break;
-            case KEY_BACKSPACE: key_name = "Backspace"; break;
-            case KEY_DC: key_name = "Delete"; break;
-            case KEY_ENTER: key_name = "Return"; break;
-            case KEY_F(3): key_name = "F3"; break;
-            case KEY_F(4): key_name = "F4"; break;
-            default:
-                debug_log << "Unknown special key: " << ch << " (ignoring)" << std::endl;
-                // Return empty string to ignore unknown special keys
-                return "";
-        }
-    } else {
-        // Handle normal ASCII characters and control codes
-        switch (ch) {
-            case 127: // DEL
-            case 8:   // BS  
-                key_name = "Backspace"; break;
-            case '\n':
-            case '\r':
-                key_name = "Return"; break;
-            case '\t': 
-                key_name = "Tab"; break;
-            case 27: 
-                key_name = "Escape"; break;
-            default:
-                // Control characters (1-26, excluding special cases)
-                if (ch >= 1 && ch >= 1 && ch <= 26 && ch != 8 && ch != 9 && ch != 10 && ch != 13) {
-                    char letter = 'a' + (ch - 1);
-                    key_name = "C-" + std::string(1, letter);
-                    debug_log << "Control character detected: " << ch << " -> " << key_name << std::endl;
-                }
-                // Printable ASCII characters
-                else if (ch >= 32 && ch <= 126) {
-                    key_name = std::string(1, static_cast<char>(ch));
-                }
-                // Extended characters (might be Meta combinations)
-                else if (ch >= 128 && ch < 256) {
-                    char base_char = ch - 128;
-                    if (base_char >= 32 && base_char <= 126) {
-                        key_name = "M-" + std::string(1, base_char);
-                    }
-                }
-                else {
-                    debug_log << "Unhandled character code: " << ch << std::endl;
-                }
-        }
-    }
-    
-    debug_log << "Resolved key: '" << key_name << "'" << std::endl;
-    debug_log << "============================" << std::endl;
-    
-    return key_name;
-}
-
-// History management
-std::vector<std::string>& TuiEditor::get_current_history() {
-    switch (mode_) {
-        case Mode::Command: return command_history_;
-        case Mode::BufferSwitch: return buffer_switch_history_;
-        case Mode::KillBuffer: return kill_buffer_history_;
-        case Mode::ISearch: return isearch_history_;
-        default: return command_history_;
-    }
-}
-
-void TuiEditor::add_to_history(const std::string& entry) {
-    if (entry.empty()) return;
-    
-    auto& history = get_current_history();
-    
-    // Remove if already exists (move to front)
-    auto it = std::find(history.begin(), history.end(), entry);
-    if (it != history.end()) {
-        history.erase(it);
-    }
-    
-    // Add to front
-    history.insert(history.begin(), entry);
-    
-    // Limit history size
-    const size_t MAX_HISTORY = 100;
-    if (history.size() > MAX_HISTORY) {
-        history.resize(MAX_HISTORY);
-    }
-}
-
-void TuiEditor::previous_history() {
-    auto& history = get_current_history();
-    if (history.empty()) return;
-    
-    if (history_index_ < history.size()) {
-        command_buffer_ = history[history_index_];
-        history_index_++;
-    }
-}
-
-void TuiEditor::next_history() {
-    auto& history = get_current_history();
-    if (history.empty() || history_index_ == 0) return;
-    
-    history_index_--;
-    if (history_index_ == 0) {
-        command_buffer_ = "";
-    } else {
-        command_buffer_ = history[history_index_ - 1];
-    }
-}
-
-void TuiEditor::reset_history_navigation() {
-    history_index_ = 0;
-}
-
-void TuiEditor::update_completion_candidates(const std::string& prefix) {
-    std::vector<std::string> candidates;
-
-    if (mode_ == Mode::Command) {
-        // Get command names from Lua
-        auto& lua = core_->lua_api()->state();
-        sol::function get_names = lua["get_command_names"];
-        if (get_names.valid()) {
-            candidates = get_names.call<std::vector<std::string>>();
-        }
-    } else {
-        // Default to buffer names for BufferSwitch/KillBuffer
-        candidates = core_->get_buffer_names();
-    }
-
-    completion_candidates_.clear();
-
-    if (prefix.empty()) {
-        completion_candidates_ = candidates;
-    } else {
-        for (const auto& name : candidates) {
-            if (name.size() >= prefix.size() &&
-                name.substr(0, prefix.size()) == prefix) {
-                completion_candidates_.push_back(name);
-            }
-        }
-    }
-
-    completion_index_ = 0;
-}
-
-// Helper to reset completion state
-void TuiEditor::reset_completion() {
-    completion_candidates_.clear();
-    completion_index_ = 0;
-    completion_prefix_.clear();
-}
-
-void TuiEditor::perform_search(bool find_next) {
-    if (isearch_query_.empty()) {
-        isearch_match_ = std::nullopt;
-        isearch_failed_ = false;
-        return;
-    }
-
-    Position start_search = core_->cursor();
-    if (find_next) {
-        if (isearch_forward_) {
-            // Forward: move cursor forward 1 char to find next
-            if (start_search.column < core_->buffer().line(start_search.line).size()) {
-                start_search.column++;
-            } else if (start_search.line < core_->buffer().line_count() - 1) {
-                start_search.line++;
-                start_search.column = 0;
-            }
-        } else {
-            // Backward: move cursor backward 1 char
-            if (start_search.column > 0) {
-                start_search.column--;
-            } else if (start_search.line > 0) {
-                start_search.line--;
-                start_search.column = core_->buffer().line(start_search.line).size();
-            }
-        }
-    }
-
-    std::optional<Range> result;
-    if (isearch_forward_) {
-        result = core_->buffer().find(isearch_query_, start_search);
-    } else {
-        result = core_->buffer().find_backward(isearch_query_, start_search);
-    }
-
-    if (result) {
-        isearch_match_ = result;
-        isearch_failed_ = false;
-        core_->set_cursor(result->start);
-        core_->adjust_scroll();
-    } else {
-        isearch_failed_ = true;
-    }
-}
 
 bool TuiEditor::handle_input(int ch) {
-    // Handle confirmation mode
-    if (mode_ == Mode::ConfirmKill) {
+    // Handle confirmation mode - this remains local to TuiEditor for now
+    if (core_->minibuffer_manager().get_current_mode() == MinibufferMode::None && mode_ == Mode::ConfirmKill) {
         if (ch == 'y' || ch == 'Y') {
             if (core_->close_buffer(command_buffer_)) {
-                message_line_ = "Closed modified buffer: " + command_buffer_;
+                core_->set_message("Closed modified buffer: " + command_buffer_);
             } else {
-                message_line_ = "Failed to close buffer";
+                core_->set_message("Failed to close buffer");
             }
             mode_ = Mode::Normal;
             command_buffer_.clear();
-            reset_completion();
         } else if (ch == 'n' || ch == 'N' || ch == 27) { // n or ESC
             mode_ = Mode::Normal;
-            message_line_ = "Cancelled kill buffer";
+            core_->set_message("Cancelled kill buffer");
             command_buffer_.clear();
-            reset_completion();
         }
         return true;
     }
 
-    // Handle ISearch
-    if (mode_ == Mode::ISearch) {
-        // C-g (7) or ESC (27) -> Cancel
-        if (ch == 27 || ch == 7) {
-            core_->set_cursor(isearch_start_pos_);
-            mode_ = Mode::Normal;
-            message_line_ = "Quit";
-            return true;
-        }
-        // RET -> Accept
-        if (ch == '\n' || ch == '\r') {
-            mode_ = Mode::Normal;
-            message_line_ = "Mark saved";
-            core_->buffer().set_mark(isearch_start_pos_);
-            return true;
-        }
-        // C-s (19) -> Next
-        if (ch == 19) {
-            isearch_forward_ = true;
-            perform_search(true);
-            return true;
-        }
-        // C-r (18) -> Prev
-        if (ch == 18) {
-            isearch_forward_ = false;
-            perform_search(true);
-            return true;
-        }
-        // Backspace
-        if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) {
-            if (!isearch_query_.empty()) {
-                isearch_query_.pop_back();
-                perform_search(false);
-            }
-            return true;
-        }
-        // Printable
-        if (ch >= 32 && ch <= 126) {
-            isearch_query_ += static_cast<char>(ch);
-            perform_search(false);
-            return true;
-        }
-        
-        // Other keys -> Exit and process
-        mode_ = Mode::Normal;
-        return handle_input(ch);
-    }
-
-    // Handle minibuffer/command mode
-    if (mode_ == Mode::Command || mode_ == Mode::FindFile ||
-        mode_ == Mode::BufferSwitch || mode_ == Mode::KillBuffer) {
-
-        // ESC - cancel
-        if (ch == 27) {
-            mode_ = Mode::Normal;
-            command_buffer_.clear();
-            reset_completion();
-            message_line_ = "Cancelled";
-            return true;
-        }
-
-        // TAB - completion
-        if (ch == '\t' && (mode_ == Mode::BufferSwitch || mode_ == Mode::KillBuffer || mode_ == Mode::Command)) {
-            if (completion_candidates_.empty()) {
-                // First TAB: save prefix and get candidates
-                completion_prefix_ = command_buffer_;
-                update_completion_candidates(completion_prefix_);
-
-                if (!completion_candidates_.empty()) {
-                    command_buffer_ = completion_candidates_[0];
-                    completion_index_ = 0;
-                } else {
-                    message_line_ = "No matches";
-                }
-            } else {
-                // Cycle through candidates
-                completion_index_ = (completion_index_ + 1) % completion_candidates_.size();
-                command_buffer_ = completion_candidates_[completion_index_];
-            }
-            return true;
-        }
-
-        // Return - execute
-        if (ch == '\n' || ch == '\r') {
-            // Add to history before execution
-            add_to_history(command_buffer_);
-            
-            if (mode_ == Mode::Command) {
-                execute_command(command_buffer_);
-            } else if (mode_ == Mode::FindFile) {
-                if (core_->load_file(command_buffer_)) {
-                    message_line_ = "Loaded: " + command_buffer_;
-                    core_->lua_api()->execute("auto_activate_major_mode()") ;
-                } else {
-                    message_line_ = "Failed to load: " + command_buffer_;
-                }
-            } else if (mode_ == Mode::BufferSwitch) {
-                if (core_->switch_buffer_in_window(command_buffer_)) {
-                    message_line_ = "Switched to: " + command_buffer_;
-                    core_->lua_api()->execute("auto_activate_major_mode()") ;
-                } else {
-                    message_line_ = "Buffer not found: " + command_buffer_;
-                }
-            } else if (mode_ == Mode::KillBuffer) {
-                // Check for modification
-                auto buf = core_->get_buffer_by_name(command_buffer_);
-                if (buf && buf->is_modified()) {
-                    mode_ = Mode::ConfirmKill;
-                    message_line_ = "Buffer modified! Kill anyway? (y/n)";
-                    return true;
-                }
-
-                if (core_->close_buffer(command_buffer_)) {
-                    message_line_ = "Closed buffer: " + command_buffer_;
-                } else {
-                    message_line_ = "Failed to close buffer: " + command_buffer_;
-                }
-            }
-            mode_ = Mode::Normal;
-            command_buffer_.clear();
-            reset_completion();
-            reset_history_navigation();
-            return true;
-        }
-
-        // M-p (Alt+p) - Previous history
-        if (ch == '\x90') { // Meta+p
-            previous_history();
-            reset_completion();
-            return true;
-        }
-        
-        // M-n (Alt+n) - Next history  
-        if (ch == '\x8E') { // Meta+n
-            next_history();
-            reset_completion();
-            return true;
-        }
-
-        // Backspace
-        if (ch == KEY_BACKSPACE || ch == 127 || ch == 8) {
-            if (!command_buffer_.empty()) {
-                command_buffer_.pop_back();
-                reset_completion(); // Reset completion on edit
-                reset_history_navigation(); // Reset history on edit
-            } else {
-                mode_ = Mode::Normal;
-            }
-            return true;
-        }
-
-        // Printable characters
-        if (ch >= 32 && ch <= 126) {
-            command_buffer_ += static_cast<char>(ch);
-            reset_completion(); // Reset completion on new input
-            return true;
-        }
-        return true;
-    }
+    // Resolve key name, including Meta combinations
+    std::string key_name;
 
-    // Check for expired meta key
+    // Check for expired meta key before processing current input
     if (waiting_for_meta_) {
         auto now = std::chrono::steady_clock::now();
         if (now - meta_time_ > META_TIMEOUT) {
             debug_log << "Meta timeout, treating ESC as Escape key" << std::endl;
             waiting_for_meta_ = false;
-            // Process the ESC as a normal Escape key
-            std::string final_key = "Escape";
-            if (core_->lua_api()->execute_key_binding(final_key)) {
-                core_->record_key_sequence(final_key);
-                return true;
+            // Process the ESC as a normal Escape key by pushing it back
+            // This is a bit hacky for ncurses, cleaner would be to simulate a key event
+            // For now, let's just make the original ESC be processed normally in next loop
+            key_name = "Escape"; // Treat previous ESC as an Escape key
+            if (core_->lua_api()->execute_key_binding(key_name)) {
+                core_->record_key_sequence(key_name);
+                return true; // Consume the "timed out ESC"
             }
-            message_line_ = "Key: " + final_key;
-            return false;
+            core_->set_message("Key: " + key_name);
+            return false; // Not consumed by keybinding, so UI should re-evaluate
         }
     }
 
+    if (ch == 27 && !waiting_for_meta_ && !core_->minibuffer_manager().is_active()) { // ESC
+        waiting_for_meta_ = true;
+        meta_time_ = std::chrono::steady_clock::now();
+        debug_log << "ESC received, waiting for meta key..." << std::endl;
+        return true; // Consume ESC, wait for next key
+    }
+    
     // If we're waiting for a meta key and got one, combine them
     if (waiting_for_meta_) {
         waiting_for_meta_ = false;
         std::string base_key = resolve_key(ch);
         if (base_key.empty()) {
             debug_log << "Empty base key after ESC, ignoring" << std::endl;
-            return false;
+            // If the key after ESC is not recognized, treat ESC as a normal Escape key
+            key_name = "Escape";
+        } else {
+            key_name = "M-" + base_key;
         }
-
-        std::string key_name = "M-" + base_key;
         debug_log << "Meta sequence complete: " << key_name << std::endl;
-
-        // Continue processing this meta key below
-        return process_key(key_name);
-    }
-
-    // Check if this is the start of a meta sequence
-    if (ch == 27) { // ESC
-        waiting_for_meta_ = true;
-        meta_time_ = std::chrono::steady_clock::now();
-        debug_log << "ESC received, waiting for meta key..." << std::endl;
-        return true;
+    } else {
+        key_name = resolve_key(ch);
     }
-
-    // Normal mode - resolve key and try bindings
-    std::string key_name = resolve_key(ch);
+    
     if (key_name.empty()) {
         debug_log << "Empty key name, ignoring input" << std::endl;
         return false;
     }
 
-    return process_key(key_name);
-}
-
-bool TuiEditor::process_key(const std::string& key_name) {
-    debug_log << "Processing key: " << key_name << std::endl;
-
-    // Use the new keybinding system
-    KeyResult result = core_->lua_api()->process_key(key_name);
-    
-    switch (result) {
-        case KeyResult::Executed:
-            debug_log << "Key binding executed successfully" << std::endl;
-            message_line_.clear(); // Clear any partial sequence display
-            // Record the key if we're recording a macro (but not if it's F3/F4 themselves)
-            if (key_name != "F3" && key_name != "F4") {
-                core_->record_key_sequence(key_name);
-            }
-            return true;
-            
-        case KeyResult::Failed:
-            debug_log << "Key binding execution failed" << std::endl;
-            message_line_ = "Command failed";
-            return true; // Key was handled, even though it failed
-            
-        case KeyResult::Partial:
-            // Building a multi-key sequence
-            message_line_ = core_->keybinding_manager().current_sequence_display();
-            debug_log << "Partial sequence: " << message_line_ << std::endl;
-            return true;
-            
-        case KeyResult::Timeout:
-            debug_log << "Key sequence timed out" << std::endl;
-            message_line_ = "Sequence timeout";
-            // Fall through to try fallback bindings
-            break;
-            
-        case KeyResult::Unbound:
-            debug_log << "No key binding found, trying C++ fallbacks" << std::endl;
-            // Fall through to C++ fallback bindings
-            break;
-    }
-    
-    // Clear any sequence display since we're not in a partial state
-    message_line_.clear();
-    
-    // C++ fallback bindings - these should eventually be moved to Lua
-    
-    // Quit
-    if (key_name == "C-q") {
-        core_->request_quit();
-        return true;
-    }
-    
-    // Command mode
-    if (key_name == "M-x") {
-        mode_ = Mode::Command;
-        command_buffer_.clear();
-        return true;
-    }
-    
-    // Navigation fallbacks (these should be in Lua)
-    if (key_name == "ArrowUp") { 
-        core_->move_up();
-        return true; 
-    }
-    if (key_name == "ArrowDown") { 
-        core_->move_down();
-        return true; 
-    }
-    if (key_name == "ArrowLeft") { 
-        core_->move_left();
-        return true; 
+    // Handle Minibuffer Input Logic
+    if (core_->minibuffer_manager().is_active()) {
+        // Pass the key event to the MinibufferManager
+        return core_->minibuffer_manager().handle_key_event(key_name);
     }
-    if (key_name == "ArrowRight") { 
-        core_->move_right();
-        return true; 
-    }
-    if (key_name == "Home") { core_->move_to_line_start(); return true; }
-    if (key_name == "End") { core_->move_to_line_end(); return true; }
-    
-    // Editing fallbacks (these should also be in Lua)
-    if (key_name == "Backspace") {
-        auto cursor = core_->cursor();
-        core_->buffer().erase_char(cursor);
-        if (cursor.column > 0) {
-            core_->set_cursor({cursor.line, cursor.column - 1});
-        } else if (cursor.line > 0) {
-            size_t prev_line_len = core_->buffer().line(cursor.line - 1).size();
-            core_->set_cursor({cursor.line - 1, prev_line_len});
-        }
-        return true;
-    }
-    
-    if (key_name == "Delete") {
-        auto cursor = core_->cursor();
-        if (cursor.column < core_->buffer().line(cursor.line).size()) {
-            core_->buffer().erase_char({cursor.line, cursor.column + 1});
-        } else if (cursor.line < core_->buffer().line_count() - 1) {
-            core_->buffer().erase_char({cursor.line + 1, 0});
-        }
-        return true;
-    }
-    
-    if (key_name == "Return") {
-        auto cursor = core_->cursor();
-        core_->buffer().insert_newline(cursor);
-        core_->set_cursor({cursor.line + 1, 0});
-        return true;
-    }
-    
-    if (key_name == "Tab") {
-        auto cursor = core_->cursor();
-        core_->buffer().insert(cursor, "    ");
-        core_->set_cursor({cursor.line, cursor.column + 4});
-        return true;
-    }
-    
-    // Insert printable characters (use original key_name for this)
-    if (key_name.size() == 1 && key_name[0] >= 32 && key_name[0] <= 126) {
-        auto cursor = core_->cursor();
-        core_->buffer().insert_char(cursor, key_name[0]);
-        core_->set_cursor({cursor.line, cursor.column + 1});
-        return true;
-    }
-    
-    return false;
-}
 
-void TuiEditor::execute_command(const std::string& cmd) {
-    if (cmd.empty()) return;
-    
-    std::istringstream iss(cmd);
-    std::string command;
-    iss >> command;
-    
-    if (command == "q" || command == "quit") {
-        core_->request_quit();
-        return;
-    }
-    
-    if (command == "w" || command == "write") {
-        core_->buffer().save();
-        message_line_ = "Saved";
-        return;
-    }
-    
-    if (command == "wq") {
-        core_->buffer().save();
-        core_->request_quit();
-        return;
-    }
-    
-    if (command == "e" || command == "edit") {
-        std::string path;
-        std::getline(iss >> std::ws, path);
-                    if (!path.empty()) {
-                        if (core_->load_file(path)) {
-                            message_line_ = "Loaded: " + path;
-                            core_->lua_api()->execute("auto_activate_major_mode()") ;
-                        } else {                message_line_ = "Failed to load: " + path;
-            }
-        } else {
-            message_line_ = "Usage: :e <filename>";
-        }
-        return;
-    }
-    
-    // Try executing via command registry first
-    auto& lua = core_->lua_api()->state();
-    sol::function exec_cmd = lua["execute_extended_command"];
-    if (exec_cmd.valid()) {
-        bool result = exec_cmd(cmd);
-        if (result) {
-            return;
-        }
-    }
+    // Normal mode processing (pass to keybinding system)
+    return process_key(key_name);
+}
 
-    // Fallback: Try executing as Lua code
-    if (core_->lua_api()->execute(cmd)) {
-        message_line_ = "Lua executed";
-    } else {
-        message_line_ = "Unknown command: " + cmd;
-    }
+int TuiEditor::get_attributes_for_face(const std::string& face_name) {
+    auto theme = core_->active_theme();
+    if (!theme) return 0;
+    return theme->get_face_attributes_ncurses(face_name);
 }
 
 int TuiEditor::get_attributes_for_face(const std::string& face_name) {
@@ -856,8 +337,12 @@ void TuiEditor::render() {
     }
     clear();
     
-    // Calculate content area (leave room for message line only)
-    int content_height = height_ - 1; 
+    // Calculate content area (leave room for message line and potentially a completion line)
+    int minibuffer_lines = 1; // Always reserve 1 line for minibuffer/message
+    // if (core_->minibuffer_manager().is_active() && !core_->minibuffer_manager().get_completion_candidates().empty()) {
+    //     minibuffer_lines++; // Reserve an extra line for completions
+    // }
+    int content_height = height_ - minibuffer_lines; 
     int content_width = width_;
     
     // Render the layout tree recursively (now includes per-window modelines)
@@ -1112,33 +597,6 @@ void TuiEditor::render_window_modeline(std::shared_ptr<Window> window, int x, in
     attroff(attrs);
 }
 
-void TuiEditor::render_status_line() {
-    const auto cursor = core_->cursor();
-    const auto& buffer = core_->buffer();
-
-    int status_y = height_ - 2;
-    int attrs = get_attributes_for_face("mode-line");
-    if (attrs == 0) attrs = A_REVERSE;
-
-    attron(attrs);
-    move(status_y, 0);
-    clrtoeol();
-
-    std::string status = buffer.name();
-    if (buffer.is_modified()) status += " [+] ";
-    status += " | " + std::to_string(cursor.line + 1) + ":" + std::to_string(cursor.column + 1);
-    status += " | " + std::to_string(width_) + "x" + std::to_string(height_);
-    if (mode_ == Mode::Command) status += " [CMD]";
-    else if (mode_ == Mode::FindFile) status += " [FILE]";
-    else if (mode_ == Mode::BufferSwitch) status += " [BUFFER]";
-    else if (mode_ == Mode::KillBuffer) status += " [KILL]";
-    else if (mode_ == Mode::ConfirmKill) status += " [CONFIRM]";
-    else if (mode_ == Mode::ISearch) status += " [I-SEARCH]";
-
-    mvprintw(status_y, 0, "%s", status.c_str());
-    attroff(attrs);
-}
-
 void TuiEditor::render_message_line() {
     int msg_y = height_ - 1;
     int attrs = get_attributes_for_face("minibuffer-prompt");
@@ -1147,32 +605,37 @@ void TuiEditor::render_message_line() {
     move(msg_y, 0);
     clrtoeol();
 
-    if (mode_ == Mode::Command) {
-        mvprintw(msg_y, 0, ":%s", command_buffer_.c_str());
-    } else if (mode_ == Mode::FindFile) {
-        mvprintw(msg_y, 0, "Find file: %s", command_buffer_.c_str());
-    } else if (mode_ == Mode::BufferSwitch) {
-        std::string prompt = "Switch to buffer: " + command_buffer_;
-        if (!completion_candidates_.empty()) {
-            prompt += " [" + std::to_string(completion_index_ + 1) + "/" +
-                      std::to_string(completion_candidates_.size()) + "]";
-        }
-        mvprintw(msg_y, 0, "%s", prompt.c_str());
-    } else if (mode_ == Mode::KillBuffer) {
-        std::string prompt = "Kill buffer: " + command_buffer_;
-        if (!completion_candidates_.empty()) {
-            prompt += " [" + std::to_string(completion_index_ + 1) + "/" +
-                      std::to_string(completion_candidates_.size()) + "]";
+    if (core_->minibuffer_manager().is_active()) {
+        std::string prompt_part = core_->minibuffer_manager().get_prompt();
+        std::string input_part = core_->minibuffer_manager().get_input_buffer();
+        std::string display_text = prompt_part + input_part;
+        
+        mvprintw(msg_y, 0, "%s", display_text.c_str());
+
+        // Display completion candidates below the input line
+        auto candidates = core_->minibuffer_manager().get_completion_candidates();
+        if (!candidates.empty()) {
+            std::string completion_display;
+            for (size_t i = 0; i < candidates.size() && completion_display.length() < width_ - 5; ++i) {
+                if (!completion_display.empty()) completion_display += " ";
+                completion_display += candidates[i].display_text; // Use display_text
+            }
+            if (completion_display.length() >= width_ - 5) {
+                completion_display = completion_display.substr(0, width_ - 8) + "...";
+            }
+            // Move up one line to display completions above the current minibuffer line
+            mvprintw(msg_y - 1, 0, "%s", completion_display.c_str());
         }
-        mvprintw(msg_y, 0, "%s", prompt.c_str());
-    } else if (mode_ == Mode::ISearch) {
-        std::string prompt = (isearch_failed_ ? "Failing " : "") + std::string("I-search: ") + isearch_query_;
-        mvprintw(msg_y, 0, "%s", prompt.c_str());
+
     } else if (!message_line_.empty()) {
+        // Display transient message
         mvprintw(msg_y, 0, "%s", message_line_.c_str());
     }
     
     attroff(attrs);
+
+    // After rendering, check if it's time to clear the message.
+    core_->check_and_clear_message();
 }
 
 namespace lumacs {