| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 |
- #include "lumacs/editor_core.hpp"
- #include "lumacs/lua_api.hpp" // Include LuaApi header
- #include <algorithm>
- #include <iostream>
- namespace lumacs {
- EditorCore::EditorCore() :
- buffers_(), // 1. std::list
- root_node_(), // 2. std::shared_ptr
- active_window_(), // 3. std::shared_ptr
- last_message_(), // 4. std::string
- event_callbacks_(), // 5. std::vector
- kill_ring_(), // 6. KillRing
- last_yank_start_(), // 7. std::optional
- last_yank_end_(), // 8. std::optional
- registers_(), // 9. std::unordered_map
- current_macro_(), // 10. std::vector
- last_macro_(), // 11. std::vector
- recording_macro_(false), // 12. bool
- rectangle_kill_ring_(), // 13. std::vector
- theme_manager_(), // 14. ThemeManager
- config_(), // 15. Config
- keybinding_manager_(), // 16. KeyBindingManager
- lua_api_(std::make_unique<LuaApi>()) // 17. std::unique_ptr
- {
- // LuaApi needs core_ pointer to be valid, so set it after constructor body starts
- lua_api_->set_core(*this);
- // Create initial buffer
- auto buffer = std::make_shared<Buffer>();
- buffers_.push_back(buffer);
-
- // Create initial window
- active_window_ = std::make_shared<Window>(buffer);
- root_node_ = std::make_shared<LayoutNode>(active_window_);
-
- // Initialize themes
- theme_manager_.create_default_themes();
- theme_manager_.set_active_theme("everforest-dark");
- // LuaApi will load init.lua, which relies on `editor` global being set via set_core().
- lua_api_->load_init_file();
- }
- EditorCore::~EditorCore() = default;
- // === Buffer Management ===
- const Buffer& EditorCore::buffer() const noexcept {
- return active_window_->buffer();
- }
- Buffer& EditorCore::buffer() noexcept {
- return active_window_->buffer();
- }
- bool EditorCore::load_file(const std::filesystem::path& path) {
- std::filesystem::path abs_path = std::filesystem::absolute(path);
- // Check if already open
- for (const auto& buf : buffers_) {
- if (buf->file_path() && std::filesystem::equivalent(*buf->file_path(), abs_path)) {
- active_window_->set_buffer(buf);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- return true;
- }
- }
- auto new_buffer_opt = Buffer::from_file(abs_path);
- if (!new_buffer_opt) {
- return false;
- }
-
- auto new_buffer = std::make_shared<Buffer>(std::move(*new_buffer_opt));
- buffers_.push_back(new_buffer);
- active_window_->set_buffer(new_buffer);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- return true;
- }
- void EditorCore::new_buffer(std::string name) {
- auto new_buffer = std::make_shared<Buffer>(std::move(name));
- buffers_.push_back(new_buffer);
- active_window_->set_buffer(new_buffer);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- }
- std::vector<std::string> EditorCore::get_buffer_names() const {
- std::vector<std::string> names;
- names.reserve(buffers_.size());
- for (const auto& buf : buffers_) {
- names.push_back(buf->name());
- }
- return names;
- }
- std::shared_ptr<Buffer> EditorCore::get_buffer_by_name(const std::string& name) {
- for (const auto& buf : buffers_) {
- if (buf->name() == name) {
- return buf;
- }
- }
- return nullptr;
- }
- bool EditorCore::switch_buffer_in_window(const std::string& name) {
- auto buf = get_buffer_by_name(name);
- if (!buf) {
- return false;
- }
- active_window_->set_buffer(buf);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- return true;
- }
- bool EditorCore::close_buffer(const std::string& name) {
- auto buf = get_buffer_by_name(name);
- if (!buf) {
- return false;
- }
- // Cannot close buffer if it's the only one
- if (buffers_.size() <= 1) {
- set_message("Cannot close last buffer");
- return false;
- }
- // Check if buffer is displayed in any window
- std::vector<std::shared_ptr<Window>> windows;
- collect_windows(root_node_.get(), windows);
- for (const auto& win : windows) {
- if (win->buffer_ptr() == buf) {
- // Buffer is displayed, switch to another buffer first
- // Find another buffer
- auto other_buf = buffers_.front() == buf ? *(++buffers_.begin()) : buffers_.front();
- win->set_buffer(other_buf);
- }
- }
- // Remove buffer from list
- buffers_.remove(buf);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- return true;
- }
- std::vector<EditorCore::BufferInfo> EditorCore::get_all_buffer_info() const {
- std::vector<BufferInfo> info;
- info.reserve(buffers_.size());
- for (const auto& buf : buffers_) {
- BufferInfo bi;
- bi.name = buf->name();
- bi.size = buf->line_count();
- bi.modified = buf->is_modified();
- bi.mode = "fundamental-mode"; // TODO: Get actual mode from buffer
- bi.filepath = buf->file_path();
- info.push_back(bi);
- }
- return info;
- }
- // === Window Management ===
- // Helper to recursively replace a window in the tree
- bool replace_window_node(std::shared_ptr<LayoutNode> node,
- std::shared_ptr<Window> target,
- std::shared_ptr<LayoutNode> replacement) {
- if (node->type == LayoutNode::Type::Leaf) {
- return false;
- }
-
- // Check immediate children
- if (node->child1->type == LayoutNode::Type::Leaf) {
- if (node->child1->window == target) {
- node->child1 = replacement;
- return true;
- }
- }
-
- if (node->child2->type == LayoutNode::Type::Leaf) {
- if (node->child2->window == target) {
- node->child2 = replacement;
- return true;
- }
- }
-
- // Recurse
- bool found = false;
- if (node->child1->type != LayoutNode::Type::Leaf) {
- found = replace_window_node(node->child1, target, replacement);
- }
-
- if (!found && node->child2->type != LayoutNode::Type::Leaf) {
- found = replace_window_node(node->child2, target, replacement);
- }
-
- return found;
- }
- void EditorCore::split_horizontally() {
- std::cerr << "[DEBUG] split_horizontally() called" << std::endl;
- // New window sharing same buffer
- auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
- new_window->set_cursor(active_window_->cursor()); // Start at same position
- // Create split node
- auto new_leaf = std::make_shared<LayoutNode>(new_window);
- auto current_leaf = std::make_shared<LayoutNode>(active_window_);
- auto split = std::make_shared<LayoutNode>(
- LayoutNode::Type::HorizontalSplit,
- current_leaf, // Top
- new_leaf // Bottom
- );
- if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
- std::cerr << "[DEBUG] Replacing root node" << std::endl;
- root_node_ = split;
- } else {
- std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
- replace_window_node(root_node_, active_window_, split);
- }
- active_window_ = new_window; // Focus new window
- emit_event(EditorEvent::WindowLayoutChanged);
- std::cerr << "[DEBUG] split_horizontally() completed" << std::endl;
- }
- void EditorCore::split_vertically() {
- std::cerr << "[DEBUG] split_vertically() called" << std::endl;
- // New window sharing same buffer
- auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
- new_window->set_cursor(active_window_->cursor());
- // Create split node
- auto new_leaf = std::make_shared<LayoutNode>(new_window);
- auto current_leaf = std::make_shared<LayoutNode>(active_window_);
- // Vertical Split = Left/Right division
- auto split = std::make_shared<LayoutNode>(
- LayoutNode::Type::VerticalSplit,
- current_leaf, // Left
- new_leaf // Right
- );
- if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
- std::cerr << "[DEBUG] Replacing root node" << std::endl;
- root_node_ = split;
- } else {
- std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
- replace_window_node(root_node_, active_window_, split);
- }
-
- active_window_ = new_window;
- emit_event(EditorEvent::WindowLayoutChanged);
- std::cerr << "[DEBUG] split_vertically() completed" << std::endl;
- }
- // Recursive parent finder
- LayoutNode* find_parent_of_node(LayoutNode* current, LayoutNode* child_target) {
- if (current->type == LayoutNode::Type::Leaf) return nullptr;
-
- if (current->child1.get() == child_target || current->child2.get() == child_target) {
- return current;
- }
-
- auto left = find_parent_of_node(current->child1.get(), child_target);
- if (left) return left;
-
- return find_parent_of_node(current->child2.get(), child_target);
- }
- // Recursive leaf finder
- LayoutNode* find_node_with_window(LayoutNode* current, std::shared_ptr<Window> target) {
- if (current->type == LayoutNode::Type::Leaf) {
- return (current->window == target) ? current : nullptr;
- }
- auto left = find_node_with_window(current->child1.get(), target);
- if (left) return left;
- return find_node_with_window(current->child2.get(), target);
- }
- void EditorCore::close_active_window() {
- // Cannot close last window
- if (root_node_->type == LayoutNode::Type::Leaf) {
- return;
- }
-
- // Find the node containing active_window
- LayoutNode* target_node = find_node_with_window(root_node_.get(), active_window_);
- if (!target_node) return; // Should not happen
-
- LayoutNode* parent = find_parent_of_node(root_node_.get(), target_node);
- if (!parent) return; // Should not happen if not root
-
- // Identify sibling
- std::shared_ptr<LayoutNode> sibling;
- if (parent->child1.get() == target_node) {
- sibling = parent->child2;
- } else {
- sibling = parent->child1;
- }
-
- // Replace parent with sibling
- // If parent is root, root becomes sibling
- if (parent == root_node_.get()) {
- root_node_ = sibling;
- } else {
- // Find grandparent
- LayoutNode* grandparent = find_parent_of_node(root_node_.get(), parent);
- if (grandparent->child1.get() == parent) {
- grandparent->child1 = sibling;
- } else {
- grandparent->child2 = sibling;
- }
- }
-
- // Focus a new window (first leaf in sibling)
- std::vector<std::shared_ptr<Window>> windows;
- collect_windows(sibling.get(), windows);
- if (!windows.empty()) {
- active_window_ = windows[0];
- }
-
- emit_event(EditorEvent::WindowLayoutChanged);
- }
- void EditorCore::collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) {
- if (node->type == LayoutNode::Type::Leaf) {
- windows.push_back(node->window);
- } else {
- collect_windows(node->child1.get(), windows);
- collect_windows(node->child2.get(), windows);
- }
- }
- void EditorCore::next_window() {
- std::vector<std::shared_ptr<Window>> windows;
- collect_windows(root_node_.get(), windows);
-
- if (windows.size() <= 1) return;
-
- auto it = std::find(windows.begin(), windows.end(), active_window_);
- if (it != windows.end()) {
- auto next = it + 1;
- if (next == windows.end()) {
- active_window_ = windows[0];
- } else {
- active_window_ = *next;
- }
- emit_event(EditorEvent::WindowFocused); // Emit new event
- }
- }
- // === Cursor Proxies ===
- Position EditorCore::cursor() const noexcept {
- return active_window_->cursor();
- }
- void EditorCore::set_cursor(Position pos) {
- active_window_->set_cursor(pos);
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_up() {
- active_window_->move_up();
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_down() {
- active_window_->move_down();
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_left() {
- active_window_->move_left();
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_right() {
- active_window_->move_right();
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_to_line_start() {
- active_window_->move_to_line_start();
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_to_line_end() {
- 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(active_window_->cursor());
- active_window_->set_cursor(new_pos);
- emit_event(EditorEvent::CursorMoved);
- }
- void EditorCore::move_backward_word() {
- auto new_pos = calculate_backward_word_pos(active_window_->cursor());
- active_window_->set_cursor(new_pos);
- emit_event(EditorEvent::CursorMoved);
- }
- Position EditorCore::calculate_forward_word_pos(Position start_pos) {
- auto& buf = active_window_->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) {
- auto& buf = active_window_->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 = active_window_->viewport();
- auto cursor = 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 = active_window_->buffer();
- const auto& line = buf.line(cursor.line);
- cursor.column = std::min(cursor.column, line.size());
- active_window_->set_cursor(cursor);
- active_window_->adjust_scroll();
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- }
- void EditorCore::page_down() {
- auto& viewport = active_window_->viewport();
- auto cursor = active_window_->cursor();
- auto& buf = active_window_->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());
- active_window_->set_cursor(cursor);
- active_window_->adjust_scroll();
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- }
- void EditorCore::goto_beginning() {
- Position pos = {0, 0};
- active_window_->set_cursor(pos);
- active_window_->adjust_scroll();
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- }
- void EditorCore::goto_end() {
- auto& buf = active_window_->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};
- active_window_->set_cursor(pos);
- active_window_->adjust_scroll();
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- }
- void EditorCore::goto_line(size_t line) {
- auto& buf = active_window_->buffer();
- line = std::min(line, buf.line_count() - 1);
- Position pos = {line, 0};
- active_window_->set_cursor(pos);
- active_window_->adjust_scroll();
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::ViewportChanged);
- }
- // === Viewport Proxies ===
- const Viewport& EditorCore::viewport() const noexcept {
- return active_window_->viewport();
- }
- void EditorCore::set_viewport_size(int width, int height) {
- active_window_->set_viewport_size(width, height);
- emit_event(EditorEvent::ViewportChanged);
- }
- void EditorCore::adjust_scroll() {
- active_window_->adjust_scroll();
- emit_event(EditorEvent::ViewportChanged);
- }
- std::pair<size_t, size_t> EditorCore::visible_line_range() const {
- return active_window_->visible_line_range();
- }
- // === Undo/Redo ===
- bool EditorCore::undo() {
- auto& buf = active_window_->buffer();
- buf.save_undo_state(active_window_->cursor()); // Save state before
- Position new_cursor = active_window_->cursor();
- if (buf.undo(new_cursor)) {
- active_window_->set_cursor(new_cursor);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::BufferModified);
- return true;
- }
- return false;
- }
- bool EditorCore::redo() {
- auto& buf = active_window_->buffer();
- Position new_cursor = active_window_->cursor();
- if (buf.redo(new_cursor)) {
- active_window_->set_cursor(new_cursor);
- emit_event(EditorEvent::CursorMoved);
- emit_event(EditorEvent::BufferModified);
- return true;
- }
- return false;
- }
- bool EditorCore::can_undo() const {
- return active_window_->buffer().can_undo();
- }
- bool EditorCore::can_redo() const {
- return active_window_->buffer().can_redo();
- }
- // === Kill Ring ===
- void EditorCore::kill_line() {
- auto& buf = active_window_->buffer();
- auto cursor = active_window_->cursor();
- const auto& line = buf.line(cursor.line);
- // If at end of line, kill the newline (join with next line)
- if (cursor.column >= line.size()) {
- if (cursor.line < buf.line_count() - 1) {
- // Kill the newline character
- Position start = {cursor.line, line.size()};
- Position end = {cursor.line + 1, 0};
- Range range = {start, end};
- std::string killed_text = "\n";
- kill_ring_.push(killed_text);
- buf.erase(range);
- emit_event(EditorEvent::BufferModified);
- std::cerr << "[DEBUG] Killed newline at end of line " << cursor.line << std::endl;
- }
- return;
- }
- // Kill from cursor to end of line
- Position start = cursor;
- Position end = {cursor.line, line.size()};
- Range range = {start, end};
- std::string killed_text = buf.get_text_in_range(range);
- if (!killed_text.empty()) {
- kill_ring_.push(killed_text);
- buf.erase(range);
- emit_event(EditorEvent::BufferModified);
- std::cerr << "[DEBUG] Killed text: '" << killed_text << "'" << std::endl;
- }
- }
- void EditorCore::kill_region() {
- auto& buf = active_window_->buffer();
- auto cursor = 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_.push(killed_text);
- buf.erase(region.value());
- buf.deactivate_mark();
- // Move cursor to start of killed region
- active_window_->set_cursor(region.value().start);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- std::cerr << "[DEBUG] Killed region: '" << killed_text << "'" << std::endl;
- }
- }
- void EditorCore::kill_word() {
- auto cursor = active_window_->cursor();
- auto end_pos = calculate_forward_word_pos(cursor);
-
- if (cursor == end_pos) return;
-
- Range range = {cursor, end_pos};
- auto& buf = active_window_->buffer();
- std::string text = buf.get_text_in_range(range);
-
- if (!text.empty()) {
- kill_ring_.push(text);
- buf.erase(range);
- emit_event(EditorEvent::BufferModified);
- std::cerr << "[DEBUG] Killed word: '" << text << "'" << std::endl;
- }
- }
- void EditorCore::backward_kill_word() {
- auto cursor = active_window_->cursor();
- auto start_pos = calculate_backward_word_pos(cursor);
-
- if (cursor == start_pos) return;
-
- Range range = {start_pos, cursor};
- auto& buf = active_window_->buffer();
- std::string text = buf.get_text_in_range(range);
-
- if (!text.empty()) {
- kill_ring_.push(text);
- buf.erase(range);
- active_window_->set_cursor(start_pos);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- std::cerr << "[DEBUG] Backward killed word: '" << text << "'" << std::endl;
- }
- }
- void EditorCore::copy_region_as_kill() {
- auto& buf = active_window_->buffer();
- auto cursor = 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_.push(copied_text);
- buf.deactivate_mark();
- set_message("Region copied");
- std::cerr << "[DEBUG] Copied region: '" << copied_text << "'" << std::endl;
- }
- }
- void EditorCore::yank() {
- if (kill_ring_.empty()) {
- set_message("Kill ring is empty");
- return;
- }
- std::string text = kill_ring_.current();
- if (text.empty()) {
- return;
- }
- auto& buf = active_window_->buffer();
- auto cursor = active_window_->cursor();
- // Save yank start position
- last_yank_start_ = cursor;
- // Insert the text
- buf.insert(cursor, text);
- // Calculate new cursor position after insertion
- Position new_cursor = cursor;
- // Count newlines in text
- 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();
- }
- last_yank_end_ = new_cursor;
- active_window_->set_cursor(new_cursor);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- std::cerr << "[DEBUG] Yanked: '" << text << "'" << std::endl;
- }
- void EditorCore::yank_pop() {
- if (kill_ring_.empty()) {
- set_message("Kill ring is empty");
- return;
- }
- if (!last_yank_start_.has_value() || !last_yank_end_.has_value()) {
- set_message("Previous command was not a yank");
- return;
- }
- // Delete the previously yanked text
- auto& buf = active_window_->buffer();
- Range yank_range = {last_yank_start_.value(), last_yank_end_.value()};
- buf.erase(yank_range);
- // Get previous entry in kill ring
- std::string text = kill_ring_.previous();
- // Restore cursor to yank start
- auto cursor = last_yank_start_.value();
- 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();
- }
- last_yank_end_ = new_cursor;
- active_window_->set_cursor(new_cursor);
- emit_event(EditorEvent::BufferModified);
- emit_event(EditorEvent::CursorMoved);
- std::cerr << "[DEBUG] Yank-pop: '" << text << "'" << std::endl;
- }
- // === Registers ===
- 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;
- }
-
- registers_[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;
- }
-
- auto it = registers_.find(register_name);
- if (it == registers_.end()) {
- set_message(std::string("Register ") + register_name + " is empty");
- return false;
- }
-
- auto& buf = buffer();
- Position cursor = active_window_->cursor();
- buf.insert(cursor, it->second);
-
- // Move cursor to end of inserted text
- size_t newline_count = std::count(it->second.begin(), it->second.end(), '\n');
- if (newline_count > 0) {
- cursor.line += newline_count;
- size_t last_newline = it->second.rfind('\n');
- cursor.column = (last_newline != std::string::npos) ? (it->second.size() - last_newline - 1) : 0;
- } else {
- cursor.column += it->second.size();
- }
-
- 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();
- Position cursor = 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 ===
- void EditorCore::start_kbd_macro() {
- if (recording_macro_) {
- set_message("Already recording macro");
- return;
- }
-
- recording_macro_ = true;
- current_macro_.clear();
- set_message("Recording macro...");
- }
- void EditorCore::end_kbd_macro_or_call() {
- if (recording_macro_) {
- // End recording
- recording_macro_ = false;
- last_macro_ = current_macro_;
- current_macro_.clear();
-
- if (last_macro_.empty()) {
- set_message("Macro recorded (empty)");
- } else {
- set_message(std::string("Macro recorded (") + std::to_string(last_macro_.size()) + " keys)");
- }
- } else {
- // Call last macro
- if (last_macro_.empty()) {
- set_message("No macro recorded");
- return;
- }
-
- set_message(std::string("Executing macro (") + std::to_string(last_macro_.size()) + " keys)");
-
- // This is a simplified approach - in a real implementation, you'd need
- // to replay the actual commands through the key binding system
- // For now, we just show that the macro system is set up
- for (const auto& key : last_macro_) {
- // TODO: Execute the actual key binding for this key
- // This would require access to the LuaApi from EditorCore
- std::cerr << "[MACRO] Would execute key: " << key << std::endl;
- }
- }
- }
- void EditorCore::record_key_sequence(const std::string& key_sequence) {
- if (recording_macro_) {
- current_macro_.push_back(key_sequence);
- }
- }
- // === Rectangles ===
- void EditorCore::kill_rectangle() {
- auto& buf = buffer();
- Position cursor = active_window_->cursor();
-
- auto region = buf.get_region(cursor);
- if (!region) {
- set_message("No active region");
- return;
- }
-
- // Ensure start is top-left, end is bottom-right
- Position start = region->start;
- Position end = region->end;
-
- if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
- std::swap(start, end);
- }
-
- rectangle_kill_ring_.clear();
-
- // Extract rectangle line by line
- for (size_t line = start.line; line <= end.line; ++line) {
- if (line >= buf.line_count()) break;
-
- std::string line_text = buf.line(line);
- size_t start_col = (line == start.line) ? start.column : std::min(start.column, end.column);
- size_t end_col = (line == end.line) ? end.column : std::max(start.column, end.column);
-
- // Ensure we don't go beyond the line length
- start_col = std::min(start_col, line_text.size());
- end_col = std::min(end_col, line_text.size());
-
- if (start_col < end_col) {
- rectangle_kill_ring_.push_back(line_text.substr(start_col, end_col - start_col));
- } else {
- rectangle_kill_ring_.push_back("");
- }
- }
-
- // Now delete the rectangle content (from bottom to top to preserve line indices)
- for (int line = static_cast<int>(end.line); line >= static_cast<int>(start.line); --line) {
- if (line >= static_cast<int>(buf.line_count())) continue;
-
- std::string line_text = buf.line(line);
- size_t start_col = (line == static_cast<int>(start.line)) ? start.column : std::min(start.column, end.column);
- size_t end_col = (line == static_cast<int>(end.line)) ? end.column : std::max(start.column, end.column);
-
- start_col = std::min(start_col, line_text.size());
- end_col = std::min(end_col, line_text.size());
-
- if (start_col < end_col) {
- Range del_range{Position(line, start_col), Position(line, end_col)};
- buf.erase(del_range);
- }
- }
-
- buf.deactivate_mark();
- emit_event(EditorEvent::BufferModified);
-
- set_message(std::string("Rectangle killed (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
- }
- void EditorCore::yank_rectangle() {
- if (rectangle_kill_ring_.empty()) {
- set_message("No rectangle in kill ring");
- return;
- }
-
- auto& buf = buffer();
- Position cursor = active_window_->cursor();
-
- // Insert rectangle starting at cursor position
- for (size_t i = 0; i < rectangle_kill_ring_.size(); ++i) {
- Position insert_pos{cursor.line + i, cursor.column};
-
- // Ensure we have enough lines
- while (buf.line_count() <= insert_pos.line) {
- buf.insert_newline(Position{buf.line_count() - 1, buf.line(buf.line_count() - 1).size()});
- }
-
- // Pad line with spaces if necessary
- std::string current_line = buf.line(insert_pos.line);
- if (current_line.size() < insert_pos.column) {
- std::string padding(insert_pos.column - current_line.size(), ' ');
- buf.insert(Position{insert_pos.line, current_line.size()}, padding);
- }
-
- buf.insert(insert_pos, rectangle_kill_ring_[i]);
- }
-
- emit_event(EditorEvent::BufferModified);
- set_message(std::string("Rectangle yanked (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
- }
- void EditorCore::string_rectangle(const std::string& text) {
- auto& buf = buffer();
- Position cursor = active_window_->cursor();
-
- auto region = buf.get_region(cursor);
- if (!region) {
- set_message("No active region");
- return;
- }
-
- Position start = region->start;
- Position end = region->end;
-
- if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
- std::swap(start, end);
- }
-
- // Fill rectangle with the given text
- for (size_t line = start.line; line <= end.line; ++line) {
- if (line >= buf.line_count()) break;
-
- std::string line_text = buf.line(line);
- size_t start_col = std::min(start.column, end.column);
- size_t end_col = std::max(start.column, end.column);
-
- // Pad line if necessary
- if (line_text.size() < end_col) {
- std::string padding(end_col - line_text.size(), ' ');
- buf.insert(Position{line, line_text.size()}, padding);
- line_text = buf.line(line); // Refresh
- }
-
- // Replace rectangle content with text
- if (start_col < line_text.size()) {
- end_col = std::min(end_col, line_text.size());
- if (start_col < end_col) {
- Range replace_range{Position(line, start_col), Position(line, end_col)};
- std::string fill_text = text;
- if (fill_text.size() > end_col - start_col) {
- fill_text = fill_text.substr(0, end_col - start_col);
- } else if (fill_text.size() < end_col - start_col) {
- fill_text += std::string(end_col - start_col - fill_text.size(), ' ');
- }
- buf.replace(replace_range, fill_text);
- }
- }
- }
-
- buf.deactivate_mark();
- emit_event(EditorEvent::BufferModified);
- set_message("Rectangle filled");
- }
- // === Private ===
- void EditorCore::emit_event(EditorEvent event) {
- for (const auto& callback : event_callbacks_) {
- callback(event);
- }
- }
- } // namespace lumacs
|