Forráskód Böngészése

feat(message-system): add message history navigation (Phase 5)

Users can now cycle through recent messages using M-p (previous) and M-n
(next) without switching to the *Messages* buffer. Features:

- Message history stores last 50 messages (no duplicates)
- Position indicator shows current position (e.g., "[3/10, M-n/M-p]")
- History browsing mode prevents auto-clear of messages
- Messages exit history mode automatically when new message arrives

New commands: previous-message, next-message
New keybindings: M-p, M-n

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Bernardo Magri 1 hónapja
szülő
commit
a8a332d9a8
4 módosított fájl, 131 hozzáadás és 2 törlés
  1. 13 1
      include/lumacs/editor_core.hpp
  2. 12 0
      src/defaults.hpp
  3. 90 0
      src/editor_core.cpp
  4. 16 1
      src/lua_api.cpp

+ 13 - 1
include/lumacs/editor_core.hpp

@@ -124,7 +124,14 @@ public:
 
     const std::string& last_message() const { return last_message_; }
     MessageSeverity last_message_severity() const { return last_message_severity_; }
-    void check_and_clear_message(); 
+    void check_and_clear_message();
+
+    // Message history navigation (M-p/M-n)
+    void previous_message();  // Show previous message from history
+    void next_message();      // Show next message in history
+    void exit_message_history();  // Exit history browsing mode
+    [[nodiscard]] bool is_browsing_message_history() const { return message_history_index_.has_value(); }
+    [[nodiscard]] size_t message_history_size() const { return message_history_.size(); } 
 
     void enter_command_mode();
     void enter_buffer_switch_mode();
@@ -187,6 +194,11 @@ private:
     std::optional<std::chrono::steady_clock::time_point> message_clear_time_;
     std::vector<EventCallback> event_callbacks_;
 
+    // Message history for M-p/M-n navigation
+    static constexpr size_t MAX_MESSAGE_HISTORY = 50;
+    std::vector<std::string> message_history_;
+    std::optional<size_t> message_history_index_;  // nullopt = not browsing history
+
     // Subsystems - Order matters for initialization based on dependencies
     // Declared in dependency order
     ThemeManager theme_manager_; // No dependencies

+ 12 - 0
src/defaults.hpp

@@ -779,6 +779,14 @@ editor:register_command("view-messages", "View the *Messages* buffer", function(
     end
 end)
 
+editor:register_command("previous-message", "Show previous message from history", function()
+    editor:previous_message()
+end)
+
+editor:register_command("next-message", "Show next message in history", function()
+    editor:next_message()
+end)
+
 -- === Theme Commands ===
 editor:register_command("set-theme", "Select color theme", function(args)
     if #args == 0 then
@@ -945,6 +953,10 @@ editor:bind_key("C-x r t", "string-rectangle")
 editor:bind_key("C-h e", "view-messages")
 editor:bind_key("C-h m", "describe-mode")
 
+-- === M-p/M-n for message history navigation ===
+editor:bind_key("M-p", "previous-message")
+editor:bind_key("M-n", "next-message")
+
 -- === Function Keys ===
 editor:bind_key("F3", "kmacro-start-macro")
 editor:bind_key("F4", "kmacro-end-or-call-macro")

+ 90 - 0
src/editor_core.cpp

@@ -738,6 +738,20 @@ void EditorCore::set_message(std::string msg, MessageSeverity severity) {
         return;
     }
 
+    // Add to message history (except empty messages)
+    if (!msg.empty()) {
+        // Avoid adding duplicate consecutive messages
+        if (message_history_.empty() || message_history_.back() != msg) {
+            message_history_.push_back(msg);
+            // Keep history bounded
+            if (message_history_.size() > MAX_MESSAGE_HISTORY) {
+                message_history_.erase(message_history_.begin());
+            }
+        }
+        // Reset history browsing when new message arrives
+        message_history_index_.reset();
+    }
+
     last_message_ = std::move(msg);
     last_message_severity_ = severity;
 
@@ -762,6 +776,10 @@ void EditorCore::set_message(std::string msg, MessageSeverity severity) {
 }
 
 void EditorCore::check_and_clear_message() {
+    // Don't auto-clear when browsing message history
+    if (message_history_index_.has_value()) {
+        return;
+    }
     if (message_clear_time_.has_value() && std::chrono::steady_clock::now() > message_clear_time_.value()) {
         last_message_.clear();
         message_clear_time_.reset();
@@ -769,6 +787,78 @@ void EditorCore::check_and_clear_message() {
     }
 }
 
+void EditorCore::previous_message() {
+    if (message_history_.empty()) {
+        set_message("No message history");
+        return;
+    }
+
+    if (!message_history_index_.has_value()) {
+        // Start browsing from the most recent message
+        message_history_index_ = message_history_.size() - 1;
+    } else if (*message_history_index_ > 0) {
+        // Move to older message
+        (*message_history_index_)--;
+    } else {
+        // Already at oldest message
+        set_message("Beginning of message history");
+        return;
+    }
+
+    // Display the message from history
+    last_message_ = message_history_[*message_history_index_];
+    last_message_severity_ = MessageSeverity::Info;
+    // Don't auto-clear when browsing history
+    message_clear_time_.reset();
+
+    // Add position indicator
+    std::string indicator = " [" + std::to_string(*message_history_index_ + 1) + "/" +
+                           std::to_string(message_history_.size()) + ", M-n/M-p]";
+    last_message_ += indicator;
+
+    emit_event(EditorEvent::Message);
+}
+
+void EditorCore::next_message() {
+    if (message_history_.empty()) {
+        set_message("No message history");
+        return;
+    }
+
+    if (!message_history_index_.has_value()) {
+        // Not browsing history, show most recent
+        message_history_index_ = message_history_.size() - 1;
+    } else if (*message_history_index_ < message_history_.size() - 1) {
+        // Move to newer message
+        (*message_history_index_)++;
+    } else {
+        // At most recent, exit history mode
+        exit_message_history();
+        set_message("End of message history");
+        return;
+    }
+
+    // Display the message from history
+    last_message_ = message_history_[*message_history_index_];
+    last_message_severity_ = MessageSeverity::Info;
+    // Don't auto-clear when browsing history
+    message_clear_time_.reset();
+
+    // Add position indicator
+    std::string indicator = " [" + std::to_string(*message_history_index_ + 1) + "/" +
+                           std::to_string(message_history_.size()) + ", M-n/M-p]";
+    last_message_ += indicator;
+
+    emit_event(EditorEvent::Message);
+}
+
+void EditorCore::exit_message_history() {
+    message_history_index_.reset();
+    last_message_.clear();
+    message_clear_time_.reset();
+    emit_event(EditorEvent::TransientMessageCleared);
+}
+
 void EditorCore::request_quit() {
     emit_event(EditorEvent::Quit);
 }

+ 16 - 1
src/lua_api.cpp

@@ -782,7 +782,13 @@ void LuaApi::register_types() {
             e.set_message(msg, severity);
         }, // Message with optional severity
 
-        
+        // Message history navigation
+        "previous_message", &EditorCore::previous_message,
+        "next_message", &EditorCore::next_message,
+        "exit_message_history", &EditorCore::exit_message_history,
+        "is_browsing_message_history", &EditorCore::is_browsing_message_history,
+        "message_history_size", &EditorCore::message_history_size,
+
         // Key binding (method on EditorCore)
         "bind_key", [this](EditorCore& core, std::string key, sol::object callback_or_cmd, sol::optional<std::string> description) {
             if (callback_or_cmd.is<std::string>()) {
@@ -909,6 +915,15 @@ void LuaApi::register_functions() {
     lua_.set_function("lumacs_editor_set_message", [this](const std::string& message, sol::optional<std::string> severity) { // Use set_function
         this->lua_editor_set_message(message, severity.value_or("info"));
     });
+    lua_.set_function("lumacs_editor_previous_message", [this]() {
+        core_->previous_message();
+    });
+    lua_.set_function("lumacs_editor_next_message", [this]() {
+        core_->next_message();
+    });
+    lua_.set_function("lumacs_editor_exit_message_history", [this]() {
+        core_->exit_message_history();
+    });
     lua_.set_function("lumacs_config_set_string", [this](const std::string& key, const std::string& value) { // Use set_function
         this->lua_config_set_string(key, value);
     });