#include "lumacs/keybinding.hpp" #include #include #include namespace lumacs { // ============================================================================ // Key Implementation // ============================================================================ Key::Key(const std::string& key_name) { *this = parse(key_name); } Key Key::parse(const std::string& key_str) { Key key; if (key_str.empty()) { return key; } std::string remaining = key_str; // Parse modifiers while (true) { if (remaining.length() >= 2 && remaining.substr(0, 2) == "C-") { key.ctrl = true; remaining = remaining.substr(2); } else if (remaining.length() >= 2 && (remaining.substr(0, 2) == "M-" || remaining.substr(0, 2) == "A-")) { key.meta = true; remaining = remaining.substr(2); } else if (remaining.length() >= 2 && remaining.substr(0, 2) == "S-") { key.shift = true; remaining = remaining.substr(2); } else { break; } } // The rest is the key name key.name = remaining; return key; } std::string Key::to_string() const { std::string result; if (ctrl) result += "C-"; if (meta) result += "M-"; if (shift) result += "S-"; result += name; return result; } bool Key::operator==(const Key& other) const { return name == other.name && ctrl == other.ctrl && meta == other.meta && shift == other.shift; } bool Key::operator<(const Key& other) const { if (name != other.name) return name < other.name; if (ctrl != other.ctrl) return ctrl < other.ctrl; if (meta != other.meta) return meta < other.meta; return shift < other.shift; } // ============================================================================ // KeySequence Implementation // ============================================================================ KeySequence::KeySequence(const std::vector& keys) : keys_(keys) { } KeySequence::KeySequence(const std::string& key_sequence_str) { if (key_sequence_str.empty()) { return; } // Split by spaces, but handle quoted strings std::istringstream iss(key_sequence_str); std::string token; while (iss >> token) { keys_.emplace_back(Key::parse(token)); } } void KeySequence::add_key(const Key& key) { keys_.push_back(key); } void KeySequence::add_key(const std::string& key_str) { keys_.emplace_back(Key::parse(key_str)); } bool KeySequence::is_prefix_of(const KeySequence& other) const { if (keys_.size() > other.keys_.size()) { return false; } return std::equal(keys_.begin(), keys_.end(), other.keys_.begin()); } bool KeySequence::has_prefix(const KeySequence& prefix) const { return prefix.is_prefix_of(*this); } std::string KeySequence::to_string() const { if (keys_.empty()) { return ""; } std::string result = keys_[0].to_string(); for (size_t i = 1; i < keys_.size(); ++i) { result += " " + keys_[i].to_string(); } return result; } void KeySequence::clear() { keys_.clear(); } KeySequence KeySequence::subsequence(size_t start_index) const { if (start_index >= keys_.size()) { return KeySequence(); } std::vector sub_keys(keys_.begin() + start_index, keys_.end()); return KeySequence(sub_keys); } bool KeySequence::operator==(const KeySequence& other) const { return keys_ == other.keys_; } bool KeySequence::operator<(const KeySequence& other) const { return keys_ < other.keys_; } // ============================================================================ // KeyBinding Implementation // ============================================================================ KeyBinding::KeyBinding(const KeySequence& seq, KeyBindingFunction func, std::string desc) : sequence(seq), function(std::move(func)), description(std::move(desc)) { } KeyBinding::KeyBinding(const std::string& key_str, KeyBindingFunction func, std::string desc) : sequence(key_str), function(std::move(func)), description(std::move(desc)) { } // ============================================================================ // KeyBindingManager Implementation // ============================================================================ KeyBindingManager::KeyBindingManager() : sequence_timeout_(std::chrono::milliseconds(1000)) { } void KeyBindingManager::bind(const KeySequence& sequence, KeyBindingFunction function, const std::string& description) { if (sequence.empty() || !function) { return; } bindings_[sequence] = KeyBinding(sequence, std::move(function), description); } void KeyBindingManager::bind(const std::string& key_str, KeyBindingFunction function, const std::string& description) { bind(KeySequence(key_str), std::move(function), description); } void KeyBindingManager::unbind(const KeySequence& sequence) { bindings_.erase(sequence); } void KeyBindingManager::unbind(const std::string& key_str) { unbind(KeySequence(key_str)); } KeyResult KeyBindingManager::process_key(const Key& key) { // Check for timeout first if (is_building_sequence() && has_sequence_timed_out()) { clear_partial_sequence(); // Fall through to process this key as a new sequence } // Add this key to the current sequence current_sequence_.add_key(key); // Update timestamp sequence_start_time_ = std::chrono::steady_clock::now(); // Check for exact binding auto exact_binding = find_exact_binding(current_sequence_); if (exact_binding.has_value()) { // Found exact match clear_partial_sequence(); try { bool success = exact_binding->function(); return success ? KeyResult::Executed : KeyResult::Failed; } catch (...) { return KeyResult::Failed; } } // Check if this could be a prefix for other bindings if (has_prefix_bindings_impl(current_sequence_)) { return KeyResult::Partial; } // No binding found, clear sequence and return unbound clear_partial_sequence(); return KeyResult::Unbound; } KeyResult KeyBindingManager::process_key(const std::string& key_str) { return process_key(Key::parse(key_str)); } bool KeyBindingManager::has_binding(const KeySequence& sequence) const { return has_exact_binding(sequence) || has_prefix_bindings(sequence); } bool KeyBindingManager::has_exact_binding(const KeySequence& sequence) const { return bindings_.find(sequence) != bindings_.end(); } bool KeyBindingManager::has_prefix_bindings(const KeySequence& sequence) const { return has_prefix_bindings_impl(sequence); } void KeyBindingManager::clear_partial_sequence() { current_sequence_.clear(); sequence_start_time_ = std::chrono::steady_clock::time_point{}; } bool KeyBindingManager::is_building_sequence() const { return !current_sequence_.empty(); } std::string KeyBindingManager::current_sequence_display() const { if (current_sequence_.empty()) { return ""; } return current_sequence_.to_string() + "-"; } std::vector KeyBindingManager::get_all_bindings() const { std::vector result; result.reserve(bindings_.size()); for (const auto& [sequence, binding] : bindings_) { result.push_back(binding); } return result; } void KeyBindingManager::set_sequence_timeout(std::chrono::milliseconds timeout) { sequence_timeout_ = timeout; } bool KeyBindingManager::has_sequence_timed_out() const { if (!is_building_sequence()) { return false; } auto now = std::chrono::steady_clock::now(); return (now - sequence_start_time_) > sequence_timeout_; } std::optional KeyBindingManager::find_exact_binding(const KeySequence& sequence) const { auto it = bindings_.find(sequence); if (it != bindings_.end()) { return it->second; } return std::nullopt; } bool KeyBindingManager::has_prefix_bindings_impl(const KeySequence& sequence) const { // Check if any registered binding has this sequence as a prefix for (const auto& [binding_sequence, binding] : bindings_) { if (sequence.is_prefix_of(binding_sequence) && sequence != binding_sequence) { return true; } } return false; } } // namespace lumacs