| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 |
- #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 <spdlog/spdlog.h>
- #include <algorithm>
- #include <chrono>
- #include <ctime>
- #include <iomanip>
- #include <sstream>
- 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<LuaApi>()),
- completion_system_(std::make_unique<CompletionSystem>(*this)),
- minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)),
- command_system_(std::make_unique<CommandSystem>(*this, *minibuffer_manager_)),
- keybinding_manager_(std::make_unique<KeyBindingManager>(command_system_.get())), // Pass raw pointer
- plugin_manager_(std::make_unique<PluginManager>(*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<KillRingManager>()),
- register_manager_(std::make_unique<RegisterManager>()),
- macro_manager_(std::make_unique<MacroManager>(*this)),
- rectangle_manager_(std::make_unique<RectangleManager>(*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<BufferManager>(*this, *this);
- // window_manager_ = std::make_unique<WindowManager>(*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<BufferManager>(*this, *this);
- // window_manager_ = std::make_unique<WindowManager>(*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<BufferManager>(*this, *this);
- window_manager_ = std::make_unique<WindowManager>(*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<size_t>(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<size_t, size_t> 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<std::string> 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<std::shared_ptr<Window>>& 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
|