MESSAGE_SYSTEM_PLAN.md 13 KB

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

// 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

-- 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

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

// 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

// 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<size_t>(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

// 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

-- 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

// 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:

// 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

-- 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

-- 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.