# Lumacs Message System Improvement Plan ## Current State Analysis ### What Works 1. **`*Messages*` buffer exists** - Messages are logged to it via `EditorCore::set_message()` 2. **Transient echo area** - Single-line message display at bottom with 3-second auto-clear 3. **Basic Lua API** - `editor:message(text)` works for simple messages ### Pain Points 1. **Long messages are truncated** - No visual indication that text was cut off 2. **`*Messages*` buffer not accessible** - Created with `create_buffer_no_window()`, not in buffer list 3. **No message history navigation** - Users can't review past messages 4. **No message severity levels** - All messages treated equally 5. **No overflow indicator** - Users don't know if they're seeing the full message --- ## Proposed Solution: Emacs-Style Message System ### Architecture Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ User Input │ │ editor:message("text") / editor:message("text", "error") │ └─────────────────────────┬───────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────┐ │ EditorCore::set_message() │ │ 1. Log to *Messages* buffer with timestamp │ │ 2. Determine display behavior based on length/severity │ │ 3. Store in transient message slot │ │ 4. Emit EditorEvent::Message │ └─────────────────────────┬───────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Echo Area Rendering │ │ IF message fits on one line: │ │ → Display full message │ │ ELSE IF message is multi-line OR too long: │ │ → Display first line + "[...see *Messages*]" │ │ OR │ │ → Display truncated + "..." indicator │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Implementation Plan ### Phase 1: Make *Messages* Buffer Accessible (Priority: HIGH) **Goal:** Users can view message history via `C-x b *Messages*` or `M-x view-messages` #### 1.1 Change buffer creation method ```cpp // Current (hidden): messages_buf = buffer_manager_->create_buffer_no_window("*Messages*"); // New (accessible): messages_buf = buffer_manager_->create_buffer_no_window("*Messages*"); // Buffer is already in buffer list, just needs to be queryable ``` **Files to modify:** - `src/buffer_manager.cpp` - Ensure `*Messages*` appears in `get_buffer_names()` - `src/editor_core.cpp` - Add timestamp to logged messages #### 1.2 Add `view-messages` command ```lua -- In defaults.hpp editor:register_command("view-messages", "View the *Messages* buffer", function() local buf = editor:get_buffer_by_name("*Messages*") if buf then editor:switch_buffer_in_window("*Messages*") editor:goto_end() else editor:message("No messages yet") end end, false) -- Keybinding (optional, Emacs uses C-h e or M-x view-echo-area-messages) editor:bind_key("C-h e", "view-messages") ``` #### 1.3 Add timestamps to messages ```cpp void EditorCore::set_message(std::string msg) { if (buffer_manager_) { auto messages_buf = buffer_manager_->get_buffer_by_name("*Messages*"); if (!messages_buf) { messages_buf = buffer_manager_->create_buffer_no_window("*Messages*"); } if (messages_buf) { // Add timestamp auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); std::stringstream ss; ss << "[" << std::put_time(std::localtime(&time_t), "%H:%M:%S") << "] " << msg << "\n"; // Append to buffer size_t last_line = messages_buf->line_count() > 0 ? messages_buf->line_count() - 1 : 0; size_t last_col = messages_buf->line_count() > 0 ? messages_buf->lines().back().length() : 0; messages_buf->insert({last_line, last_col}, ss.str()); } } // ... rest of existing code } ``` --- ### Phase 2: Add Truncation Indicator (Priority: HIGH) **Goal:** Users know when messages are truncated #### 2.1 GTK Renderer - Add ellipsis for long messages ```cpp // In gtk_renderer.cpp render_minibuffer() void GtkRenderer::render_minibuffer(/* ... */) { // ... existing code ... if (!core_.minibuffer_manager().is_active() && !core_.last_message().empty()) { std::string msg = core_.last_message(); // Calculate available width double available_width = width - PADDING_LEFT - PADDING_RIGHT; // Create layout to measure text auto layout = Pango::Layout::create(context_widget_.get_pango_context()); layout->set_font_description(font_desc_); layout->set_text(msg); int text_width, text_height; layout->get_pixel_size(text_width, text_height); // If text is too wide, truncate with indicator if (text_width > available_width) { const std::string suffix = "... [C-h e for full message]"; // Binary search for max chars that fit size_t max_chars = find_max_fitting_chars(layout, msg, available_width, suffix); msg = msg.substr(0, max_chars) + suffix; layout->set_text(msg); } // Render cr->move_to(minibuffer_x, minibuffer_y); layout->show_in_cairo_context(cr); } } ``` #### 2.2 TUI Renderer - Add ellipsis for long messages ```cpp // In tui_editor.cpp render() void TuiEditor::render_minibuffer_area() { // ... existing code ... if (!core_->minibuffer_manager().is_active() && !message_line_.empty()) { std::string msg = message_line_; // Calculate available width (leave room for indicator) int available_width = width_ - 2; if (msg.length() > static_cast(available_width)) { const std::string suffix = "...[C-h e]"; size_t max_chars = available_width - suffix.length(); msg = msg.substr(0, max_chars) + suffix; } mvprintw(height_ - 1, 0, "%s", msg.c_str()); } } ``` --- ### Phase 3: Message Severity Levels (Priority: MEDIUM) **Goal:** Different message types are visually distinct and have different behaviors #### 3.1 Add MessageSeverity enum ```cpp // In editor_core.hpp enum class MessageSeverity { Info, // Normal messages, 3-second timeout Warning, // Yellow/orange, 5-second timeout Error, // Red, no auto-clear until user input Debug // Only logged to *Messages*, not displayed in echo area }; // Enhanced set_message signature void set_message(std::string msg, MessageSeverity severity = MessageSeverity::Info); ``` #### 3.2 Update Lua API ```lua -- Enhanced API editor:message("File saved") -- Info (default) editor:message("Buffer modified", "warning") -- Warning editor:message("File not found!", "error") -- Error editor:log("Debug info") -- Debug (log only) ``` #### 3.3 Severity-based rendering ```cpp // Different colors based on severity Color get_message_color(MessageSeverity severity) { switch (severity) { case MessageSeverity::Info: return fg_color; case MessageSeverity::Warning: return Color(255, 200, 0); // Yellow/Orange case MessageSeverity::Error: return Color(255, 80, 80); // Red default: return fg_color; } } // Different timeout based on severity std::chrono::seconds get_message_timeout(MessageSeverity severity) { switch (severity) { case MessageSeverity::Info: return std::chrono::seconds(3); case MessageSeverity::Warning: return std::chrono::seconds(5); case MessageSeverity::Error: return std::chrono::seconds(0); // No auto-clear default: return std::chrono::seconds(3); } } ``` --- ### Phase 4: Multi-line Message Support (Priority: LOW) **Goal:** Important messages can span multiple lines in the echo area #### 4.1 Expandable echo area For very important messages (errors, confirmations), allow the echo area to expand: ```cpp // Track if echo area should expand bool echo_area_expanded_ = false; size_t echo_area_lines_ = 1; // 1 = normal, 2-3 for expanded // Auto-expand for multi-line messages void set_message(std::string msg, MessageSeverity severity) { size_t newlines = std::count(msg.begin(), msg.end(), '\n'); if (newlines > 0 && severity >= MessageSeverity::Warning) { echo_area_expanded_ = true; echo_area_lines_ = std::min(newlines + 1, 3UL); // Max 3 lines } // ... rest of implementation } ``` This is lower priority because it requires significant rendering changes. --- ### Phase 5: Message History Navigation (Priority: LOW) **Goal:** Users can cycle through recent messages without switching to `*Messages*` buffer ```lua -- M-p to show previous message -- M-n to show next message (when in message history mode) local message_history_index = nil editor:bind_key("M-p", function() -- Show previous message from history -- This would require C++ support to track message history end) ``` This is lower priority because `view-messages` command provides this functionality. --- ## Implementation Order | Phase | Description | Effort | Impact | Priority | |-------|-------------|--------|--------|----------| | 1.1 | Make *Messages* in buffer list | Low | High | **P0** | | 1.2 | Add view-messages command | Low | High | **P0** | | 1.3 | Add timestamps to messages | Low | Medium | **P0** | | 2.1 | GTK truncation indicator | Medium | High | **P1** | | 2.2 | TUI truncation indicator | Medium | High | **P1** | | 3.x | Message severity levels | Medium | Medium | **P2** | | 4.x | Multi-line echo area | High | Low | **P3** | | 5.x | Message history navigation | Medium | Low | **P3** | --- ## Quick Wins (Can implement immediately) ### 1. Add `view-messages` command to defaults.hpp ```lua -- Add to COMMANDS section of defaults.hpp editor:register_command("view-messages", "View the *Messages* buffer", function() local buf = editor:get_buffer_by_name("*Messages*") if buf then editor:switch_buffer_in_window("*Messages*") editor:goto_end() editor:message("Viewing *Messages* buffer") else editor:message("No messages logged yet") end end, false) -- Add keybinding editor:bind_key("C-h e", "view-messages") ``` ### 2. Add timestamp to messages in C++ Small change to `EditorCore::set_message()` to prefix messages with `[HH:MM:SS]`. ### 3. Verify *Messages* buffer appears in buffer list Check if `create_buffer_no_window` adds buffer to the list returned by `get_buffer_names()`. --- ## Testing Checklist - [ ] `M-x view-messages` opens *Messages* buffer - [ ] `C-h e` opens *Messages* buffer - [ ] `C-x b *Messages*` switches to *Messages* buffer - [ ] Long messages show truncation indicator - [ ] Messages have timestamps in *Messages* buffer - [ ] Different severity messages have different colors (if implemented) - [ ] Error messages don't auto-clear (if implemented) - [ ] All 32 existing tests still pass --- ## Comparison with Emacs | Feature | Emacs | Lumacs Current | Lumacs Planned | |---------|-------|----------------|----------------| | Echo area | ✓ | ✓ | ✓ | | *Messages* buffer | ✓ | ✓ (hidden) | ✓ (accessible) | | `view-echo-area-messages` | ✓ | ✗ | ✓ | | Truncation indicator | ✓ | ✗ | ✓ | | Message severity | Partial | ✗ | Planned | | Multi-line echo | ✓ | ✗ | Optional | | Message history nav | ✗ | ✗ | Optional | This plan brings Lumacs closer to Emacs behavior while keeping the implementation manageable.