|
|
@@ -4,26 +4,31 @@
|
|
|
#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 <algorithm>
|
|
|
#include <iostream>
|
|
|
|
|
|
namespace lumacs {
|
|
|
|
|
|
EditorCore::EditorCore() :
|
|
|
- buffers_(),
|
|
|
- root_node_(),
|
|
|
- active_window_(),
|
|
|
+ // root_node_(), // Managed by WindowManager
|
|
|
+ // active_window_(), // Managed by WindowManager
|
|
|
last_message_(),
|
|
|
message_clear_time_(),
|
|
|
event_callbacks_(),
|
|
|
- kill_ring_(),
|
|
|
+ // kill_ring_(), // Managed by KillRingManager
|
|
|
last_yank_start_(),
|
|
|
last_yank_end_(),
|
|
|
- registers_(),
|
|
|
- current_macro_(),
|
|
|
- last_macro_(),
|
|
|
- recording_macro_(false),
|
|
|
- rectangle_kill_ring_(),
|
|
|
+ // registers_(), // Managed by RegisterManager
|
|
|
+ // current_macro_(), // Managed by MacroManager
|
|
|
+ // last_macro_(), // Managed by MacroManager
|
|
|
+ // recording_macro_(false), // Managed by MacroManager
|
|
|
+ // rectangle_kill_ring_(), // Managed by RectangleManager
|
|
|
theme_manager_(),
|
|
|
config_(),
|
|
|
// Subsystem initializations - order matters for dependencies
|
|
|
@@ -32,18 +37,20 @@ EditorCore::EditorCore() :
|
|
|
minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)), // MinibufferManager is a dependency for CommandSystem
|
|
|
command_system_(std::make_unique<CommandSystem>(*this, *minibuffer_manager_)), // CommandSystem is a dependency for KeyBindingManager
|
|
|
keybinding_manager_(std::make_unique<KeyBindingManager>(*command_system_)), // KeyBindingManager needs CommandSystem
|
|
|
- plugin_manager_(std::make_unique<PluginManager>(*this, *lua_api_)) // PluginManager needs EditorCore and LuaApi
|
|
|
+ plugin_manager_(std::make_unique<PluginManager>(*this, *lua_api_)), // PluginManager needs EditorCore and LuaApi
|
|
|
+ buffer_manager_(std::make_unique<BufferManager>(*this)), // BufferManager needs EditorCore
|
|
|
+ window_manager_(std::make_unique<WindowManager>(*this)), // WindowManager needs EditorCore and BufferManager (implicitly via EditorCore)
|
|
|
+ kill_ring_manager_(std::make_unique<KillRingManager>()), // KillRingManager is simple, no dependencies here
|
|
|
+ register_manager_(std::make_unique<RegisterManager>()), // RegisterManager is simple
|
|
|
+ macro_manager_(std::make_unique<MacroManager>(*this)), // MacroManager needs EditorCore
|
|
|
+ rectangle_manager_(std::make_unique<RectangleManager>(*this)) // RectangleManager needs EditorCore
|
|
|
{
|
|
|
// 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_);
|
|
|
+ // Initial buffer and window creation is now managed by BufferManager and WindowManager.
|
|
|
+ // The WindowManager constructor will create the initial window and assign it a buffer
|
|
|
+ // using the BufferManager.
|
|
|
|
|
|
// Initialize themes
|
|
|
theme_manager_.create_default_themes();
|
|
|
@@ -55,393 +62,83 @@ EditorCore::EditorCore() :
|
|
|
|
|
|
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);
|
|
|
+// === Window Management (Delegated to WindowManager) ===
|
|
|
|
|
|
- emit_event(EditorEvent::BufferModified);
|
|
|
- emit_event(EditorEvent::CursorMoved);
|
|
|
- emit_event(EditorEvent::ViewportChanged);
|
|
|
- return true;
|
|
|
-}
|
|
|
+// These are still in EditorCore because they are part of the ICommandTarget interface
|
|
|
+// but their implementation delegates to WindowManager.
|
|
|
|
|
|
-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);
|
|
|
- }
|
|
|
+/*
|
|
|
+// Helper to recursively replace a window in the tree (Moved to WindowManager)
|
|
|
+bool replace_window_node(...) { ... }
|
|
|
|
|
|
- return info;
|
|
|
-}
|
|
|
+// Recursive parent finder (Moved to WindowManager)
|
|
|
+LayoutNode* find_parent_of_node(...) { ... }
|
|
|
|
|
|
-// === 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;
|
|
|
-}
|
|
|
+// Recursive leaf finder (Moved to WindowManager)
|
|
|
+LayoutNode* find_node_with_window(...) { ... }
|
|
|
+*/
|
|
|
|
|
|
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;
|
|
|
+ window_manager_->split_horizontally();
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
+ window_manager_->split_vertically();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
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);
|
|
|
+ window_manager_->close_active_window();
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
- }
|
|
|
+ window_manager_->collect_windows(node, windows);
|
|
|
}
|
|
|
|
|
|
void EditorCore::next_window() {
|
|
|
- // Cycle to the next window in the window tree
|
|
|
- // Note: Focus jumping bug was fixed in GTK frontend by caching active_window during redraws
|
|
|
- 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);
|
|
|
- }
|
|
|
+ window_manager_->next_window();
|
|
|
}
|
|
|
|
|
|
void EditorCore::next_window_safe() {
|
|
|
- // Deprecated: Use next_window() instead. Kept for backwards compatibility.
|
|
|
next_window();
|
|
|
}
|
|
|
|
|
|
bool EditorCore::set_active_window(std::shared_ptr<Window> window) {
|
|
|
- if (!window) return false;
|
|
|
-
|
|
|
- // Verify that the window exists in the current window tree
|
|
|
- std::vector<std::shared_ptr<Window>> windows;
|
|
|
- collect_windows(root_node_.get(), windows);
|
|
|
-
|
|
|
- auto it = std::find(windows.begin(), windows.end(), window);
|
|
|
- if (it != windows.end()) {
|
|
|
- if (active_window_ != window) {
|
|
|
- active_window_ = window;
|
|
|
- emit_event(EditorEvent::WindowFocused);
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return false; // Window not found in tree
|
|
|
+ return window_manager_->set_active_window(window);
|
|
|
}
|
|
|
|
|
|
// === Cursor Proxies ===
|
|
|
|
|
|
Position EditorCore::cursor() const noexcept {
|
|
|
- return active_window_->cursor();
|
|
|
+ return active_window()->cursor(); // Uses delegated active_window()
|
|
|
}
|
|
|
|
|
|
void EditorCore::set_cursor(Position pos) {
|
|
|
- active_window_->set_cursor(pos);
|
|
|
+ active_window()->set_cursor(pos); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
}
|
|
|
|
|
|
void EditorCore::move_up() {
|
|
|
- active_window_->move_up();
|
|
|
+ active_window()->move_up();
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
}
|
|
|
void EditorCore::move_down() {
|
|
|
- active_window_->move_down();
|
|
|
+ active_window()->move_down();
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
}
|
|
|
void EditorCore::move_left() {
|
|
|
- active_window_->move_left();
|
|
|
+ active_window()->move_left();
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
}
|
|
|
void EditorCore::move_right() {
|
|
|
- active_window_->move_right();
|
|
|
+ active_window()->move_right();
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
}
|
|
|
void EditorCore::move_to_line_start() {
|
|
|
- active_window_->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();
|
|
|
+ active_window()->move_to_line_end();
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
}
|
|
|
|
|
|
@@ -452,19 +149,20 @@ static bool is_word_char(char c) {
|
|
|
}
|
|
|
|
|
|
void EditorCore::move_forward_word() {
|
|
|
- auto new_pos = calculate_forward_word_pos(active_window_->cursor());
|
|
|
- active_window_->set_cursor(new_pos);
|
|
|
+ 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);
|
|
|
+ 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();
|
|
|
+ // Delegates to active buffer
|
|
|
+ auto& buf = buffer();
|
|
|
auto cursor = start_pos;
|
|
|
|
|
|
// Check if we are at the end of buffer
|
|
|
@@ -505,7 +203,8 @@ Position EditorCore::calculate_forward_word_pos(Position start_pos) {
|
|
|
}
|
|
|
|
|
|
Position EditorCore::calculate_backward_word_pos(Position start_pos) {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ // Delegates to active buffer
|
|
|
+ auto& buf = buffer();
|
|
|
auto cursor = start_pos;
|
|
|
|
|
|
// Skip whitespace and punctuation backwards
|
|
|
@@ -538,8 +237,8 @@ Position EditorCore::calculate_backward_word_pos(Position start_pos) {
|
|
|
}
|
|
|
|
|
|
void EditorCore::page_up() {
|
|
|
- auto& viewport = active_window_->viewport();
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto& viewport = active_window()->viewport(); // Uses delegated active_window()
|
|
|
+ auto cursor = active_window()->cursor(); // Uses delegated active_window()
|
|
|
|
|
|
// Move up by viewport height (minus 2 for overlap)
|
|
|
int page_size = std::max(1, viewport.height - 2);
|
|
|
@@ -551,20 +250,20 @@ void EditorCore::page_up() {
|
|
|
}
|
|
|
|
|
|
// Keep column position if possible
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ auto& buf = 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();
|
|
|
+ active_window()->set_cursor(cursor); // Uses delegated active_window()
|
|
|
+ active_window()->adjust_scroll(); // Uses delegated active_window()
|
|
|
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();
|
|
|
+ auto& viewport = active_window()->viewport(); // Uses delegated active_window()
|
|
|
+ auto cursor = active_window()->cursor(); // Uses delegated active_window()
|
|
|
+ auto& buf = buffer();
|
|
|
|
|
|
// Move down by viewport height (minus 2 for overlap)
|
|
|
int page_size = std::max(1, viewport.height - 2);
|
|
|
@@ -575,72 +274,72 @@ void EditorCore::page_down() {
|
|
|
const auto& line = buf.line(cursor.line);
|
|
|
cursor.column = std::min(cursor.column, line.size());
|
|
|
|
|
|
- active_window_->set_cursor(cursor);
|
|
|
- active_window_->adjust_scroll();
|
|
|
+ active_window()->set_cursor(cursor); // Uses delegated active_window()
|
|
|
+ active_window()->adjust_scroll(); // Uses delegated active_window()
|
|
|
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();
|
|
|
+ active_window()->set_cursor(pos); // Uses delegated active_window()
|
|
|
+ active_window()->adjust_scroll(); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
emit_event(EditorEvent::ViewportChanged);
|
|
|
}
|
|
|
|
|
|
void EditorCore::goto_end() {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ auto& buf = 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();
|
|
|
+ active_window()->set_cursor(pos); // Uses delegated active_window()
|
|
|
+ active_window()->adjust_scroll(); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
emit_event(EditorEvent::ViewportChanged);
|
|
|
}
|
|
|
|
|
|
void EditorCore::goto_line(size_t line) {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ auto& buf = buffer();
|
|
|
line = std::min(line, buf.line_count() > 0 ? buf.line_count() - 1 : 0); // Clamp to max line index
|
|
|
Position pos = {line, 0};
|
|
|
|
|
|
- active_window_->set_cursor(pos);
|
|
|
- active_window_->adjust_scroll();
|
|
|
+ active_window()->set_cursor(pos); // Uses delegated active_window()
|
|
|
+ active_window()->adjust_scroll(); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
emit_event(EditorEvent::ViewportChanged);
|
|
|
}
|
|
|
|
|
|
-// === Viewport Proxies ===
|
|
|
+// === Viewport Management (Proxies to active window) ===
|
|
|
|
|
|
const Viewport& EditorCore::viewport() const noexcept {
|
|
|
- return active_window_->viewport();
|
|
|
+ return active_window()->viewport(); // Uses delegated active_window()
|
|
|
}
|
|
|
|
|
|
void EditorCore::set_viewport_size(int width, int height) {
|
|
|
- active_window_->set_viewport_size(width, height);
|
|
|
+ active_window()->set_viewport_size(width, height); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::ViewportChanged);
|
|
|
}
|
|
|
|
|
|
void EditorCore::adjust_scroll() {
|
|
|
- active_window_->adjust_scroll();
|
|
|
+ active_window()->adjust_scroll(); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::ViewportChanged);
|
|
|
}
|
|
|
|
|
|
std::pair<size_t, size_t> EditorCore::visible_line_range() const {
|
|
|
- return active_window_->visible_line_range();
|
|
|
+ return active_window()->visible_line_range(); // Uses delegated active_window()
|
|
|
}
|
|
|
|
|
|
// === Undo/Redo ===
|
|
|
|
|
|
bool EditorCore::undo() {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
- buf.save_undo_state(active_window_->cursor()); // Save state before
|
|
|
+ auto& buf = buffer();
|
|
|
+ buf.save_undo_state(active_window()->cursor()); // Uses delegated active_window()
|
|
|
|
|
|
- Position new_cursor = active_window_->cursor();
|
|
|
+ Position new_cursor = active_window()->cursor(); // Uses delegated active_window()
|
|
|
if (buf.undo(new_cursor)) {
|
|
|
- active_window_->set_cursor(new_cursor);
|
|
|
+ active_window()->set_cursor(new_cursor); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
return true;
|
|
|
@@ -649,10 +348,10 @@ bool EditorCore::undo() {
|
|
|
}
|
|
|
|
|
|
bool EditorCore::redo() {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
- Position new_cursor = active_window_->cursor();
|
|
|
+ auto& buf = buffer();
|
|
|
+ Position new_cursor = active_window()->cursor(); // Uses delegated active_window()
|
|
|
if (buf.redo(new_cursor)) {
|
|
|
- active_window_->set_cursor(new_cursor);
|
|
|
+ active_window()->set_cursor(new_cursor); // Uses delegated active_window()
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
return true;
|
|
|
@@ -661,18 +360,18 @@ bool EditorCore::redo() {
|
|
|
}
|
|
|
|
|
|
bool EditorCore::can_undo() const {
|
|
|
- return active_window_->buffer().can_undo();
|
|
|
+ return buffer().can_undo();
|
|
|
}
|
|
|
|
|
|
bool EditorCore::can_redo() const {
|
|
|
- return active_window_->buffer().can_redo();
|
|
|
+ return buffer().can_redo();
|
|
|
}
|
|
|
|
|
|
-// === Kill Ring ===
|
|
|
+// === Kill Ring (Delegated to KillRingManager) ===
|
|
|
|
|
|
void EditorCore::kill_line() {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto& buf = 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)
|
|
|
@@ -683,8 +382,7 @@ void EditorCore::kill_line() {
|
|
|
Position end = {cursor.line + 1, 0};
|
|
|
Range range = {start, end};
|
|
|
|
|
|
- std::string killed_text = "\n";
|
|
|
- kill_ring_.push(killed_text);
|
|
|
+ kill_ring_manager_->push("\n"); // Delegate to manager
|
|
|
|
|
|
buf.erase(range);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
@@ -701,7 +399,7 @@ void EditorCore::kill_line() {
|
|
|
|
|
|
std::string killed_text = buf.get_text_in_range(range);
|
|
|
if (!killed_text.empty()) {
|
|
|
- kill_ring_.push(killed_text);
|
|
|
+ kill_ring_manager_->push(killed_text); // Delegate to manager
|
|
|
buf.erase(range);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
|
|
|
@@ -710,8 +408,8 @@ void EditorCore::kill_line() {
|
|
|
}
|
|
|
|
|
|
void EditorCore::kill_region() {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto& buf = buffer();
|
|
|
+ auto cursor = active_window()->cursor();
|
|
|
|
|
|
auto region = buf.get_region(cursor);
|
|
|
if (!region.has_value()) {
|
|
|
@@ -721,11 +419,11 @@ void EditorCore::kill_region() {
|
|
|
|
|
|
std::string killed_text = buf.get_text_in_range(region.value());
|
|
|
if (!killed_text.empty()) {
|
|
|
- kill_ring_.push(killed_text);
|
|
|
+ kill_ring_manager_->push(killed_text); // Delegate to manager
|
|
|
buf.deactivate_mark();
|
|
|
|
|
|
// Move cursor to start of killed region
|
|
|
- active_window_->set_cursor(region.value().start);
|
|
|
+ active_window()->set_cursor(region.value().start);
|
|
|
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
@@ -735,17 +433,17 @@ void EditorCore::kill_region() {
|
|
|
}
|
|
|
|
|
|
void EditorCore::kill_word() {
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto cursor = active_window()->cursor();
|
|
|
auto end_pos = calculate_forward_word_pos(cursor);
|
|
|
|
|
|
- if (cursor == end_pos) return;
|
|
|
+ if (cursor == end_pos) return;
|
|
|
|
|
|
Range range = {cursor, end_pos};
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ auto& buf = buffer();
|
|
|
std::string text = buf.get_text_in_range(range);
|
|
|
|
|
|
if (!text.empty()) {
|
|
|
- kill_ring_.push(text);
|
|
|
+ kill_ring_manager_->push(text); // Delegate to manager
|
|
|
buf.erase(range);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
std::cerr << "[DEBUG] Killed word: '" << text << "'" << std::endl;
|
|
|
@@ -753,19 +451,19 @@ void EditorCore::kill_word() {
|
|
|
}
|
|
|
|
|
|
void EditorCore::backward_kill_word() {
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto cursor = active_window()->cursor();
|
|
|
auto start_pos = calculate_backward_word_pos(cursor);
|
|
|
|
|
|
- if (cursor == start_pos) return;
|
|
|
+ if (cursor == start_pos) return;
|
|
|
|
|
|
Range range = {start_pos, cursor};
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ auto& buf = buffer();
|
|
|
std::string text = buf.get_text_in_range(range);
|
|
|
|
|
|
if (!text.empty()) {
|
|
|
- kill_ring_.push(text);
|
|
|
+ kill_ring_manager_->push(text); // Delegate to manager
|
|
|
buf.erase(range);
|
|
|
- active_window_->set_cursor(start_pos);
|
|
|
+ active_window()->set_cursor(start_pos);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
std::cerr << "[DEBUG] Backward killed word: '" << text << "'" << std::endl;
|
|
|
@@ -774,8 +472,8 @@ void EditorCore::backward_kill_word() {
|
|
|
|
|
|
|
|
|
void EditorCore::copy_region_as_kill() {
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto& buf = buffer();
|
|
|
+ auto cursor = active_window()->cursor();
|
|
|
|
|
|
auto region = buf.get_region(cursor);
|
|
|
if (!region.has_value()) {
|
|
|
@@ -785,7 +483,7 @@ void EditorCore::copy_region_as_kill() {
|
|
|
|
|
|
std::string copied_text = buf.get_text_in_range(region.value());
|
|
|
if (!copied_text.empty()) {
|
|
|
- kill_ring_.push(copied_text);
|
|
|
+ kill_ring_manager_->push(copied_text); // Delegate to manager
|
|
|
buf.deactivate_mark();
|
|
|
|
|
|
set_message("Region copied");
|
|
|
@@ -794,18 +492,18 @@ void EditorCore::copy_region_as_kill() {
|
|
|
}
|
|
|
|
|
|
void EditorCore::yank() {
|
|
|
- if (kill_ring_.empty()) {
|
|
|
+ if (kill_ring_manager_->empty()) { // Delegate to manager
|
|
|
set_message("Kill ring is empty");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- std::string text = kill_ring_.current();
|
|
|
+ std::string text = kill_ring_manager_->current(); // Delegate to manager
|
|
|
if (text.empty()) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
- auto cursor = active_window_->cursor();
|
|
|
+ auto& buf = buffer();
|
|
|
+ auto cursor = active_window()->cursor();
|
|
|
|
|
|
// Save yank start position
|
|
|
last_yank_start_ = cursor;
|
|
|
@@ -830,7 +528,7 @@ void EditorCore::yank() {
|
|
|
}
|
|
|
|
|
|
last_yank_end_ = new_cursor;
|
|
|
- active_window_->set_cursor(new_cursor);
|
|
|
+ active_window()->set_cursor(new_cursor);
|
|
|
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
@@ -839,7 +537,7 @@ void EditorCore::yank() {
|
|
|
}
|
|
|
|
|
|
void EditorCore::yank_pop() {
|
|
|
- if (kill_ring_.empty()) {
|
|
|
+ if (kill_ring_manager_->empty()) { // Delegate to manager
|
|
|
set_message("Kill ring is empty");
|
|
|
return;
|
|
|
}
|
|
|
@@ -850,16 +548,16 @@ void EditorCore::yank_pop() {
|
|
|
}
|
|
|
|
|
|
// Delete the previously yanked text
|
|
|
- auto& buf = active_window_->buffer();
|
|
|
+ auto& buf = 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();
|
|
|
+ std::string text = kill_ring_manager_->previous(); // Delegate to manager
|
|
|
|
|
|
// Restore cursor to yank start
|
|
|
auto cursor = last_yank_start_.value();
|
|
|
- active_window_->set_cursor(cursor);
|
|
|
+ active_window()->set_cursor(cursor);
|
|
|
|
|
|
// Insert new text
|
|
|
buf.insert(cursor, text);
|
|
|
@@ -877,7 +575,7 @@ void EditorCore::yank_pop() {
|
|
|
}
|
|
|
|
|
|
last_yank_end_ = new_cursor;
|
|
|
- active_window_->set_cursor(new_cursor);
|
|
|
+ active_window()->set_cursor(new_cursor);
|
|
|
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
@@ -885,7 +583,7 @@ void EditorCore::yank_pop() {
|
|
|
std::cerr << "[DEBUG] Yank-pop: '" << text << "'" << std::endl;
|
|
|
}
|
|
|
|
|
|
-// === Registers ===
|
|
|
+// === 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)
|
|
|
@@ -893,8 +591,7 @@ void EditorCore::copy_to_register(char register_name, const std::string& text) {
|
|
|
set_message("Invalid register name");
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- registers_[register_name] = text;
|
|
|
+ register_manager_->copy_to_register(register_name, text);
|
|
|
set_message(std::string("Saved text to register ") + register_name);
|
|
|
}
|
|
|
|
|
|
@@ -905,27 +602,28 @@ bool EditorCore::insert_register(char register_name) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- auto it = registers_.find(register_name);
|
|
|
- if (it == registers_.end()) {
|
|
|
+ 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();
|
|
|
- Position cursor = active_window_->cursor();
|
|
|
- buf.insert(cursor, it->second);
|
|
|
+ Position cursor = active_window()->cursor();
|
|
|
+ buf.insert(cursor, text);
|
|
|
|
|
|
// Move cursor to end of inserted text
|
|
|
- size_t newline_count = std::count(it->second.begin(), it->second.end(), '\n');
|
|
|
+ size_t newline_count = std::count(text.begin(), text.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;
|
|
|
+ size_t last_newline = text.rfind('\n');
|
|
|
+ cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0;
|
|
|
} else {
|
|
|
- cursor.column += it->second.size();
|
|
|
+ cursor.column += text.size();
|
|
|
}
|
|
|
|
|
|
- active_window_->set_cursor(cursor);
|
|
|
+ active_window()->set_cursor(cursor);
|
|
|
emit_event(EditorEvent::BufferModified);
|
|
|
emit_event(EditorEvent::CursorMoved);
|
|
|
|
|
|
@@ -935,7 +633,7 @@ bool EditorCore::insert_register(char register_name) {
|
|
|
|
|
|
void EditorCore::copy_region_to_register(char register_name) {
|
|
|
auto& buf = buffer();
|
|
|
- Position cursor = active_window_->cursor();
|
|
|
+ Position cursor = active_window()->cursor();
|
|
|
|
|
|
auto region = buf.get_region(cursor);
|
|
|
if (!region) {
|
|
|
@@ -951,204 +649,32 @@ bool EditorCore::yank_from_register(char register_name) {
|
|
|
return insert_register(register_name);
|
|
|
}
|
|
|
|
|
|
-// === Keyboard Macros ===
|
|
|
+// === Keyboard Macros (Delegated to MacroManager) ===
|
|
|
|
|
|
void EditorCore::start_kbd_macro() {
|
|
|
- if (recording_macro_) {
|
|
|
- set_message("Already recording macro");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- recording_macro_ = true;
|
|
|
- current_macro_.clear();
|
|
|
- set_message("Recording macro...");
|
|
|
+ macro_manager_->start_kbd_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;
|
|
|
- }
|
|
|
- }
|
|
|
+ macro_manager_->end_kbd_macro_or_call();
|
|
|
}
|
|
|
|
|
|
void EditorCore::record_key_sequence(const std::string& key_sequence) {
|
|
|
- if (recording_macro_) {
|
|
|
- current_macro_.push_back(key_sequence);
|
|
|
- }
|
|
|
+ macro_manager_->record_key_sequence(key_sequence);
|
|
|
}
|
|
|
|
|
|
-// === Rectangles ===
|
|
|
+// === Rectangles (Delegated to RectangleManager) ===
|
|
|
|
|
|
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)");
|
|
|
+ rectangle_manager_->kill_rectangle();
|
|
|
}
|
|
|
|
|
|
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)");
|
|
|
+ rectangle_manager_->yank_rectangle();
|
|
|
}
|
|
|
|
|
|
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");
|
|
|
+ rectangle_manager_->string_rectangle(text);
|
|
|
}
|
|
|
|
|
|
// === Private ===
|