瀏覽代碼

feat: Implement M-x command registry with tab completion and built-in commands

Bernardo Magri 1 月之前
父節點
當前提交
af2c641473
共有 2 個文件被更改,包括 164 次插入7 次删除
  1. 135 1
      init.lua
  2. 29 6
      src/main_ncurses.cpp

+ 135 - 1
init.lua

@@ -1097,4 +1097,138 @@ dofile("themes.lua")
 message("Lumacs ready! C-k=kill, C-y=yank, C-@=mark, C-w=cut, M-w=copy, M-f/b=word, C-v/M-v=page")
 
 -- Auto-activate mode for initial buffer
-auto_activate_major_mode()
+auto_activate_major_mode()
+
+-- ============================================================================
+-- COMMAND REGISTRY (M-x support)
+-- ============================================================================
+
+lumacs.command_registry = {}
+
+function define_command(name, func, doc)
+    lumacs.command_registry[name] = {
+        func = func,
+        doc = doc or "No documentation"
+    }
+end
+
+function execute_extended_command(name)
+    local cmd = lumacs.command_registry[name]
+    if cmd then
+        cmd.func()
+        return true
+    end
+    return false
+end
+
+function get_command_names()
+    local names = {}
+    for name, _ in pairs(lumacs.command_registry) do
+        table.insert(names, name)
+    end
+    table.sort(names)
+    return names
+end
+
+-- File and Buffer Commands
+define_command("save-buffer", function() 
+    if editor.buffer:save() then 
+        message("Saved " .. editor.buffer:name()) 
+    else 
+        message("Save failed") 
+    end 
+end, "Save current buffer")
+
+define_command("find-file", function() 
+    editor:find_file_mode() 
+end, "Find file")
+
+define_command("kill-buffer", function() 
+    editor:kill_buffer_mode() 
+end, "Kill buffer")
+
+define_command("switch-buffer", function() 
+    editor:buffer_switch_mode() 
+end, "Switch buffer")
+
+define_command("list-buffers", function() 
+    -- Use existing implementation via keybinding or duplicate logic?
+    -- Calling the function bound to C-x C-b if we can find it, or just define it here.
+    -- Since C-x C-b binding is anonymous function, I'll copy logic or better, 
+    -- extract list-buffers logic in init.lua (future refactor).
+    -- For now, I'll just invoke the binding C-x C-b if possible? No.
+    -- I'll just leave it for now or copy-paste the logic.
+    -- Copying logic is safer for now.
+    
+    local buffer_info = editor:get_all_buffer_info()
+    if #buffer_info == 0 then
+        message("No buffers open")
+        return
+    end
+
+    local lines = {}
+    table.insert(lines, "Buffer List:")
+    table.insert(lines, "------------")
+    table.insert(lines, "")
+    table.insert(lines, string.format("%-3s %-20s %-10s %s", "Mod", "Name", "Size", "File"))
+    table.insert(lines, string.format("%-3s %-20s %-10s %s", "---", "----", "----", "----"))
+
+    for i, info in ipairs(buffer_info) do
+        local modified = info.modified and " * " or "   "
+        local filepath = ""
+        if info.filepath then filepath = tostring(info.filepath) end
+        table.insert(lines, string.format("%s %-20s %-10d %s", modified, info.name, info.size, filepath))
+    end
+
+    local list_text = table.concat(lines, "\n")
+    local list_buf_name = "*Buffer List*"
+    local list_buf = editor:get_buffer_by_name(list_buf_name)
+    if list_buf then
+        editor:switch_buffer_in_window(list_buf_name)
+    else
+        editor:new_buffer(list_buf_name)
+    end
+    editor.buffer:clear()
+    editor.buffer:insert(lumacs.Position(0,0), list_text)
+    editor:goto_beginning()
+end, "List all buffers")
+
+-- Navigation
+define_command("next-line", function() editor:move_down() end, "Move cursor down")
+define_command("previous-line", function() editor:move_up() end, "Move cursor up")
+define_command("forward-char", function() editor:move_right() end, "Move cursor right")
+define_command("backward-char", function() editor:move_left() end, "Move cursor left")
+define_command("forward-word", function() editor:move_forward_word() end, "Move forward one word")
+define_command("backward-word", function() editor:move_backward_word() end, "Move backward one word")
+define_command("beginning-of-buffer", function() editor:goto_beginning() end, "Go to beginning of buffer")
+define_command("end-of-buffer", function() editor:goto_end() end, "Go to end of buffer")
+define_command("scroll-up-command", function() editor:page_down() end, "Page down")
+define_command("scroll-down-command", function() editor:page_up() end, "Page up")
+
+-- Window Management
+define_command("split-window-below", function() editor:split_horizontally() end, "Split window horizontally")
+define_command("split-window-right", function() editor:split_vertically() end, "Split window vertically")
+define_command("delete-window", function() editor:close_window() end, "Close current window")
+define_command("other-window", function() editor:next_window() end, "Select other window")
+define_command("delete-other-windows", function() 
+    -- Simplified: keep closing others until only 1?
+    -- Or just implement properly in C++ later.
+    -- For now, assume users use C-x 1 if implemented?
+    -- C-x 1 isn't implemented in init.lua yet.
+    message("delete-other-windows not implemented yet")
+end, "Delete all other windows")
+
+-- Editing
+define_command("kill-line", function() editor:kill_line() end, "Kill rest of line")
+define_command("kill-region", function() editor:kill_region() end, "Kill selected region")
+define_command("copy-region-as-kill", function() editor:copy_region_as_kill() end, "Copy region")
+define_command("yank", function() editor:yank() end, "Paste from kill ring")
+define_command("undo", function() if editor:undo() then message("Undid") else message("No undo") end end, "Undo last change")
+define_command("redo", function() if editor:redo() then message("Redid") else message("No redo") end end, "Redo last undo")
+
+-- Modes
+define_command("lua-mode", function() activate_major_mode("lua-mode") end, "Switch to Lua mode")
+define_command("fundamental-mode", function() activate_major_mode("fundamental-mode") end, "Switch to Fundamental mode")
+define_command("auto-save-mode", function() toggle_minor_mode("auto-save-mode") end, "Toggle auto-save")
+
+message("Commands loaded. Try M-x list-buffers")

+ 29 - 6
src/main_ncurses.cpp

@@ -351,13 +351,26 @@ private:
     }
 
     void update_completion_candidates(const std::string& prefix) {
-        auto all_buffers = core_->get_buffer_names();
+        std::vector<std::string> candidates;
+
+        if (mode_ == Mode::Command) {
+            // Get command names from Lua
+            auto& lua = 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_ = all_buffers;
+            completion_candidates_ = candidates;
         } else {
-            for (const auto& name : all_buffers) {
+            for (const auto& name : candidates) {
                 if (name.size() >= prefix.size() &&
                     name.substr(0, prefix.size()) == prefix) {
                     completion_candidates_.push_back(name);
@@ -502,8 +515,8 @@ private:
                 return true;
             }
 
-            // TAB - completion (only for BufferSwitch and KillBuffer)
-            if (ch == '\t' && (mode_ == Mode::BufferSwitch || mode_ == Mode::KillBuffer)) {
+            // 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_;
@@ -818,7 +831,17 @@ private:
             return;
         }
         
-        // Try executing as Lua
+        // Try executing via command registry first
+        auto& lua = lua_api_->state();
+        sol::function exec_cmd = lua["execute_extended_command"];
+        if (exec_cmd.valid()) {
+            bool result = exec_cmd(cmd);
+            if (result) {
+                return;
+            }
+        }
+
+        // Fallback: Try executing as Lua code
         if (lua_api_->execute(cmd)) {
             message_line_ = "Lua executed";
         } else {