*Messages* buffer exists - Messages are logged to it via EditorCore::set_message()editor:message(text) works for simple messages*Messages* buffer not accessible - Created with create_buffer_no_window(), not in buffer list┌─────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────┘
Goal: Users can view message history via C-x b *Messages* or M-x view-messages
// 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 messagesview-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")
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
}
Goal: Users know when messages are truncated
// 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);
}
}
// 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());
}
}
Goal: Different message types are visually distinct and have different behaviors
// 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);
-- 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)
// 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);
}
}
Goal: Important messages can span multiple lines in the 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.
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.
| 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 |
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")
Small change to EditorCore::set_message() to prefix messages with [HH:MM:SS].
Check if create_buffer_no_window adds buffer to the list returned by get_buffer_names().
M-x view-messages opens Messages bufferC-h e opens Messages bufferC-x b *Messages* switches to Messages buffer| 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.