Explorar o código

refactor(core): Apply general updates from previous tasks

Bernardo Magri hai 1 mes
pai
achega
199d1a8404
Modificáronse 8 ficheiros con 149 adicións e 728 borrados
  1. 1 0
      CMakeLists.txt
  2. 67 49
      GEMINI.md
  3. 35 118
      include/lumacs/command_system.hpp
  4. 2 4
      include/lumacs/editor_core.hpp
  5. 30 544
      src/command_system.cpp
  6. 2 2
      src/editor_core.cpp
  7. 10 9
      src/keybinding.cpp
  8. 2 2
      src/lua_api.cpp

+ 1 - 0
CMakeLists.txt

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

+ 67 - 49
GEMINI.md

@@ -1,77 +1,95 @@
-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.
+# Lumacs Developer System Instructions
 
-Your goal is to execute the Refactoring Roadmap found in documentation/PLAN.md while maintaining strict memory safety and architectural purity.
+## 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.
 
-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.
+Your goal is to execute the Refactoring Roadmap found in `documentation/PLAN.md` while maintaining strict memory safety and architectural purity.
 
-For every complex task, you must follow the RCI (Recursive Criticism and Improvement) loop:
+## 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.
 
-Step 1: Analysis & Plan
-Context: Identify which files (@filename) are involved.
+For every complex task, you must follow the **RCI (Recursive Criticism and Improvement)** loop:
 
-Draft Plan: Briefly outline how you intend to solve the problem.
+### 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`).
 
-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 2: Critique (The "Thinking" Phase)
-Self-Correction: Critically analyze your own Draft Plan. Look for:
+### Step 3: Execution
+* Only *after* Step 2, generate the C++ implementation.
 
-Memory Safety: Are there dangling pointers? (Use std::shared_ptr/std::weak_ptr).
+## 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.
 
-Lua/C++ Boundary: Did I expose this new feature to lua_api.cpp? If not, it's useless to the user.
+## 4. Coding Standards (Strict C++20)
 
-GTK Threading: Am I updating UI widgets from a non-main thread?
+* **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.
 
-Cycle Detection: Will EditorCore -> Window -> EditorCore cause a dependency cycle?
+### Tech Stack Constraints (Strict Enforcement)
+* **GTK4 Only:** You are strictly forbidden from using GTK3 legacy methods.
+    * **BANNED:** `gtk_widget_show_all` (Use `.show()` or `.set_visible(true)`).
+    * **BANNED:** `gtk_box_pack_start` (Use `.append()` or `.set_child()`).
+* **C++20:** Use `std::format`, `concepts`, and `std::span` where appropriate.
 
-Refinement: Update the plan based on these findings.
+## 5. The `PLAN.md` Protocol
+`documentation/PLAN.md` is the **Single Source of Truth**.
+1.  **Read First:** Before writing code, check the "Current Focus" in `PLAN.md`.
+2.  **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.
 
-Step 3: Execution
-Only after Step 2, generate the C++ implementation.
+## 6. Output Format & Safety
 
-3. Project Architecture
-Core (src/core/): EditorCore, Buffer (Gap Buffer), Window. NO GTK CODE HERE.
+### No Lazy Coding (CRITICAL)
+You are **FORBIDDEN** from outputting placeholders like `// ... rest of code unchanged ...`. 
+* When using `write_file` or `cat`, you **MUST** output the **FULL** executable file content.
+* Failure to do this will result in data loss.
 
-UI (src/ui/): GtkEditor (GUI), TuiEditor (Ncurses). Implements IEditorView.
+### Format
+Always provide shell commands for file creation/updates. Use Conventional Commits.
 
-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).
+```bash
+# Example Output format
+cat << 'EOF' > src/new_file.cpp
+... FULL CONTENT OF FILE ...
+EOF
 
-Lua Bindings:
+git add src/new_file.cpp
+git commit -m "feat(core): implement gap buffer resize logic"
 
-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.
+## 7. Anti-Loop & Failure Protocol (CRITICAL)
+If a tool execution fails (especially replace or edit):
 
-Headers: Use #pragma once.
+Stop and Analyze: Do NOT immediately retry the same command.
 
-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).
+Switch Strategy:
 
-5. The PLAN.md Protocol
-documentation/PLAN.md is the Single Source of Truth.
+If replace failed: It is likely due to a whitespace/formatting mismatch in the old_string.
 
-Read First: Before writing code, check the "Current Focus" in PLAN.md.
+The Fix: Do NOT try to fix the replace arguments. Instead, immediately switch to write_file (or save_file) and overwrite the entire file with the corrected content.
 
-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.
+The "Three-Strike" Rule:
 
-6. Output Format
-Always provide shell commands for file creation/updates. Use Conventional Commits for git messages.
+If you fail to edit a file twice in a row, you must STOP, output the error log, and ask the user for manual intervention.
 
-```
-Bash
+NEVER loop more than twice on the same task.
 
-# Example Output format
-cat << 'EOF' > src/new_file.cpp
-... code ...
-EOF
+## 8. State Verification & Context Hygiene
+Verify: If you are unsure of a file's content (e.g., after a failed edit), you must run read_file on it again before attempting any further edits.
 
-git add src/new_file.cpp
-git commit -m "feat(core): implement gap buffer resize logic"
-```
+Ignore: Do not read files in build/, .git/, or bin/. Focus only on source code.

+ 35 - 118
include/lumacs/command_system.hpp

@@ -1,147 +1,64 @@
 #pragma once
 
 #include <string>
-#include <functional>
-#include <memory>
 #include <vector>
+#include <functional>
 #include <unordered_map>
-#include <filesystem>
-
-#include "lumacs/completion_common.hpp" // For CompletionCandidate
+#include <optional>
+#include <variant>
 
 namespace lumacs {
 
-class EditorCore; // Forward declaration
+// Forward declaration for EditorCore, as CommandSystem will interact with it
+class EditorCore;
 
-/// @brief Result of command execution.
+/// @brief Represents the result of a command execution.
 struct CommandResult {
     bool success;
     std::string message;
-    
-    CommandResult(bool s = true, std::string msg = "") 
-        : success(s), message(std::move(msg)) {}
+    // Potentially add more fields, like return value, error code, etc.
 };
 
-/// @brief Function signature for executable commands.
-/// Takes a vector of string arguments and returns a CommandResult.
+/// @brief Type for command functions.
+/// Commands take a vector of string arguments and return a CommandResult.
 using CommandFunction = std::function<CommandResult(const std::vector<std::string>&)>;
 
-/// @brief Metadata and implementation of a named command.
+/// @brief Represents a single command.
 struct Command {
-    std::string name;             ///< Unique command name (e.g., "find-file").
-    std::string description;      ///< Human-readable help text.
-    CommandFunction function;     ///< The implementation function.
-    std::vector<std::string> aliases; ///< Alternative names.
-    bool interactive;             ///< Whether this command can be called interactively (M-x).
-    
-    Command(std::string n, std::string desc, CommandFunction func, 
-            std::vector<std::string> alias = {}, bool inter = true)
-        : name(std::move(n)), description(std::move(desc)), 
-          function(std::move(func)), aliases(std::move(alias)), interactive(inter) {}
-};
-
-/// @brief Provider function for generating completions.
-using CompletionProvider = std::function<std::vector<CompletionCandidate>(const std::string& input)>;
-
-/// @brief Utility class for filesystem completions.
-class FileSystemCompletionProvider {
-public:
-    /// @brief Complete paths (files and directories) relative to CWD or absolute.
-    std::vector<CompletionCandidate> complete_path(const std::string& input) const;
-    
-    /// @brief Complete directory paths only.
-    std::vector<CompletionCandidate> complete_directory(const std::string& input) const;
-    
-    /// @brief Complete files, optionally filtered by extension.
-    std::vector<CompletionCandidate> complete_file(const std::string& input, const std::vector<std::string>& extensions = {}) const;
-    
-private:
-    bool is_absolute_path(const std::string& path) const;
-    std::string normalize_path(const std::string& path) const;
-    int calculate_path_score(const std::string& candidate, const std::string& input) const;
+    std::string name;
+    CommandFunction function;
+    std::string doc_string; // Documentation for the command
+    bool interactive;       // Whether the command can be called interactively (e.g., via M-x)
+    std::string interactive_spec; // Emacs-like interactive spec for argument gathering
+
+    Command(std::string name, CommandFunction func, std::string doc = "", bool interactive = false, std::string i_spec = "")
+        : name(std::move(name)), function(std::move(func)), doc_string(std::move(doc)), interactive(interactive), interactive_spec(std::move(i_spec)) {}
 };
 
-/// @brief Central registry for commands and completion logic.
-/// 
-/// The CommandSystem manages:
-/// - Registration of named commands.
-/// - Execution of commands by name.
-/// - Parsing of command strings.
-/// - Autocompletion providers for different contexts (M-x, Find File, etc.).
+/// @brief Manages the registration and execution of editor commands.
 class CommandSystem {
 public:
-    explicit CommandSystem(EditorCore& core);
-    ~CommandSystem() = default;
-
-    // === Command Registration ===
-
-    /// @brief Register a command object.
-    void register_command(std::unique_ptr<Command> command);
-
-    /// @brief Helper to register a command.
-    void register_command(const std::string& name, const std::string& description,
-                         CommandFunction function, const std::vector<std::string>& aliases = {},
-                         bool interactive = true);
-    
-    // === Command Execution ===
-
-    /// @brief Execute a named command with arguments.
-    /// @return Result indicating success/failure and message.
-    CommandResult execute(const std::string& command_name, const std::vector<std::string>& args = {});
-
-    /// @brief Parse and execute a raw command string (e.g., "find-file src/main.cpp").
-    CommandResult execute_string(const std::string& command_string);
-    
-    // === Command Lookup ===
-
-    /// @brief Check if a command exists.
-    bool has_command(const std::string& name) const;
-
-    /// @brief Get a command by name.
-    std::shared_ptr<Command> get_command(const std::string& name) const;
-
-    /// @brief Get names of all registered commands.
-    std::vector<std::string> get_all_command_names() const;
-
-    /// @brief Get names of commands marked as interactive.
-    std::vector<std::string> get_interactive_command_names() const;
-    
-    // === Completion System ===
-
-    /// @brief Get completions for command names (M-x).
-    std::vector<CompletionCandidate> complete_command(const std::string& input) const;
-
-    /// @brief Get completions for buffer names (C-x b).
-    std::vector<CompletionCandidate> complete_buffer_name(const std::string& input) const;
+    explicit CommandSystem(EditorCore* core = nullptr);
 
-    /// @brief Get completions for file paths (C-x C-f).
-    std::vector<CompletionCandidate> complete_file_path(const std::string& input) const;
+    /// @brief Registers a command with the system.
+    void register_command(const std::string& name, CommandFunction function,
+                          const std::string& doc_string = "", bool interactive = false);
 
-    /// @brief Get completions for theme names.
-    std::vector<CompletionCandidate> complete_theme_name(const std::string& input) const;
-    
-    /// @brief Register a custom completion provider for a command argument.
-    void register_completion_provider(const std::string& command_name, CompletionProvider provider);
+    /// @brief Executes a command by name with given arguments.
+    /// @param name The name of the command to execute.
+    /// @param args Arguments for the command.
+    /// @return CommandResult indicating success/failure and a message.
+    CommandResult execute(const std::string& name, const std::vector<std::string>& args);
 
-    /// @brief Get completions for a specific command's arguments.
-    std::vector<CompletionCandidate> get_completions(const std::string& command_name, const std::string& input) const;
-    
-    // === Utilities ===
+    /// @brief Get a list of all registered command names.
+    std::vector<std::string> get_command_names() const;
 
-    /// @brief Calculate fuzzy match score between candidate and input.
-    static int fuzzy_match_score(const std::string& candidate, const std::string& input);
+    /// @brief Get documentation for a specific command.
+    std::optional<std::string> get_command_doc_string(const std::string& name) const;
 
-    /// @brief Check if candidate matches input fuzzily.
-    static bool fuzzy_match(const std::string& candidate, const std::string& input);
-    
 private:
-    EditorCore& core_;
-    std::unordered_map<std::string, std::shared_ptr<Command>> commands_;
-    std::unordered_map<std::string, CompletionProvider> completion_providers_;
-    FileSystemCompletionProvider fs_provider_;
-    
-    void register_builtin_commands();
-    std::vector<std::string> parse_command_string(const std::string& command_string) const;
+    EditorCore* core_; // Pointer to EditorCore for commands to interact with editor state
+    std::unordered_map<std::string, Command> commands_;
 };
 
-} // namespace lumacs
+} // namespace lumacs

+ 2 - 4
include/lumacs/editor_core.hpp

@@ -363,12 +363,10 @@ private:
     // Subsystems
     ThemeManager theme_manager_;
     Config config_;
-    KeyBindingManager keybinding_manager_;                                                                                                                        
+    std::unique_ptr<CommandSystem> command_system_; // Must be declared before KeyBindingManager
+    KeyBindingManager keybinding_manager_;
     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);
     

+ 30 - 544
src/command_system.cpp

@@ -1,567 +1,53 @@
 #include "lumacs/command_system.hpp"
-#include "lumacs/editor_core.hpp"
-#include "lumacs/lua_api.hpp"
-#include <algorithm>
-#include <sstream>
-#include <regex>
-#include <iostream>
-#include <filesystem>
-#include <unordered_set>
-#include <iomanip>
+#include "lumacs/editor_core.hpp" // For EditorCore interaction (if needed by commands)
+#include <algorithm> // For std::sort
 
 namespace lumacs {
 
-// FileSystemCompletionProvider Implementation
-
-std::vector<CompletionCandidate> FileSystemCompletionProvider::complete_path(const std::string& input) const {
-    std::vector<CompletionCandidate> candidates;
-    
-    try {
-        std::string path_part = input;
-        std::string dirname = ".";
-        std::string basename = input;
-        
-        // Extract directory and filename parts
-        auto last_slash = input.find_last_of("/\\");
-        if (last_slash != std::string::npos) {
-            dirname = input.substr(0, last_slash);
-            basename = input.substr(last_slash + 1);
-            if (dirname.empty()) dirname = "/";
-        }
-        
-        // Expand ~ to home directory
-        if (dirname.starts_with("~")) {
-            const char* home = getenv("HOME");
-            if (home) {
-                dirname = std::string(home) + dirname.substr(1);
-            }
-        }
-        
-        if (!std::filesystem::exists(dirname)) {
-            return candidates;
-        }
-        
-        // Iterate through directory entries
-        for (const auto& entry : std::filesystem::directory_iterator(dirname)) {
-            std::string filename = entry.path().filename().string();
-            
-            // Skip hidden files unless input starts with .
-            if (filename.starts_with(".") && !basename.starts_with(".")) {
-                continue;
-            }
-            
-            // Check if filename matches input
-            if (basename.empty() || filename.starts_with(basename)) {
-                std::string full_path;
-                if (dirname == ".") {
-                    full_path = filename;
-                } else {
-                    full_path = (dirname == "/" ? "/" : dirname + "/") + filename;
-                }
-                
-                // Add trailing slash for directories
-                if (entry.is_directory()) {
-                    full_path += "/";
-                }
-                
-                int score = calculate_path_score(filename, basename);
-                std::string desc = entry.is_directory() ? "directory" : "file";
-                candidates.emplace_back(full_path, score, desc);
-            }
-        }
-    } catch (const std::filesystem::filesystem_error& e) {
-        // Ignore filesystem errors
-    }
-    
-    std::sort(candidates.begin(), candidates.end());
-    return candidates;
-}
-
-std::vector<CompletionCandidate> FileSystemCompletionProvider::complete_directory(const std::string& input) const {
-    auto candidates = complete_path(input);
-    // Filter to only directories
-    candidates.erase(
-        std::remove_if(candidates.begin(), candidates.end(),
-            [](const CompletionCandidate& c) { return c.description != "directory"; }),
-        candidates.end());
-    return candidates;
-}
-
-std::vector<CompletionCandidate> FileSystemCompletionProvider::complete_file(const std::string& input, const std::vector<std::string>& extensions) const {
-    auto candidates = complete_path(input);
-    
-    if (!extensions.empty()) {
-        candidates.erase(
-            std::remove_if(candidates.begin(), candidates.end(),
-                [&extensions](const CompletionCandidate& c) {
-                    if (c.description == "directory") return false; // Keep directories
-                    for (const auto& ext : extensions) {
-                        if (c.text.ends_with(ext)) return false;
-                    }
-                    return true;
-                }),
-            candidates.end());
-    }
-    
-    return candidates;
+CommandSystem::CommandSystem(EditorCore* core) : core_(core) {
+    // Register core commands here, or ensure they are registered via Lua
 }
 
-bool FileSystemCompletionProvider::is_absolute_path(const std::string& path) const {
-    return !path.empty() && (path[0] == '/' || path[0] == '~');
-}
-
-std::string FileSystemCompletionProvider::normalize_path(const std::string& path) const {
-    std::string normalized = path;
-    if (normalized.starts_with("~")) {
-        const char* home = getenv("HOME");
-        if (home) {
-            normalized = std::string(home) + normalized.substr(1);
-        }
+void CommandSystem::register_command(const std::string& name, CommandFunction function,
+                                     const std::string& doc_string, bool interactive, std::string interactive_spec) {
+    // Check if command already exists
+    if (commands_.count(name)) {
+        // Log a warning or throw an error if overriding is not allowed
+        // For now, new registration simply overwrites old one
     }
-    return normalized;
+    commands_.emplace(name, Command(name, std::move(function), doc_string, interactive, std::move(interactive_spec)));
 }
 
-int FileSystemCompletionProvider::calculate_path_score(const std::string& candidate, const std::string& input) const {
-    if (input.empty()) return 50;
-    if (candidate == input) return 100;
-    if (candidate.starts_with(input)) return 90;
-    
-    // Fuzzy matching score
-    return CommandSystem::fuzzy_match_score(candidate, input);
-}
-
-// CommandSystem Implementation
-
-CommandSystem::CommandSystem(EditorCore& core) : core_(core) {
-    register_builtin_commands();
-}
-
-void CommandSystem::register_command(std::unique_ptr<Command> command) {
-    if (!command) return;
-    
-    std::string name = command->name;
-    auto shared_cmd = std::shared_ptr<Command>(command.release());
-    commands_[name] = shared_cmd;
-    
-    // Register aliases
-    for (const auto& alias : shared_cmd->aliases) {
-        commands_[alias] = shared_cmd;
-    }
-}
-
-void CommandSystem::register_command(const std::string& name, const std::string& description,
-                                   CommandFunction function, const std::vector<std::string>& aliases,
-                                   bool interactive) {
-    auto command = std::make_unique<Command>(name, description, std::move(function), aliases, interactive);
-    register_command(std::move(command));
-}
-
-CommandResult CommandSystem::execute(const std::string& command_name, const std::vector<std::string>& args) {
-    auto it = commands_.find(command_name);
-    if (it == commands_.end()) {
-        return CommandResult(false, "Command not found: " + command_name);
-    }
-    
-    try {
-        return it->second->function(args);
-    } catch (const std::exception& e) {
-        return CommandResult(false, "Command error: " + std::string(e.what()));
-    }
-}
-
-CommandResult CommandSystem::execute_string(const std::string& command_string) {
-    auto parts = parse_command_string(command_string);
-    if (parts.empty()) {
-        return CommandResult(false, "Empty command");
-    }
-    
-    std::string command_name = parts[0];
-    std::vector<std::string> args(parts.begin() + 1, parts.end());
-    
-    return execute(command_name, args);
-}
-
-bool CommandSystem::has_command(const std::string& name) const {
-    return commands_.find(name) != commands_.end();
-}
-
-std::shared_ptr<Command> CommandSystem::get_command(const std::string& name) const {
+CommandResult CommandSystem::execute(const std::string& name, const std::vector<std::string>& args) {
     auto it = commands_.find(name);
-    return (it != commands_.end()) ? it->second : nullptr;
-}
-
-std::vector<std::string> CommandSystem::get_all_command_names() const {
-    std::vector<std::string> names;
-    std::unordered_set<std::string> unique_names;
-    
-    for (const auto& [name, command] : commands_) {
-        if (unique_names.insert(command->name).second) {
-            names.push_back(command->name);
+    if (it != commands_.end()) {
+        try {
+            return it->second.function(args);
+        } catch (const std::exception& e) {
+            return {false, "Command '" + name + "' failed: " + e.what()};
+        } catch (...) {
+            return {false, "Command '" + name + "' failed with unknown error."};
         }
     }
-    
-    std::sort(names.begin(), names.end());
-    return names;
+    return {false, "Unknown command: " + name};
 }
 
-std::vector<std::string> CommandSystem::get_interactive_command_names() const {
+std::vector<std::string> CommandSystem::get_command_names() const {
     std::vector<std::string> names;
-    std::unordered_set<std::string> unique_names;
-    
-    for (const auto& [name, command] : commands_) {
-        if (command->interactive && unique_names.insert(command->name).second) {
-            names.push_back(command->name);
-        }
+    names.reserve(commands_.size());
+    for (const auto& pair : commands_) {
+        names.push_back(pair.first);
     }
-    
     std::sort(names.begin(), names.end());
     return names;
 }
 
-std::vector<CompletionCandidate> CommandSystem::complete_command(const std::string& input) const {
-    std::vector<CompletionCandidate> candidates;
-    std::unordered_set<std::string> unique_names;
-    
-    for (const auto& [name, command] : commands_) {
-        if (!command->interactive || !unique_names.insert(command->name).second) {
-            continue;
-        }
-        
-        int score = fuzzy_match_score(command->name, input);
-        if (score > 0) {
-            candidates.emplace_back(command->name, score, command->description);
-        }
-    }
-    
-    std::sort(candidates.begin(), candidates.end());
-    return candidates;
-}
-
-std::vector<CompletionCandidate> CommandSystem::complete_buffer_name(const std::string& input) const {
-    std::vector<CompletionCandidate> candidates;
-    auto buffer_names = core_.get_buffer_names();
-    
-    for (const auto& name : buffer_names) {
-        int score = fuzzy_match_score(name, input);
-        if (score > 0) {
-            auto buffer = core_.get_buffer_by_name(name);
-            std::string desc = "buffer";
-            if (buffer && buffer->is_modified()) {
-                desc += " (modified)";
-            }
-            candidates.emplace_back(name, score, desc);
-        }
-    }
-    
-    std::sort(candidates.begin(), candidates.end());
-    return candidates;
-}
-
-std::vector<CompletionCandidate> CommandSystem::complete_file_path(const std::string& input) const {
-    return fs_provider_.complete_path(input);
-}
-
-std::vector<CompletionCandidate> CommandSystem::complete_theme_name(const std::string& input) const {
-    std::vector<CompletionCandidate> candidates;
-    auto theme_names = core_.theme_manager().theme_names();
-    auto current_theme = core_.active_theme();
-    std::string current_name = current_theme ? current_theme->name() : "";
-    
-    for (const auto& name : theme_names) {
-        int score = fuzzy_match_score(name, input);
-        if (score > 0) {
-            std::string desc = "theme";
-            if (name == current_name) {
-                desc += " (current)";
-            }
-            candidates.emplace_back(name, score, desc);
-        }
-    }
-    
-    std::sort(candidates.begin(), candidates.end());
-    return candidates;
-}
-
-void CommandSystem::register_completion_provider(const std::string& command_name, CompletionProvider provider) {
-    completion_providers_[command_name] = std::move(provider);
-}
-
-std::vector<CompletionCandidate> CommandSystem::get_completions(const std::string& command_name, const std::string& input) const {
-    auto it = completion_providers_.find(command_name);
-    if (it != completion_providers_.end()) {
-        return it->second(input);
-    }
-    return {};
-}
-
-int CommandSystem::fuzzy_match_score(const std::string& candidate, const std::string& input) {
-    if (input.empty()) return 50;
-    if (candidate == input) return 100;
-    if (candidate.starts_with(input)) return 90;
-    
-    // Convert to lowercase for case-insensitive matching
-    std::string lower_candidate = candidate;
-    std::string lower_input = input;
-    std::transform(lower_candidate.begin(), lower_candidate.end(), lower_candidate.begin(), ::tolower);
-    std::transform(lower_input.begin(), lower_input.end(), lower_input.begin(), ::tolower);
-    
-    if (lower_candidate == lower_input) return 95;
-    if (lower_candidate.starts_with(lower_input)) return 85;
-    
-    // Fuzzy matching - check if all input characters appear in order
-    size_t candidate_idx = 0;
-    size_t matched_chars = 0;
-    
-    for (char c : lower_input) {
-        while (candidate_idx < lower_candidate.size() && lower_candidate[candidate_idx] != c) {
-            candidate_idx++;
-        }
-        if (candidate_idx < lower_candidate.size()) {
-            matched_chars++;
-            candidate_idx++;
-        }
-    }
-    
-    if (matched_chars == input.size()) {
-        // All characters matched, score based on ratio and position
-        int base_score = 40;
-        base_score += (matched_chars * 20) / input.size();
-        base_score += std::max(0, 20 - (int)candidate.size() + (int)input.size());
-        return std::min(80, base_score);
-    }
-    
-    return 0; // No match
-}
-
-bool CommandSystem::fuzzy_match(const std::string& candidate, const std::string& input) {
-    return fuzzy_match_score(candidate, input) > 0;
-}
-
-void CommandSystem::register_builtin_commands() {
-    // File operations
-    register_command("save-buffer", "Save current buffer to file",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            if (core_.buffer().save()) {
-                return CommandResult(true, "Buffer saved: " + core_.buffer().name());
-            } else {
-                return CommandResult(false, "Failed to save buffer");
-            }
-        });
-        
-    register_command("find-file", "Open a file",
-        [this](const std::vector<std::string>& args) -> CommandResult {
-            if (args.empty()) {
-                core_.enter_find_file_mode();
-                return CommandResult(true, "Find file mode activated");
-            } else {
-                if (core_.load_file(args[0])) {
-                    return CommandResult(true, "Loaded: " + args[0]);
-                } else {
-                    return CommandResult(false, "Failed to load: " + args[0]);
-                }
-            }
-        });
-    
-    // Buffer operations
-    register_command("switch-to-buffer", "Switch to buffer",
-        [this](const std::vector<std::string>& args) -> CommandResult {
-            if (args.empty()) {
-                core_.enter_buffer_switch_mode();
-                return CommandResult(true, "Buffer switch mode activated");
-            } else {
-                if (core_.switch_buffer_in_window(args[0])) {
-                    return CommandResult(true, "Switched to: " + args[0]);
-                } else {
-                    return CommandResult(false, "Buffer not found: " + args[0]);
-                }
-            }
-        }, {"switch-buffer"});
-    
-    register_command("kill-buffer", "Close buffer",
-        [this](const std::vector<std::string>& args) -> CommandResult {
-            if (args.empty()) {
-                core_.enter_kill_buffer_mode();
-                return CommandResult(true, "Kill buffer mode activated");
-            } else {
-                if (core_.close_buffer(args[0])) {
-                    return CommandResult(true, "Killed buffer: " + args[0]);
-                } else {
-                    return CommandResult(false, "Cannot kill buffer: " + args[0]);
-                }
-            }
-        });
-    
-    register_command("list-buffers", "List all open buffers",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            auto buffer_info = core_.get_all_buffer_info();
-            if (buffer_info.empty()) {
-                return CommandResult(true, "No buffers open");
-            }
-            
-            std::ostringstream ss;
-            ss << "Open buffers (" << buffer_info.size() << "):\n";
-            for (const auto& info : buffer_info) {
-                ss << "  " << (info.modified ? "*" : " ") << info.name 
-                   << " (" << info.size << " chars)";
-                if (info.filepath) {
-                    ss << " - " << info.filepath->string();
-                }
-                ss << "\n";
-            }
-            
-            return CommandResult(true, ss.str());
-        });
-    
-    // Navigation
-    register_command("goto-line", "Go to line number",
-        [this](const std::vector<std::string>& args) -> CommandResult {
-            if (args.empty()) {
-                return CommandResult(false, "Line number required");
-            }
-            try {
-                size_t line = std::stoull(args[0]);
-                core_.goto_line(line - 1); // Convert to 0-based
-                return CommandResult(true, "Moved to line " + args[0]);
-            } catch (...) {
-                return CommandResult(false, "Invalid line number: " + args[0]);
-            }
-        });
-    
-    // Window management
-    register_command("split-window-below", "Split window horizontally",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            core_.split_horizontally();
-            return CommandResult(true, "Window split horizontally");
-        });
-    
-    register_command("split-window-right", "Split window vertically", 
-        [this](const std::vector<std::string>&) -> CommandResult {
-            core_.split_vertically();
-            return CommandResult(true, "Window split vertically");
-        });
-    
-    register_command("delete-window", "Close current window",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            core_.close_active_window();
-            return CommandResult(true, "Window closed");
-        });
-    
-    register_command("other-window", "Switch to next window",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            core_.next_window_safe();
-            return CommandResult(true, "Switched to other window");
-        });
-    
-    // Editing
-    register_command("undo", "Undo last change",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            if (core_.undo()) {
-                return CommandResult(true, "Undid last change");
-            } else {
-                return CommandResult(false, "Nothing to undo");
-            }
-        });
-    
-    register_command("redo", "Redo last undo",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            if (core_.redo()) {
-                return CommandResult(true, "Redid last change");
-            } else {
-                return CommandResult(false, "Nothing to redo");
-            }
-        });
-    
-    // Theme management
-    register_command("list-themes", "List all available themes",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            auto theme_names = core_.theme_manager().theme_names();
-            if (theme_names.empty()) {
-                return CommandResult(true, "No themes available");
-            }
-            
-            std::ostringstream ss;
-            ss << "Available themes (" << theme_names.size() << "):\n";
-            auto current_theme = core_.active_theme();
-            std::string current_name = current_theme ? current_theme->name() : "none";
-            
-            for (const auto& name : theme_names) {
-                ss << "  " << (name == current_name ? "* " : "  ") << name << "\n";
-            }
-            
-            return CommandResult(true, ss.str());
-        });
-    
-    register_command("set-theme", "Switch to a theme",
-        [this](const std::vector<std::string>& args) -> CommandResult {
-            if (args.empty()) {
-                core_.enter_theme_selection_mode();
-                return CommandResult(true, "Theme selection mode activated");
-            } else {
-                const std::string& theme_name = args[0];
-                auto theme_names = core_.theme_manager().theme_names();
-                auto it = std::find(theme_names.begin(), theme_names.end(), theme_name);
-                
-                if (it == theme_names.end()) {
-                    return CommandResult(false, "Theme not found: " + theme_name);
-                }
-                
-                core_.set_theme(theme_name);
-                return CommandResult(true, "Switched to theme: " + theme_name);
-            }
-        }, {"switch-theme", "theme"});
-    
-    register_command("reload-themes", "Reload all themes",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            // Clear and recreate default themes
-            core_.theme_manager().create_default_themes();
-            
-            // Reload themes.lua if available
-            if (core_.lua_api()) {
-                std::filesystem::path themes_file = std::filesystem::current_path() / "themes.lua";
-                if (std::filesystem::exists(themes_file)) {
-                    core_.lua_api()->load_file(themes_file);
-                }
-            }
-            
-            return CommandResult(true, "Themes reloaded");
-        });
-    
-    // System
-    register_command("quit", "Quit editor",
-        [this](const std::vector<std::string>&) -> CommandResult {
-            core_.request_quit();
-            return CommandResult(true, "Quitting...");
-        }, {"q", "exit"});
-    
-    register_command("eval", "Evaluate Lua code",
-        [this](const std::vector<std::string>& args) -> CommandResult {
-            if (args.empty()) {
-                return CommandResult(false, "Lua code required");
-            }
-            
-            std::string code = args[0];
-            for (size_t i = 1; i < args.size(); ++i) {
-                code += " " + args[i];
-            }
-            
-            if (core_.lua_api() && core_.lua_api()->execute(code)) {
-                return CommandResult(true, "Lua code executed");
-            } else {
-                return CommandResult(false, "Lua code execution failed");
-            }
-        });
-}
-
-std::vector<std::string> CommandSystem::parse_command_string(const std::string& command_string) const {
-    std::vector<std::string> parts;
-    std::istringstream iss(command_string);
-    std::string part;
-    
-    while (iss >> std::quoted(part) || iss >> part) {
-        parts.push_back(part);
+std::optional<std::string> CommandSystem::get_command_doc_string(const std::string& name) const {
+    auto it = commands_.find(name);
+    if (it != commands_.end()) {
+        return it->second.doc_string;
     }
-    
-    return parts;
+    return std::nullopt;
 }
 
-} // namespace lumacs
+} // namespace lumacs

+ 2 - 2
src/editor_core.cpp

@@ -24,9 +24,9 @@ EditorCore::EditorCore() :
     rectangle_kill_ring_(), // 13. std::vector
     theme_manager_(), // 14. ThemeManager
     config_(), // 15. Config
-    keybinding_manager_(), // 16. KeyBindingManager
+    keybinding_manager_(command_system_.get()), // 16. KeyBindingManager, now initialized with command_system_
     lua_api_(std::make_unique<LuaApi>()), // 17. std::unique_ptr
-    command_system_(std::make_unique<CommandSystem>(*this)), // 18. std::unique_ptr
+    command_system_(std::make_unique<CommandSystem>(this)), // 18. std::unique_ptr, initialized first
     completion_system_(std::make_unique<CompletionSystem>(*this)), // 19. std::unique_ptr, initialized first
     minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)) // 20. std::unique_ptr
 {

+ 10 - 9
src/keybinding.cpp

@@ -318,7 +318,7 @@ void KeyBindingManager::unbind(const std::string& key_str) {
     unbind(KeySequence(key_str));
 }
 
-KeyResult KeyBindingManager::process_key(const Key& key) {
+KeyProcessingResult KeyBindingManager::process_key(const Key& key) {
     // Check for timeout first
     if (is_building_sequence() && has_sequence_timed_out()) {
         clear_partial_sequence();
@@ -340,27 +340,28 @@ KeyResult KeyBindingManager::process_key(const Key& key) {
         try {
             // Execute the command via the CommandSystem
             if (command_system_) {
-                CommandResult result = command_system_->execute(exact_binding->command_name, {}); // No args for now
-                // Optionally handle result.success or result.message
-                return result.success ? KeyResult::Executed : KeyResult::Failed;
+                CommandResult cmd_result = command_system_->execute(exact_binding->command_name, {}); // No args for now
+                return {cmd_result.success ? KeyResult::Executed : KeyResult::Failed, cmd_result};
             }
-            return KeyResult::Failed; // No command system
+            return {KeyResult::Failed, CommandResult{false, "No CommandSystem available"}};
+        } catch (const std::exception& e) {
+            return {KeyResult::Failed, CommandResult{false, std::string("Command execution failed: ") + e.what()}};
         } catch (...) {
-            return KeyResult::Failed;
+            return {KeyResult::Failed, CommandResult{false, "Command execution failed with unknown error"}};
         }
     }
     
     // Check if this could be a prefix for other bindings
     if (has_prefix_bindings_impl(current_sequence_)) {
-        return KeyResult::Partial;
+        return {KeyResult::Partial, std::nullopt};
     }
     
     // No binding found, clear sequence and return unbound
     clear_partial_sequence();
-    return KeyResult::Unbound;
+    return {KeyResult::Unbound, std::nullopt};
 }
 
-KeyResult KeyBindingManager::process_key(const std::string& key_str) {
+KeyProcessingResult KeyBindingManager::process_key(const std::string& key_str) {
     return process_key(Key::parse(key_str));
 }
 

+ 2 - 2
src/lua_api.cpp

@@ -509,7 +509,7 @@ void LuaApi::register_functions() {
         return std::make_tuple(result.success, result.message);
     };
 
-    lua_["register_command"] = [this](std::string name, std::string description, sol::function func, sol::optional<sol::table> aliases_table, sol::optional<bool> interactive) {
+    lua_["register_command"] = [this](std::string name, std::string description, sol::function func, sol::optional<sol::table> aliases_table, sol::optional<bool> interactive, sol::optional<std::string> interactive_spec) {
         std::vector<std::string> aliases;
         if (aliases_table) {
             for (auto& pair : aliases_table.value()) {
@@ -553,7 +553,7 @@ void LuaApi::register_functions() {
             }
         };
         
-        core_->command_system().register_command(name, description, command_func, aliases, interactive.value_or(true));
+        core_->command_system().register_command(name, command_func, description, interactive.value_or(true), interactive_spec.value_or(""));
     };
 
     lua_["get_command_names"] = [this]() {