Quellcode durchsuchen

refactor(keybinding): Implement Enhanced Error Reporting (B.5)

Bernardo Magri vor 1 Monat
Ursprung
Commit
c67f232f00
3 geänderte Dateien mit 89 neuen und 141 gelöschten Zeilen
  1. 34 6
      include/lumacs/keybinding.hpp
  2. 27 115
      src/gtk_window_controller.cpp
  3. 28 20
      src/tui_editor.cpp

+ 34 - 6
include/lumacs/keybinding.hpp

@@ -109,7 +109,7 @@ struct KeyBinding {
     KeyBinding(const std::string& key_str, std::string cmd_name, std::string desc = "");
 };
 
-/// @brief Result of processing a key in the binding manager.
+/// @brief Represents the result of processing a key in the binding manager.
 enum class KeyResult {
     Unbound,        ///< No binding found, key not handled.
     Executed,       ///< Binding found and executed successfully.
@@ -118,6 +118,15 @@ enum class KeyResult {
     Timeout         ///< Partial sequence timed out.
 };
 
+// Forward declaration for CommandResult
+struct CommandResult;
+
+/// @brief Detailed result of a key processing operation.
+struct KeyProcessingResult {
+    KeyResult type;
+    std::optional<CommandResult> command_result; // Relevant if type is Executed or Failed
+};
+
 /// @brief Central manager for all key bindings.
 /// 
 /// Handles:
@@ -139,8 +148,8 @@ public:
     
     /// @brief Process a single key press.
     /// @return Result indicating if key was bound, partial, etc.
-    KeyResult process_key(const Key& key);
-    KeyResult process_key(const std::string& key_str);
+    KeyProcessingResult process_key(const Key& key);
+    KeyProcessingResult process_key(const std::string& key_str);
     
     /// @brief Check if a sequence has any bindings (exact or partial).
     bool has_binding(const KeySequence& sequence) const;
@@ -173,13 +182,32 @@ public:
     bool has_sequence_timed_out() const;
 
 private:
-    std::map<KeySequence, KeyBinding> bindings_;
+    // Trie node structure
+    struct TrieNode {
+        std::optional<KeyBinding> binding; // Stores binding if this node represents the end of a sequence
+        std::map<Key, std::unique_ptr<TrieNode>> children;
+        int ref_count = 0; // Number of bindings passing through this node
+
+        TrieNode() = default;
+
+        // Prevent copying, allow moving
+        TrieNode(const TrieNode&) = delete;
+        TrieNode& operator=(const TrieNode&) = delete;
+        TrieNode(TrieNode&&) noexcept = default;
+        TrieNode& operator=(TrieNode&&) noexcept = default;
+    };
+    
+    std::unique_ptr<TrieNode> root_; // Root of the trie
     KeySequence current_sequence_;
     std::chrono::steady_clock::time_point sequence_start_time_;
     std::chrono::milliseconds sequence_timeout_;
+    CommandSystem* command_system_ = nullptr; // Dependency on CommandSystem
+    
+    // Helper function to find a node in the trie
+    TrieNode* find_node(const KeySequence& sequence) const;
     
-    std::optional<KeyBinding> find_exact_binding(const KeySequence& sequence) const;
-    bool has_prefix_bindings_impl(const KeySequence& sequence) const;
+    // Helper to traverse trie and collect all bindings
+    void collect_bindings(TrieNode* node, KeySequence current_prefix, std::vector<KeyBinding>& result) const;
 };
 
 } // namespace lumacs

+ 27 - 115
src/gtk_window_controller.cpp

@@ -50,13 +50,16 @@ void GtkWindowController::set_cursor_visible(bool visible) {
 }
 
 
-std::string GtkWindowController::resolve_key(guint keyval, Gdk::ModifierType state) {
-    // Handle modifier keys
-    unsigned int state_uint = static_cast<unsigned int>(state);
-    bool is_control = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) != 0;
-    
-    // Convert keyval to string
+bool GtkWindowController::on_key_pressed(guint keyval, guint /*keycode*/, Gdk::ModifierType state) {
+    if (!window_) return false;
+
+    // Make cursor visible immediately when typing
+    cursor_visible_ = true;
+    drawing_area_.queue_draw(); // Redraw immediately to show cursor
+
+    // 1. Convert GDK keyval and state to our canonical Key representation
     std::string key_name;
+    // Special handling for some keys
     switch (keyval) {
         case GDK_KEY_Return: key_name = "Return"; break;
         case GDK_KEY_Tab: key_name = "Tab"; break;
@@ -77,11 +80,6 @@ std::string GtkWindowController::resolve_key(guint keyval, Gdk::ModifierType sta
             // Handle printable characters
             if (keyval >= GDK_KEY_a && keyval <= GDK_KEY_z) {
                 key_name = std::string(1, static_cast<char>(keyval));
-                if (is_control) { 
-                    // Logic for Control keys if needed
-                } else if ((state_uint & static_cast<unsigned int>(Gdk::ModifierType::SHIFT_MASK)) != 0) { 
-                    key_name = std::string(1, static_cast<char>(keyval - (GDK_KEY_a - GDK_KEY_A)));
-                }
             } else if (keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9) {
                 key_name = std::string(1, static_cast<char>(keyval));
             } else if (keyval >= 32 && keyval <= 126) { 
@@ -89,28 +87,23 @@ std::string GtkWindowController::resolve_key(guint keyval, Gdk::ModifierType sta
             }
             break;
     }
-    return key_name;
-}
 
-bool GtkWindowController::on_key_pressed(guint keyval, guint /*keycode*/, Gdk::ModifierType state) {
-    if (!window_) return false;
+    if (key_name.empty()) return false; // Unhandled key
 
-    // Make cursor visible immediately when typing
-    cursor_visible_ = true;
-    drawing_area_.queue_draw(); // Redraw immediately to show cursor
-
-    // 1. Resolve the base key name
-    std::string key_name = resolve_key(keyval, state);
-    if (key_name.empty()) return false;
-    
-    // 2. Handle Modifiers
+    // Apply modifiers
     unsigned int state_uint = static_cast<unsigned int>(state);
-    bool is_control = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) != 0;
-    bool is_alt = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::ALT_MASK)) != 0;
-    bool is_meta = (state_uint & static_cast<unsigned int>(Gdk::ModifierType::META_MASK)) != 0;
-    bool is_lumacs_meta = is_alt || is_meta;
+    if (state_uint & static_cast<unsigned int>(Gdk::ModifierType::CONTROL_MASK)) {
+        key_name = "C-" + key_name;
+    }
+    if (state_uint & static_cast<unsigned int>(Gdk::ModifierType::ALT_MASK) ||
+        state_uint & static_cast<unsigned int>(Gdk::ModifierType::META_MASK)) {
+        key_name = "M-" + key_name;
+    }
+    if (state_uint & static_cast<unsigned int>(Gdk::ModifierType::SHIFT_MASK)) {
+        key_name = "S-" + key_name;
+    }
 
-    // 3. Handle Minibuffer Input Logic (Command/Buffer/File modes)
+    // 2. Handle Minibuffer Input Logic (Command/Buffer/File modes)
     if (core_.minibuffer_manager().is_active()) {
         // Pass the key event to the MinibufferManager
         core_.minibuffer_manager().handle_key_event(key_name);
@@ -118,96 +111,15 @@ bool GtkWindowController::on_key_pressed(guint keyval, guint /*keycode*/, Gdk::M
         return true;
     }
 
-    // 4. Normal Mode Processing (Pass to Lua)
-    if (is_control && key_name.length() == 1) key_name = "C-" + key_name;
-    if (is_lumacs_meta) key_name = "M-" + key_name; // Use combined meta/alt
-
-    KeyResult result = core_.lua_api()->process_key(key_name);
+    // 3. Normal Mode Processing (Pass to KeyBindingManager)
+    KeyProcessingResult result = core_.keybinding_manager().process_key(key_name);
 
-    // Fallback handlers for common editing keys
-    if (result == KeyResult::Unbound) {
-        // Return - insert newline
-        if (key_name == "Return") {
-            auto cursor = core_.cursor();
-            window_->buffer().insert_newline(cursor);
-            window_->set_cursor({cursor.line + 1, 0});
-        }
-        // Backspace - delete character
-        else if (key_name == "Backspace") {
-            auto cursor = core_.cursor();
-            window_->buffer().erase_char(cursor);
-            if (cursor.column > 0) {
-                window_->set_cursor({cursor.line, cursor.column - 1});
-            } else if (cursor.line > 0) {
-                // Join with previous line
-                const auto& prev_line = window_->buffer().line(cursor.line - 1);
-                size_t prev_line_len = prev_line.length();
-                window_->set_cursor({cursor.line - 1, prev_line_len});
-            }
-        }
-        // Delete - delete character forward
-        else if (key_name == "Delete") {
-            auto cursor = core_.cursor();
-            window_->buffer().erase_char({cursor.line, cursor.column + 1});
-            // No cursor movement needed for forward delete
-        }
-        // Arrow key navigation - use Window methods for proper scrolling
-        else if (key_name == "ArrowUp") {
-            window_->move_up();
-        }
-        else if (key_name == "ArrowDown") {
-            window_->move_down();
-        }
-        else if (key_name == "ArrowLeft") {
-            window_->move_left();
-        }
-        else if (key_name == "ArrowRight") {
-            window_->move_right();
-        }
-        // Page navigation - scroll multiple lines
-        else if (key_name == "PageUp") {
-            auto cursor = window_->cursor();
-            int page_size = std::max(1, window_->viewport().height - 2); // Leave 2 lines overlap
-            
-            // Move cursor up by page size
-            size_t new_line = (cursor.line >= static_cast<size_t>(page_size)) 
-                ? cursor.line - page_size 
-                : 0;
-            
-            window_->set_cursor({new_line, cursor.column});
-        }
-        else if (key_name == "PageDown") {
-            auto cursor = window_->cursor();
-            int page_size = std::max(1, window_->viewport().height - 2); // Leave 2 lines overlap
-            
-            // Move cursor down by page size
-            size_t max_line = window_->buffer().line_count() - 1;
-            size_t new_line = std::min(cursor.line + page_size, max_line);
-            
-            window_->set_cursor({new_line, cursor.column});
-        }
-        // Home/End navigation
-        else if (key_name == "Home") {
-            window_->move_to_line_start();
-        }
-        else if (key_name == "End") {
-            window_->move_to_line_end();
-        }
-        // Insert printable characters if unbound
-        else if (key_name.size() == 1 && key_name[0] >= 32 && key_name[0] <= 126) {
-            auto cursor = window_->cursor();
-            window_->buffer().insert_char(cursor, key_name[0]);
-            window_->set_cursor({cursor.line, cursor.column + 1});
-            
-            // Debug cursor position
-            auto new_cursor = window_->cursor();
-            std::cerr << "[DEBUG] Inserted '" << key_name[0] << "' at (" << cursor.line << "," << cursor.column 
-                     << ") -> cursor now at (" << new_cursor.line << "," << new_cursor.column << ")" << std::endl;
-        }
+    if (result.command_result.has_value()) {
+        core_.set_message(result.command_result->message);
     }
 
     drawing_area_.queue_draw();
-    return true;
+    return result.type != KeyResult::Unbound; // Return true if handled, false otherwise
 }
 
 

+ 28 - 20
src/tui_editor.cpp

@@ -235,7 +235,12 @@ bool TuiEditor::handle_input(int ch) {
     }
 
     // Normal mode processing (pass to keybinding system)
-    return core_->keybinding_manager().process_key(Key::parse(key_name)) != KeyResult::Unbound;
+    KeyProcessingResult result = core_->keybinding_manager().process_key(Key::parse(key_name));
+
+    if (result.command_result.has_value()) {
+        core_->set_message(result.command_result->message);
+    }
+    return result.type != KeyResult::Unbound;
 }
 
 // process_key is removed as keybinding_manager handles it
@@ -408,26 +413,29 @@ void TuiEditor::render_window(std::shared_ptr<Window> window, int x, int y, int
             }
         }
 
-        // Highlight ISearch match - now handled by MinibufferManager
-        // if (mode_ == Mode::ISearch && isearch_match_ && isearch_match_->start.line == buffer_line_idx) {
-        //     size_t match_start = isearch_match_->start.column;
-        //     size_t match_len = isearch_match_->end.column - match_start;
-        //     if (match_start < line_text.size()) {
-        //         size_t display_len = std::min(match_len, line_text.size() - match_start);
-        //         std::string matched_text = line_text.substr(match_start, display_len);
-                
-        //         int match_x = x + line_number_width + match_start;
-        //         // Simple clipping check
-        //         if (match_x < x + width) {
-        //             int attrs = get_attributes_for_face(isearch_failed_ ? "isearch-fail" : "isearch");
-        //             if (attrs == 0) attrs = A_REVERSE; // Fallback
+        // Highlight ISearch match
+        if (core_->minibuffer_manager().is_isearch_active() && core_->minibuffer_manager().get_isearch_match_range().has_value()) {
+            auto isearch_match = core_->minibuffer_manager().get_isearch_match_range().value();
+            if (isearch_match.start.line == buffer_line_idx) {
+                size_t match_start = isearch_match.start.column;
+                size_t match_len = isearch_match.end.column - match_start;
+                if (match_start < line_text.size()) {
+                    size_t display_len = std::min(match_len, line_text.size() - match_start);
+                    std::string matched_text = line_text.substr(match_start, display_len);
                     
-        //             attron(attrs);
-        //             mvprintw(y + screen_y, match_x, "%s", matched_text.c_str());
-        //             attroff(attrs);
-        //         }
-        //     }
-        // }
+                    int match_x = x + line_number_width + match_start;
+                    // Simple clipping check
+                    if (match_x < x + width) {
+                        int attrs = get_attributes_for_face(core_->minibuffer_manager().is_isearch_failed() ? "isearch-fail" : "isearch");
+                        if (attrs == 0) attrs = A_REVERSE; // Fallback
+                        
+                        attron(attrs);
+                        mvprintw(y + screen_y, match_x, "%s", matched_text.c_str());
+                        attroff(attrs);
+                    }
+                }
+            }
+        }
         
         // Show cursor if this is the cursor line and this is the active window
         if (buffer_line_idx == cursor.line && is_active) {