#include "lumacs/editor_core.hpp" #include "lumacs/lua_api.hpp" // Include LuaApi header #include "lumacs/command_system.hpp" #include "lumacs/completion_system.hpp" // Include CompletionSystem header #include "lumacs/minibuffer_manager.hpp" // Include MinibufferManager header #include "lumacs/plugin_manager.hpp" // New include for PluginManager #include "lumacs/buffer_manager.hpp" // New include for BufferManager #include "lumacs/window_manager.hpp" // New include for WindowManager #include "lumacs/kill_ring_manager.hpp" // New include for KillRingManager #include "lumacs/register_manager.hpp" // New include for RegisterManager #include "lumacs/macro_manager.hpp" // New include for MacroManager #include "lumacs/rectangle_manager.hpp" // New include for RectangleManager #include "lumacs/logger.hpp" #include #include #include #include #include #include namespace lumacs { EditorCore::EditorCore() : last_message_(), message_clear_time_(), event_callbacks_(), theme_manager_(), config_(), modeline_manager_member_(), // Direct member // Subsystem initializations - order matters for dependencies based on member declaration order in .hpp // Corrected Order based on dependencies and declaration order: lua_api_(std::make_unique()), completion_system_(std::make_unique(*this)), minibuffer_manager_(std::make_unique(*this, *lua_api_, *completion_system_)), command_system_(std::make_unique(*this, *minibuffer_manager_)), keybinding_manager_(std::make_unique(command_system_.get())), // Pass raw pointer plugin_manager_(std::make_unique(*this, *lua_api_)), // Initialize BufferManager with *this (IEditorNotifier) and a placeholder for IWindowManager until WindowManager is initialized // Wait, WindowManager depends on BufferManager for initialization. // This is a classic chicken-and-egg problem if we strictly enforce constructor injection. // BufferManager needs IWindowManager. WindowManager needs BufferManager. // WindowManager creates the initial buffer via BufferManager. // // To break this: // 1. BufferManager shouldn't need IWindowManager in constructor if it only uses it in methods. // It stores the reference. // We can use a two-phase init or pass references to managers after creation? // Or, rely on the fact that we are constructing them here. // But we can't pass *window_manager_ before it's constructed. // // Solution: // Create BufferManager without IWindowManager reference initially? No, it's a reference member. // // Alternative: EditorCore implements IWindowManager too! // Since EditorCore owns WindowManager and delegates to it, EditorCore can implement IWindowManager // and just forward calls to window_manager_. // This allows passing *this to BufferManager. // // Let's verify if EditorCore implements IWindowManager. Not yet. // // Let's update EditorCore to implement IWindowManager as well. buffer_manager_(nullptr), // Temporary null, will initialize in body? No, unique_ptr. window_manager_(nullptr), kill_ring_manager_(std::make_unique()), register_manager_(std::make_unique()), macro_manager_(std::make_unique(*this)), rectangle_manager_(std::make_unique(*this)) { // We need to initialize buffer_manager_ and window_manager_ carefully. // We can use a raw pointer trick or late initialization if we change members to not be references? // Or implementing interfaces on EditorCore is the cleanest way to maintain the "Core is the mediator" pattern // while breaking the direct dependency on the *concrete* Core class. // Let's assume EditorCore implements IWindowManager. // Then: // buffer_manager_ = std::make_unique(*this, *this); // window_manager_ = std::make_unique(*this, *buffer_manager_); // But wait, we haven't made EditorCore implement IWindowManager yet. // Let's modify the plan slightly to make EditorCore implement IWindowManager. // Reverting to the prompt instruction context: I should probably just implement the initialization // assuming I will fix the header next. // Actually, I can't change the header in the same turn easily if I'm stuck here. // But I can use a deferred initialization approach or a temporary valid object. // BETTER APPROACH: // Initialize BufferManager and WindowManager. // WindowManager needs BufferManager. // BufferManager needs WindowManager. // // We can't satisfy both const references at construction time if they depend on each other. // // One must take the other as a setter, or we proxy through EditorCore. // If EditorCore implements both interfaces, we pass *this to both. // // buffer_manager_ = std::make_unique(*this, *this); // window_manager_ = std::make_unique(*this, *buffer_manager_); // This requires EditorCore to be IWindowManager. // Let's do that. // For this step, I will initialize them assuming EditorCore implements IWindowManager. // I will need to update EditorCore header in the next tool call. buffer_manager_ = std::make_unique(*this, *this); window_manager_ = std::make_unique(*this, *buffer_manager_); // ... rest of constructor // LuaApi needs core_ pointer to be valid, so set it after constructor body starts lua_api_->set_core(*this); // Ensure the initial window has a valid buffer new_buffer("*scratch*"); // Initialize themes // theme_manager_.create_default_themes(); // REMOVED: Themes are now loaded from Lua // theme_manager_.set_active_theme("everforest-dark"); // REMOVED: Initial theme will be set by Lua } EditorCore::~EditorCore() = default; // === Cursor Proxies === Position EditorCore::cursor() const noexcept { Position cursor_pos = active_window()->cursor(); return cursor_pos; } void EditorCore::set_cursor(Position pos) { active_window()->set_cursor(pos); adjust_scroll(); } void EditorCore::move_up() { window_manager_->active_window()->move_up(); emit_event(EditorEvent::CursorMoved); } void EditorCore::move_down() { window_manager_->active_window()->move_down(); emit_event(EditorEvent::CursorMoved); } void EditorCore::move_left() { window_manager_->active_window()->move_left(); emit_event(EditorEvent::CursorMoved); } void EditorCore::move_right() { window_manager_->active_window()->move_right(); emit_event(EditorEvent::CursorMoved); } void EditorCore::move_to_line_start() { window_manager_->active_window()->move_to_line_start(); emit_event(EditorEvent::CursorMoved); } void EditorCore::move_to_line_end() { window_manager_->active_window()->move_to_line_end(); emit_event(EditorEvent::CursorMoved); } // Helper: Check if character is a word constituent static bool is_word_char(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } void EditorCore::move_forward_word() { auto new_pos = calculate_forward_word_pos(window_manager_->active_window()->cursor()); window_manager_->active_window()->set_cursor(new_pos); emit_event(EditorEvent::CursorMoved); } void EditorCore::move_backward_word() { auto new_pos = calculate_backward_word_pos(window_manager_->active_window()->cursor()); window_manager_->active_window()->set_cursor(new_pos); emit_event(EditorEvent::CursorMoved); } Position EditorCore::calculate_forward_word_pos(Position start_pos) { // Delegates to active buffer auto& buf = *buffer_manager_->active_buffer(); auto cursor = start_pos; // Check if we are at the end of buffer if (cursor.line >= buf.line_count()) { return cursor; } // 1. Skip non-word chars (whitespace/punctuation) while (true) { const auto& line = buf.line(cursor.line); while (cursor.column < line.size() && !is_word_char(line[cursor.column])) { cursor.column++; } // If we found a word char, we're done with step 1 if (cursor.column < line.size()) { break; } // Move to next line if (cursor.line < buf.line_count() - 1) { cursor.line++; cursor.column = 0; } else { // At end of buffer return cursor; } } // 2. Skip word chars const auto& line = buf.line(cursor.line); while (cursor.column < line.size() && is_word_char(line[cursor.column])) { cursor.column++; } return cursor; } Position EditorCore::calculate_backward_word_pos(Position start_pos) { // Delegates to active buffer auto& buf = *buffer_manager_->active_buffer(); auto cursor = start_pos; // Skip whitespace and punctuation backwards while (true) { // If at start of line, go to previous line if (cursor.column == 0) { if (cursor.line == 0) { // At start of buffer break; } cursor.line--; cursor.column = buf.line(cursor.line).size(); continue; } // Move back one char cursor.column--; const auto& line = buf.line(cursor.line); // If we hit a word char, keep going back through the word if (is_word_char(line[cursor.column])) { // Move to start of word while (cursor.column > 0 && is_word_char(line[cursor.column - 1])) { cursor.column--; } break; } } return cursor; } void EditorCore::page_up() { auto& viewport = window_manager_->active_window()->viewport(); auto cursor = window_manager_->active_window()->cursor(); // Move up by viewport height (minus 2 for overlap) int page_size = std::max(1, viewport.height - 2); if (cursor.line >= static_cast(page_size)) { cursor.line -= page_size; } else { cursor.line = 0; } // Keep column position if possible auto& buf = *buffer_manager_->active_buffer(); const auto& line = buf.line(cursor.line); cursor.column = std::min(cursor.column, line.size()); window_manager_->active_window()->set_cursor(cursor); window_manager_->active_window()->adjust_scroll(); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::ViewportChanged); } void EditorCore::page_down() { auto& viewport = window_manager_->active_window()->viewport(); auto cursor = window_manager_->active_window()->cursor(); auto& buf = *buffer_manager_->active_buffer(); // Move down by viewport height (minus 2 for overlap) int page_size = std::max(1, viewport.height - 2); cursor.line = std::min(cursor.line + page_size, buf.line_count() - 1); // Keep column position if possible const auto& line = buf.line(cursor.line); cursor.column = std::min(cursor.column, line.size()); window_manager_->active_window()->set_cursor(cursor); window_manager_->active_window()->adjust_scroll(); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::ViewportChanged); } void EditorCore::goto_beginning() { Position pos = {0, 0}; window_manager_->active_window()->set_cursor(pos); window_manager_->active_window()->adjust_scroll(); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::ViewportChanged); } void EditorCore::goto_end() { auto& buf = *buffer_manager_->active_buffer(); size_t last_line = buf.line_count() > 0 ? buf.line_count() - 1 : 0; size_t last_col = buf.line(last_line).size(); Position pos = {last_line, last_col}; window_manager_->active_window()->set_cursor(pos); window_manager_->active_window()->adjust_scroll(); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::ViewportChanged); } void EditorCore::goto_line(size_t line) { auto& buf = *buffer_manager_->active_buffer(); line = std::min(line, buf.line_count() > 0 ? buf.line_count() - 1 : 0); // Clamp to max line index Position pos = {line, 0}; window_manager_->active_window()->set_cursor(pos); window_manager_->active_window()->adjust_scroll(); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::ViewportChanged); } // === Viewport Management (Proxies to active window) === const Viewport& EditorCore::viewport() const noexcept { return window_manager_->active_window()->viewport(); } void EditorCore::set_viewport_size(int width, int height) { window_manager_->active_window()->set_viewport_size(width, height); emit_event(EditorEvent::ViewportChanged); } void EditorCore::adjust_scroll() { window_manager_->active_window()->adjust_scroll(); emit_event(EditorEvent::ViewportChanged); } std::pair EditorCore::visible_line_range() const { return window_manager_->active_window()->visible_line_range(); } // === Undo/Redo === bool EditorCore::undo() { auto& buf = *buffer_manager_->active_buffer(); buf.save_undo_state(window_manager_->active_window()->cursor()); Position new_cursor = window_manager_->active_window()->cursor(); if (buf.undo(new_cursor)) { window_manager_->active_window()->set_cursor(new_cursor); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::BufferModified); return true; } return false; } bool EditorCore::redo() { auto& buf = *buffer_manager_->active_buffer(); Position new_cursor = window_manager_->active_window()->cursor(); if (buf.redo(new_cursor)) { window_manager_->active_window()->set_cursor(new_cursor); emit_event(EditorEvent::CursorMoved); emit_event(EditorEvent::BufferModified); return true; } return false; } bool EditorCore::can_undo() const { return buffer_manager_->active_buffer()->can_undo(); } bool EditorCore::can_redo() const { return buffer_manager_->active_buffer()->can_redo(); } // === Kill Ring (Delegated to KillRingManager) === void EditorCore::kill_line() { auto& buf = *buffer_manager_->active_buffer(); auto cursor = window_manager_->active_window()->cursor(); const auto& line_text = buf.line(cursor.line); Range range_to_kill; std::string killed_text; if (cursor.column == 0 && cursor.line < buf.line_count() - 1) { // Kill whole line including newline range_to_kill = {{cursor.line, 0}, {cursor.line + 1, 0}}; } else if (cursor.column < line_text.size()) { // Kill from cursor to end of line range_to_kill = {cursor, {cursor.line, line_text.size()}}; } else { // Cursor is at or past end of line if (cursor.line < buf.line_count() - 1) { // Kill only the newline (join with next line) range_to_kill = {{cursor.line, line_text.size()}, {cursor.line + 1, 0}}; } else { // At end of last line, nothing to kill return; } } killed_text = buf.get_text_in_range(range_to_kill); // Get text BEFORE erase if (!killed_text.empty()) { kill_ring_manager_->push(killed_text); buf.erase(range_to_kill); // Cursor position does not change after kill-line; it remains at the beginning of the killed region. emit_event(EditorEvent::BufferModified); emit_event(EditorEvent::CursorMoved); } } void EditorCore::kill_region() { auto& buf = *buffer_manager_->active_buffer(); auto cursor = window_manager_->active_window()->cursor(); auto region = buf.get_region(cursor); if (!region.has_value()) { set_message("No active region"); return; } std::string killed_text = buf.get_text_in_range(region.value()); if (!killed_text.empty()) { kill_ring_manager_->push(killed_text); // Delegate to manager buf.deactivate_mark(); // Move cursor to start of killed region window_manager_->active_window()->set_cursor(region.value().start); emit_event(EditorEvent::BufferModified); emit_event(EditorEvent::CursorMoved); spdlog::debug("Killed region: '{}'", killed_text); } } void EditorCore::kill_word() { auto cursor = window_manager_->active_window()->cursor(); auto end_pos = calculate_forward_word_pos(cursor); if (cursor == end_pos) return; Range range = {cursor, end_pos}; auto& buf = *buffer_manager_->active_buffer(); std::string text = buf.get_text_in_range(range); if (!text.empty()) { kill_ring_manager_->push(text); // Delegate to manager buf.erase(range); emit_event(EditorEvent::BufferModified); spdlog::debug("Killed word: '{}'", text); } } void EditorCore::backward_kill_word() { auto cursor = window_manager_->active_window()->cursor(); auto start_pos = calculate_backward_word_pos(cursor); if (cursor == start_pos) return; Range range = {start_pos, cursor}; auto& buf = *buffer_manager_->active_buffer(); std::string text = buf.get_text_in_range(range); if (!text.empty()) { kill_ring_manager_->push(text); // Delegate to manager buf.erase(range); window_manager_->active_window()->set_cursor(start_pos); emit_event(EditorEvent::BufferModified); emit_event(EditorEvent::CursorMoved); spdlog::debug("Backward killed word: '{}'", text); } } void EditorCore::copy_region_as_kill() { auto& buf = *buffer_manager_->active_buffer(); auto cursor = window_manager_->active_window()->cursor(); auto region = buf.get_region(cursor); if (!region.has_value()) { set_message("No active region"); return; } std::string copied_text = buf.get_text_in_range(region.value()); if (!copied_text.empty()) { kill_ring_manager_->push(copied_text); // Delegate to manager buf.deactivate_mark(); set_message("Region copied"); spdlog::debug("Copied region: '{}'", copied_text); } } void EditorCore::yank() { if (kill_ring_manager_->empty()) { set_message("Kill ring is empty"); return; } std::string text = kill_ring_manager_->current(); if (text.empty()) { return; } auto& buf = *buffer_manager_->active_buffer(); auto cursor = window_manager_->active_window()->cursor(); // Insert the text buf.insert(cursor, text); // Calculate new cursor position after insertion Position new_cursor = cursor; size_t newline_count = std::count(text.begin(), text.end(), '\n'); if (newline_count > 0) { // Multi-line yank: cursor goes to end of inserted text new_cursor.line += newline_count; size_t last_newline = text.rfind('\n'); new_cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0; } else { // Single-line yank: advance column new_cursor.column += text.size(); } // Save yank range in KillRingManager for yank-pop kill_ring_manager_->set_yank_range(cursor, new_cursor); window_manager_->active_window()->set_cursor(new_cursor); emit_event(EditorEvent::BufferModified); emit_event(EditorEvent::CursorMoved); spdlog::debug("Yanked: '{}'", text); } void EditorCore::yank_pop() { if (kill_ring_manager_->empty()) { set_message("Kill ring is empty"); return; } if (!kill_ring_manager_->has_yank_state()) { set_message("Previous command was not a yank"); return; } // Delete the previously yanked text auto& buf = *buffer_manager_->active_buffer(); Range yank_range = {kill_ring_manager_->yank_start().value(), kill_ring_manager_->yank_end().value()}; buf.erase(yank_range); // Get previous entry in kill ring std::string text = kill_ring_manager_->previous(); // Restore cursor to yank start auto cursor = kill_ring_manager_->yank_start().value(); window_manager_->active_window()->set_cursor(cursor); // Insert new text buf.insert(cursor, text); // Calculate new end position Position new_cursor = cursor; size_t newline_count = std::count(text.begin(), text.end(), '\n'); if (newline_count > 0) { new_cursor.line += newline_count; size_t last_newline = text.rfind('\n'); new_cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0; } else { new_cursor.column += text.size(); } // Update yank end position kill_ring_manager_->set_yank_end(new_cursor); window_manager_->active_window()->set_cursor(new_cursor); emit_event(EditorEvent::BufferModified); emit_event(EditorEvent::CursorMoved); spdlog::debug("Yank-pop: '{}'", text); } // === Registers (Delegated to RegisterManager) === void EditorCore::copy_to_register(char register_name, const std::string& text) { // Validate register name (a-z, A-Z, 0-9) if (!std::isalnum(register_name)) { set_message("Invalid register name"); return; } register_manager_->copy_to_register(register_name, text); set_message(std::string("Saved text to register ") + register_name); } bool EditorCore::insert_register(char register_name) { // Validate register name if (!std::isalnum(register_name)) { set_message("Invalid register name"); return false; } std::optional text_opt = register_manager_->get_from_register(register_name); if (!text_opt.has_value()) { set_message(std::string("Register ") + register_name + " is empty"); return false; } std::string text = text_opt.value(); auto& buf = *buffer_manager_->active_buffer(); Position cursor = window_manager_->active_window()->cursor(); buf.insert(cursor, text); // Move cursor to end of inserted text size_t newline_count = std::count(text.begin(), text.end(), '\n'); if (newline_count > 0) { cursor.line += newline_count; size_t last_newline = text.rfind('\n'); cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0; } else { cursor.column += text.size(); } window_manager_->active_window()->set_cursor(cursor); emit_event(EditorEvent::BufferModified); emit_event(EditorEvent::CursorMoved); set_message(std::string("Inserted register ") + register_name); return true; } void EditorCore::copy_region_to_register(char register_name) { auto& buf = *buffer_manager_->active_buffer(); Position cursor = window_manager_->active_window()->cursor(); auto region = buf.get_region(cursor); if (!region) { set_message("No active region"); return; } std::string text = buf.get_text_in_range(*region); copy_to_register(register_name, text); } bool EditorCore::yank_from_register(char register_name) { return insert_register(register_name); } // === Keyboard Macros (Delegated to MacroManager) === void EditorCore::start_kbd_macro() { macro_manager_->start_kbd_macro(); } void EditorCore::end_kbd_macro_or_call() { macro_manager_->end_kbd_macro_or_call(); } void EditorCore::record_key_sequence(const std::string& key_sequence) { macro_manager_->record_key_sequence(key_sequence); } // === Rectangles (Delegated to RectangleManager) === void EditorCore::kill_rectangle() { rectangle_manager_->kill_rectangle(); } void EditorCore::yank_rectangle() { rectangle_manager_->yank_rectangle(); } void EditorCore::string_rectangle(const std::string& text) { rectangle_manager_->string_rectangle(text); } // === Helper Implementation === void EditorCore::collect_windows(LayoutNode* node, std::vector>& windows) { if (!node) return; if (node->type == LayoutNode::Type::Leaf && node->window) { windows.push_back(node->window); } else { collect_windows(node->child1.get(), windows); collect_windows(node->child2.get(), windows); } } void EditorCore::set_message(std::string msg, MessageSeverity severity) { // Log to *Messages* buffer with timestamp 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) { // Format timestamp and severity prefix auto now = std::chrono::system_clock::now(); auto time_t_now = std::chrono::system_clock::to_time_t(now); std::stringstream timestamp_ss; timestamp_ss << "[" << std::put_time(std::localtime(&time_t_now), "%H:%M:%S") << "] "; // Add severity prefix for non-info messages std::string severity_prefix; switch (severity) { case MessageSeverity::Warning: severity_prefix = "[WARN] "; break; case MessageSeverity::Error: severity_prefix = "[ERROR] "; break; case MessageSeverity::Debug: severity_prefix = "[DEBUG] "; break; default: break; } // Append message with timestamp to buffer size_t last_line = messages_buf->line_count() > 0 ? messages_buf->line_count() - 1 : 0; size_t last_col = 0; if (messages_buf->line_count() > 0) { last_col = messages_buf->lines().back().length(); } messages_buf->insert({last_line, last_col}, timestamp_ss.str() + severity_prefix + msg + "\n"); } } // Debug messages are only logged, not displayed in echo area if (severity == MessageSeverity::Debug) { return; } last_message_ = std::move(msg); last_message_severity_ = severity; // Set timeout based on severity switch (severity) { case MessageSeverity::Info: message_clear_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(3); break; case MessageSeverity::Warning: message_clear_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(5); break; case MessageSeverity::Error: // Error messages don't auto-clear message_clear_time_.reset(); break; default: message_clear_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(3); break; } emit_event(EditorEvent::Message); } void EditorCore::check_and_clear_message() { if (message_clear_time_.has_value() && std::chrono::steady_clock::now() > message_clear_time_.value()) { last_message_.clear(); message_clear_time_.reset(); emit_event(EditorEvent::TransientMessageCleared); } } void EditorCore::request_quit() { emit_event(EditorEvent::Quit); } bool EditorCore::is_recording_macro() const noexcept { return macro_manager_->is_recording_macro(); } void EditorCore::enter_command_mode() { emit_event(EditorEvent::CommandMode); } void EditorCore::enter_buffer_switch_mode() { emit_event(EditorEvent::BufferSwitchMode); } void EditorCore::enter_kill_buffer_mode() { emit_event(EditorEvent::KillBufferMode); } void EditorCore::enter_find_file_mode() { emit_event(EditorEvent::FindFileMode); } void EditorCore::enter_theme_selection_mode() { emit_event(EditorEvent::ThemeSelectionMode); } void EditorCore::enter_isearch_mode() { emit_event(EditorEvent::ISearchMode); } void EditorCore::enter_isearch_backward_mode() { emit_event(EditorEvent::ISearchBackwardMode); } void EditorCore::set_theme(const std::string& theme_name) { theme_manager_.set_active_theme(theme_name); emit_event(EditorEvent::ThemeChanged); } // === Private === void EditorCore::emit_event(EditorEvent event) { for (const auto& callback : event_callbacks_) { callback(event); } } } // namespace lumacs