editor_core.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. #include "lumacs/editor_core.hpp"
  2. #include "lumacs/lua_api.hpp" // Include LuaApi header
  3. #include "lumacs/command_system.hpp"
  4. #include "lumacs/completion_system.hpp" // Include CompletionSystem header
  5. #include "lumacs/minibuffer_manager.hpp" // Include MinibufferManager header
  6. #include <algorithm>
  7. #include <iostream>
  8. namespace lumacs {
  9. EditorCore::EditorCore() :
  10. buffers_(),
  11. root_node_(),
  12. active_window_(),
  13. last_message_(),
  14. message_clear_time_(),
  15. event_callbacks_(),
  16. kill_ring_(),
  17. last_yank_start_(),
  18. last_yank_end_(),
  19. registers_(),
  20. current_macro_(),
  21. last_macro_(),
  22. recording_macro_(false),
  23. rectangle_kill_ring_(),
  24. theme_manager_(),
  25. config_(),
  26. command_system_(std::make_unique<CommandSystem>(this)),
  27. keybinding_manager_(std::make_unique<KeyBindingManager>(command_system_.get())), // Initialized after command_system_
  28. lua_api_(std::make_unique<LuaApi>()),
  29. modeline_manager_(),
  30. completion_system_(std::make_unique<CompletionSystem>(*this)), // Initialized before minibuffer_manager_
  31. minibuffer_manager_(std::make_unique<MinibufferManager>(*this, *lua_api_, *completion_system_)) // Initialized after completion_system_
  32. {
  33. // LuaApi needs core_ pointer to be valid, so set it after constructor body starts
  34. lua_api_->set_core(*this);
  35. // Create initial buffer
  36. auto buffer = std::make_shared<Buffer>();
  37. buffers_.push_back(buffer);
  38. // Create initial window
  39. active_window_ = std::make_shared<Window>(buffer);
  40. root_node_ = std::make_shared<LayoutNode>(active_window_);
  41. // Initialize themes
  42. theme_manager_.create_default_themes();
  43. theme_manager_.set_active_theme("everforest-dark");
  44. // LuaApi will load init.lua, which relies on `editor` global being set via set_core().
  45. lua_api_->load_init_file();
  46. }
  47. EditorCore::~EditorCore() = default;
  48. // === Buffer Management ===
  49. const Buffer& EditorCore::buffer() const noexcept {
  50. return active_window_->buffer();
  51. }
  52. Buffer& EditorCore::buffer() noexcept {
  53. return active_window_->buffer();
  54. }
  55. bool EditorCore::load_file(const std::filesystem::path& path) {
  56. std::filesystem::path abs_path = std::filesystem::absolute(path);
  57. // Check if already open
  58. for (const auto& buf : buffers_) {
  59. if (buf->file_path() && std::filesystem::equivalent(*buf->file_path(), abs_path)) {
  60. active_window_->set_buffer(buf);
  61. emit_event(EditorEvent::BufferModified);
  62. emit_event(EditorEvent::CursorMoved);
  63. emit_event(EditorEvent::ViewportChanged);
  64. return true;
  65. }
  66. }
  67. auto new_buffer_opt = Buffer::from_file(abs_path);
  68. if (!new_buffer_opt) {
  69. return false;
  70. }
  71. auto new_buffer = std::make_shared<Buffer>(std::move(*new_buffer_opt));
  72. buffers_.push_back(new_buffer);
  73. active_window_->set_buffer(new_buffer);
  74. emit_event(EditorEvent::BufferModified);
  75. emit_event(EditorEvent::CursorMoved);
  76. emit_event(EditorEvent::ViewportChanged);
  77. return true;
  78. }
  79. void EditorCore::new_buffer(std::string name) {
  80. auto new_buffer = std::make_shared<Buffer>(std::move(name));
  81. buffers_.push_back(new_buffer);
  82. active_window_->set_buffer(new_buffer);
  83. emit_event(EditorEvent::BufferModified);
  84. emit_event(EditorEvent::CursorMoved);
  85. emit_event(EditorEvent::ViewportChanged);
  86. }
  87. std::vector<std::string> EditorCore::get_buffer_names() const {
  88. std::vector<std::string> names;
  89. names.reserve(buffers_.size());
  90. for (const auto& buf : buffers_) {
  91. names.push_back(buf->name());
  92. }
  93. return names;
  94. }
  95. std::shared_ptr<Buffer> EditorCore::get_buffer_by_name(const std::string& name) {
  96. for (const auto& buf : buffers_) {
  97. if (buf->name() == name) {
  98. return buf;
  99. }
  100. }
  101. return nullptr;
  102. }
  103. bool EditorCore::switch_buffer_in_window(const std::string& name) {
  104. auto buf = get_buffer_by_name(name);
  105. if (!buf) {
  106. return false;
  107. }
  108. active_window_->set_buffer(buf);
  109. emit_event(EditorEvent::BufferModified);
  110. emit_event(EditorEvent::CursorMoved);
  111. emit_event(EditorEvent::ViewportChanged);
  112. return true;
  113. }
  114. bool EditorCore::close_buffer(const std::string& name) {
  115. auto buf = get_buffer_by_name(name);
  116. if (!buf) {
  117. return false;
  118. }
  119. // Cannot close buffer if it's the only one
  120. if (buffers_.size() <= 1) {
  121. set_message("Cannot close last buffer");
  122. return false;
  123. }
  124. // Check if buffer is displayed in any window
  125. std::vector<std::shared_ptr<Window>> windows;
  126. collect_windows(root_node_.get(), windows);
  127. for (const auto& win : windows) {
  128. if (win->buffer_ptr() == buf) {
  129. // Buffer is displayed, switch to another buffer first
  130. // Find another buffer
  131. auto other_buf = buffers_.front() == buf ? *(++buffers_.begin()) : buffers_.front();
  132. win->set_buffer(other_buf);
  133. }
  134. }
  135. // Remove buffer from list
  136. buffers_.remove(buf);
  137. emit_event(EditorEvent::BufferModified);
  138. emit_event(EditorEvent::CursorMoved);
  139. emit_event(EditorEvent::ViewportChanged);
  140. return true;
  141. }
  142. std::vector<EditorCore::BufferInfo> EditorCore::get_all_buffer_info() const {
  143. std::vector<BufferInfo> info;
  144. info.reserve(buffers_.size());
  145. for (const auto& buf : buffers_) {
  146. BufferInfo bi;
  147. bi.name = buf->name();
  148. bi.size = buf->line_count();
  149. bi.modified = buf->is_modified();
  150. bi.mode = "fundamental-mode"; // TODO: Get actual mode from buffer
  151. bi.filepath = buf->file_path();
  152. info.push_back(bi);
  153. }
  154. return info;
  155. }
  156. // === Window Management ===
  157. // Helper to recursively replace a window in the tree
  158. bool replace_window_node(std::shared_ptr<LayoutNode> node,
  159. std::shared_ptr<Window> target,
  160. std::shared_ptr<LayoutNode> replacement) {
  161. if (node->type == LayoutNode::Type::Leaf) {
  162. return false;
  163. }
  164. // Check immediate children
  165. if (node->child1->type == LayoutNode::Type::Leaf) {
  166. if (node->child1->window == target) {
  167. node->child1 = replacement;
  168. return true;
  169. }
  170. }
  171. if (node->child2->type == LayoutNode::Type::Leaf) {
  172. if (node->child2->window == target) {
  173. node->child2 = replacement;
  174. return true;
  175. }
  176. }
  177. // Recurse
  178. bool found = false;
  179. if (node->child1->type != LayoutNode::Type::Leaf) {
  180. found = replace_window_node(node->child1, target, replacement);
  181. }
  182. if (!found && node->child2->type != LayoutNode::Type::Leaf) {
  183. found = replace_window_node(node->child2, target, replacement);
  184. }
  185. return found;
  186. }
  187. void EditorCore::split_horizontally() {
  188. std::cerr << "[DEBUG] split_horizontally() called" << std::endl;
  189. // New window sharing same buffer
  190. auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
  191. new_window->set_cursor(active_window_->cursor()); // Start at same position
  192. // Create split node
  193. auto new_leaf = std::make_shared<LayoutNode>(new_window);
  194. auto current_leaf = std::make_shared<LayoutNode>(active_window_);
  195. auto split = std::make_shared<LayoutNode>(
  196. LayoutNode::Type::HorizontalSplit,
  197. current_leaf, // Top
  198. new_leaf // Bottom
  199. );
  200. if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
  201. std::cerr << "[DEBUG] Replacing root node" << std::endl;
  202. root_node_ = split;
  203. } else {
  204. std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
  205. replace_window_node(root_node_, active_window_, split);
  206. }
  207. active_window_ = new_window; // Focus new window
  208. emit_event(EditorEvent::WindowLayoutChanged);
  209. std::cerr << "[DEBUG] split_horizontally() completed" << std::endl;
  210. }
  211. void EditorCore::split_vertically() {
  212. std::cerr << "[DEBUG] split_vertically() called" << std::endl;
  213. // New window sharing same buffer
  214. auto new_window = std::make_shared<Window>(active_window_->buffer_ptr());
  215. new_window->set_cursor(active_window_->cursor());
  216. // Create split node
  217. auto new_leaf = std::make_shared<LayoutNode>(new_window);
  218. auto current_leaf = std::make_shared<LayoutNode>(active_window_);
  219. // Vertical Split = Left/Right division
  220. auto split = std::make_shared<LayoutNode>(
  221. LayoutNode::Type::VerticalSplit,
  222. current_leaf, // Left
  223. new_leaf // Right
  224. );
  225. if (root_node_->type == LayoutNode::Type::Leaf && root_node_->window == active_window_) {
  226. std::cerr << "[DEBUG] Replacing root node" << std::endl;
  227. root_node_ = split;
  228. } else {
  229. std::cerr << "[DEBUG] Replacing window node in tree" << std::endl;
  230. replace_window_node(root_node_, active_window_, split);
  231. }
  232. active_window_ = new_window;
  233. emit_event(EditorEvent::WindowLayoutChanged);
  234. std::cerr << "[DEBUG] split_vertically() completed" << std::endl;
  235. }
  236. // Recursive parent finder
  237. LayoutNode* find_parent_of_node(LayoutNode* current, LayoutNode* child_target) {
  238. if (current->type == LayoutNode::Type::Leaf) return nullptr;
  239. if (current->child1.get() == child_target || current->child2.get() == child_target) {
  240. return current;
  241. }
  242. auto left = find_parent_of_node(current->child1.get(), child_target);
  243. if (left) return left;
  244. return find_parent_of_node(current->child2.get(), child_target);
  245. }
  246. // Recursive leaf finder
  247. LayoutNode* find_node_with_window(LayoutNode* current, std::shared_ptr<Window> target) {
  248. if (current->type == LayoutNode::Type::Leaf) {
  249. return (current->window == target) ? current : nullptr;
  250. }
  251. auto left = find_node_with_window(current->child1.get(), target);
  252. if (left) return left;
  253. return find_node_with_window(current->child2.get(), target);
  254. }
  255. void EditorCore::close_active_window() {
  256. // Cannot close last window
  257. if (root_node_->type == LayoutNode::Type::Leaf) {
  258. return;
  259. }
  260. // Find the node containing active_window
  261. LayoutNode* target_node = find_node_with_window(root_node_.get(), active_window_);
  262. if (!target_node) return; // Should not happen
  263. LayoutNode* parent = find_parent_of_node(root_node_.get(), target_node);
  264. if (!parent) return; // Should not happen if not root
  265. // Identify sibling
  266. std::shared_ptr<LayoutNode> sibling;
  267. if (parent->child1.get() == target_node) {
  268. sibling = parent->child2;
  269. } else {
  270. sibling = parent->child1;
  271. }
  272. // Replace parent with sibling
  273. // If parent is root, root becomes sibling
  274. if (parent == root_node_.get()) {
  275. root_node_ = sibling;
  276. } else {
  277. // Find grandparent
  278. LayoutNode* grandparent = find_parent_of_node(root_node_.get(), parent);
  279. if (grandparent->child1.get() == parent) {
  280. grandparent->child1 = sibling;
  281. } else {
  282. grandparent->child2 = sibling;
  283. }
  284. }
  285. // Focus a new window (first leaf in sibling)
  286. std::vector<std::shared_ptr<Window>> windows;
  287. collect_windows(sibling.get(), windows);
  288. if (!windows.empty()) {
  289. active_window_ = windows[0];
  290. }
  291. emit_event(EditorEvent::WindowLayoutChanged);
  292. }
  293. void EditorCore::collect_windows(LayoutNode* node, std::vector<std::shared_ptr<Window>>& windows) {
  294. if (node->type == LayoutNode::Type::Leaf) {
  295. windows.push_back(node->window);
  296. } else {
  297. collect_windows(node->child1.get(), windows);
  298. collect_windows(node->child2.get(), windows);
  299. }
  300. }
  301. void EditorCore::next_window() {
  302. // Cycle to the next window in the window tree
  303. // Note: Focus jumping bug was fixed in GTK frontend by caching active_window during redraws
  304. std::vector<std::shared_ptr<Window>> windows;
  305. collect_windows(root_node_.get(), windows);
  306. if (windows.size() <= 1) return;
  307. auto it = std::find(windows.begin(), windows.end(), active_window_);
  308. if (it != windows.end()) {
  309. auto next = it + 1;
  310. if (next == windows.end()) {
  311. active_window_ = windows[0];
  312. } else {
  313. active_window_ = *next;
  314. }
  315. emit_event(EditorEvent::WindowFocused);
  316. }
  317. }
  318. void EditorCore::next_window_safe() {
  319. // Deprecated: Use next_window() instead. Kept for backwards compatibility.
  320. next_window();
  321. }
  322. bool EditorCore::set_active_window(std::shared_ptr<Window> window) {
  323. if (!window) return false;
  324. // Verify that the window exists in the current window tree
  325. std::vector<std::shared_ptr<Window>> windows;
  326. collect_windows(root_node_.get(), windows);
  327. auto it = std::find(windows.begin(), windows.end(), window);
  328. if (it != windows.end()) {
  329. if (active_window_ != window) {
  330. active_window_ = window;
  331. emit_event(EditorEvent::WindowFocused);
  332. }
  333. return true;
  334. }
  335. return false; // Window not found in tree
  336. }
  337. // === Cursor Proxies ===
  338. Position EditorCore::cursor() const noexcept {
  339. return active_window_->cursor();
  340. }
  341. void EditorCore::set_cursor(Position pos) {
  342. active_window_->set_cursor(pos);
  343. emit_event(EditorEvent::CursorMoved);
  344. }
  345. void EditorCore::move_up() {
  346. active_window_->move_up();
  347. emit_event(EditorEvent::CursorMoved);
  348. }
  349. void EditorCore::move_down() {
  350. active_window_->move_down();
  351. emit_event(EditorEvent::CursorMoved);
  352. }
  353. void EditorCore::move_left() {
  354. active_window_->move_left();
  355. emit_event(EditorEvent::CursorMoved);
  356. }
  357. void EditorCore::move_right() {
  358. active_window_->move_right();
  359. emit_event(EditorEvent::CursorMoved);
  360. }
  361. void EditorCore::move_to_line_start() {
  362. active_window_->move_to_line_start();
  363. emit_event(EditorEvent::CursorMoved);
  364. }
  365. void EditorCore::move_to_line_end() {
  366. active_window_->move_to_line_end();
  367. emit_event(EditorEvent::CursorMoved);
  368. }
  369. // Helper: Check if character is a word constituent
  370. static bool is_word_char(char c) {
  371. return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
  372. (c >= '0' && c <= '9') || c == '_';
  373. }
  374. void EditorCore::move_forward_word() {
  375. auto new_pos = calculate_forward_word_pos(active_window_->cursor());
  376. active_window_->set_cursor(new_pos);
  377. emit_event(EditorEvent::CursorMoved);
  378. }
  379. void EditorCore::move_backward_word() {
  380. auto new_pos = calculate_backward_word_pos(active_window_->cursor());
  381. active_window_->set_cursor(new_pos);
  382. emit_event(EditorEvent::CursorMoved);
  383. }
  384. Position EditorCore::calculate_forward_word_pos(Position start_pos) {
  385. auto& buf = active_window_->buffer();
  386. auto cursor = start_pos;
  387. // Check if we are at the end of buffer
  388. if (cursor.line >= buf.line_count()) {
  389. return cursor;
  390. }
  391. // 1. Skip non-word chars (whitespace/punctuation)
  392. while (true) {
  393. const auto& line = buf.line(cursor.line);
  394. while (cursor.column < line.size() && !is_word_char(line[cursor.column])) {
  395. cursor.column++;
  396. }
  397. // If we found a word char, we're done with step 1
  398. if (cursor.column < line.size()) {
  399. break;
  400. }
  401. // Move to next line
  402. if (cursor.line < buf.line_count() - 1) {
  403. cursor.line++;
  404. cursor.column = 0;
  405. } else {
  406. // At end of buffer
  407. return cursor;
  408. }
  409. }
  410. // 2. Skip word chars
  411. const auto& line = buf.line(cursor.line);
  412. while (cursor.column < line.size() && is_word_char(line[cursor.column])) {
  413. cursor.column++;
  414. }
  415. return cursor;
  416. }
  417. Position EditorCore::calculate_backward_word_pos(Position start_pos) {
  418. auto& buf = active_window_->buffer();
  419. auto cursor = start_pos;
  420. // Skip whitespace and punctuation backwards
  421. while (true) {
  422. // If at start of line, go to previous line
  423. if (cursor.column == 0) {
  424. if (cursor.line == 0) {
  425. // At start of buffer
  426. break;
  427. }
  428. cursor.line--;
  429. cursor.column = buf.line(cursor.line).size();
  430. continue;
  431. }
  432. // Move back one char
  433. cursor.column--;
  434. const auto& line = buf.line(cursor.line);
  435. // If we hit a word char, keep going back through the word
  436. if (is_word_char(line[cursor.column])) {
  437. // Move to start of word
  438. while (cursor.column > 0 && is_word_char(line[cursor.column - 1])) {
  439. cursor.column--;
  440. }
  441. break;
  442. }
  443. }
  444. return cursor;
  445. }
  446. void EditorCore::page_up() {
  447. auto& viewport = active_window_->viewport();
  448. auto cursor = active_window_->cursor();
  449. // Move up by viewport height (minus 2 for overlap)
  450. int page_size = std::max(1, viewport.height - 2);
  451. if (cursor.line >= static_cast<size_t>(page_size)) {
  452. cursor.line -= page_size;
  453. } else {
  454. cursor.line = 0;
  455. }
  456. // Keep column position if possible
  457. auto& buf = active_window_->buffer();
  458. const auto& line = buf.line(cursor.line);
  459. cursor.column = std::min(cursor.column, line.size());
  460. active_window_->set_cursor(cursor);
  461. active_window_->adjust_scroll();
  462. emit_event(EditorEvent::CursorMoved);
  463. emit_event(EditorEvent::ViewportChanged);
  464. }
  465. void EditorCore::page_down() {
  466. auto& viewport = active_window_->viewport();
  467. auto cursor = active_window_->cursor();
  468. auto& buf = active_window_->buffer();
  469. // Move down by viewport height (minus 2 for overlap)
  470. int page_size = std::max(1, viewport.height - 2);
  471. cursor.line = std::min(cursor.line + page_size, buf.line_count() - 1);
  472. // Keep column position if possible
  473. const auto& line = buf.line(cursor.line);
  474. cursor.column = std::min(cursor.column, line.size());
  475. active_window_->set_cursor(cursor);
  476. active_window_->adjust_scroll();
  477. emit_event(EditorEvent::CursorMoved);
  478. emit_event(EditorEvent::ViewportChanged);
  479. }
  480. void EditorCore::goto_beginning() {
  481. Position pos = {0, 0};
  482. active_window_->set_cursor(pos);
  483. active_window_->adjust_scroll();
  484. emit_event(EditorEvent::CursorMoved);
  485. emit_event(EditorEvent::ViewportChanged);
  486. }
  487. void EditorCore::goto_end() {
  488. auto& buf = active_window_->buffer();
  489. size_t last_line = buf.line_count() > 0 ? buf.line_count() - 1 : 0;
  490. size_t last_col = buf.line(last_line).size();
  491. Position pos = {last_line, last_col};
  492. active_window_->set_cursor(pos);
  493. active_window_->adjust_scroll();
  494. emit_event(EditorEvent::CursorMoved);
  495. emit_event(EditorEvent::ViewportChanged);
  496. }
  497. void EditorCore::goto_line(size_t line) {
  498. auto& buf = active_window_->buffer();
  499. line = std::min(line, buf.line_count() > 0 ? buf.line_count() - 1 : 0); // Clamp to max line index
  500. Position pos = {line, 0};
  501. active_window_->set_cursor(pos);
  502. active_window_->adjust_scroll();
  503. emit_event(EditorEvent::CursorMoved);
  504. emit_event(EditorEvent::ViewportChanged);
  505. }
  506. // === Viewport Proxies ===
  507. const Viewport& EditorCore::viewport() const noexcept {
  508. return active_window_->viewport();
  509. }
  510. void EditorCore::set_viewport_size(int width, int height) {
  511. active_window_->set_viewport_size(width, height);
  512. emit_event(EditorEvent::ViewportChanged);
  513. }
  514. void EditorCore::adjust_scroll() {
  515. active_window_->adjust_scroll();
  516. emit_event(EditorEvent::ViewportChanged);
  517. }
  518. std::pair<size_t, size_t> EditorCore::visible_line_range() const {
  519. return active_window_->visible_line_range();
  520. }
  521. // === Undo/Redo ===
  522. bool EditorCore::undo() {
  523. auto& buf = active_window_->buffer();
  524. buf.save_undo_state(active_window_->cursor()); // Save state before
  525. Position new_cursor = active_window_->cursor();
  526. if (buf.undo(new_cursor)) {
  527. active_window_->set_cursor(new_cursor);
  528. emit_event(EditorEvent::CursorMoved);
  529. emit_event(EditorEvent::BufferModified);
  530. return true;
  531. }
  532. return false;
  533. }
  534. bool EditorCore::redo() {
  535. auto& buf = active_window_->buffer();
  536. Position new_cursor = active_window_->cursor();
  537. if (buf.redo(new_cursor)) {
  538. active_window_->set_cursor(new_cursor);
  539. emit_event(EditorEvent::CursorMoved);
  540. emit_event(EditorEvent::BufferModified);
  541. return true;
  542. }
  543. return false;
  544. }
  545. bool EditorCore::can_undo() const {
  546. return active_window_->buffer().can_undo();
  547. }
  548. bool EditorCore::can_redo() const {
  549. return active_window_->buffer().can_redo();
  550. }
  551. // === Kill Ring ===
  552. void EditorCore::kill_line() {
  553. auto& buf = active_window_->buffer();
  554. auto cursor = active_window_->cursor();
  555. const auto& line = buf.line(cursor.line);
  556. // If at end of line, kill the newline (join with next line)
  557. if (cursor.column >= line.size()) {
  558. if (cursor.line < buf.line_count() - 1) {
  559. // Kill the newline character
  560. Position start = {cursor.line, line.size()};
  561. Position end = {cursor.line + 1, 0};
  562. Range range = {start, end};
  563. std::string killed_text = "\n";
  564. kill_ring_.push(killed_text);
  565. buf.erase(range);
  566. emit_event(EditorEvent::BufferModified);
  567. std::cerr << "[DEBUG] Killed newline at end of line " << cursor.line << std::endl;
  568. }
  569. return;
  570. }
  571. // Kill from cursor to end of line
  572. Position start = cursor;
  573. Position end = {cursor.line, line.size()};
  574. Range range = {start, end};
  575. std::string killed_text = buf.get_text_in_range(range);
  576. if (!killed_text.empty()) {
  577. kill_ring_.push(killed_text);
  578. buf.erase(range);
  579. emit_event(EditorEvent::BufferModified);
  580. std::cerr << "[DEBUG] Killed text: '" << killed_text << "'" << std::endl;
  581. }
  582. }
  583. void EditorCore::kill_region() {
  584. auto& buf = active_window_->buffer();
  585. auto cursor = active_window_->cursor();
  586. auto region = buf.get_region(cursor);
  587. if (!region.has_value()) {
  588. set_message("No active region");
  589. return;
  590. }
  591. std::string killed_text = buf.get_text_in_range(region.value());
  592. if (!killed_text.empty()) {
  593. kill_ring_.push(killed_text);
  594. buf.deactivate_mark();
  595. // Move cursor to start of killed region
  596. active_window_->set_cursor(region.value().start);
  597. emit_event(EditorEvent::BufferModified);
  598. emit_event(EditorEvent::CursorMoved);
  599. std::cerr << "[DEBUG] Killed region: '" << killed_text << "'" << std::endl;
  600. }
  601. }
  602. void EditorCore::kill_word() {
  603. auto cursor = active_window_->cursor();
  604. auto end_pos = calculate_forward_word_pos(cursor);
  605. if (cursor == end_pos) return;
  606. Range range = {cursor, end_pos};
  607. auto& buf = active_window_->buffer();
  608. std::string text = buf.get_text_in_range(range);
  609. if (!text.empty()) {
  610. kill_ring_.push(text);
  611. buf.erase(range);
  612. emit_event(EditorEvent::BufferModified);
  613. std::cerr << "[DEBUG] Killed word: '" << text << "'" << std::endl;
  614. }
  615. }
  616. void EditorCore::backward_kill_word() {
  617. auto cursor = active_window_->cursor();
  618. auto start_pos = calculate_backward_word_pos(cursor);
  619. if (cursor == start_pos) return;
  620. Range range = {start_pos, cursor};
  621. auto& buf = active_window_->buffer();
  622. std::string text = buf.get_text_in_range(range);
  623. if (!text.empty()) {
  624. kill_ring_.push(text);
  625. buf.erase(range);
  626. active_window_->set_cursor(start_pos);
  627. emit_event(EditorEvent::BufferModified);
  628. emit_event(EditorEvent::CursorMoved);
  629. std::cerr << "[DEBUG] Backward killed word: '" << text << "'" << std::endl;
  630. }
  631. }
  632. void EditorCore::copy_region_as_kill() {
  633. auto& buf = active_window_->buffer();
  634. auto cursor = active_window_->cursor();
  635. auto region = buf.get_region(cursor);
  636. if (!region.has_value()) {
  637. set_message("No active region");
  638. return;
  639. }
  640. std::string copied_text = buf.get_text_in_range(region.value());
  641. if (!copied_text.empty()) {
  642. kill_ring_.push(copied_text);
  643. buf.deactivate_mark();
  644. set_message("Region copied");
  645. std::cerr << "[DEBUG] Copied region: '" << copied_text << "'" << std::endl;
  646. }
  647. }
  648. void EditorCore::yank() {
  649. if (kill_ring_.empty()) {
  650. set_message("Kill ring is empty");
  651. return;
  652. }
  653. std::string text = kill_ring_.current();
  654. if (text.empty()) {
  655. return;
  656. }
  657. auto& buf = active_window_->buffer();
  658. auto cursor = active_window_->cursor();
  659. // Save yank start position
  660. last_yank_start_ = cursor;
  661. // Insert the text
  662. buf.insert(cursor, text);
  663. // Calculate new cursor position after insertion
  664. Position new_cursor = cursor;
  665. // Count newlines in text
  666. size_t newline_count = std::count(text.begin(), text.end(), '\n');
  667. if (newline_count > 0) {
  668. // Multi-line yank: cursor goes to end of inserted text
  669. new_cursor.line += newline_count;
  670. size_t last_newline = text.rfind('\n');
  671. new_cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0;
  672. } else {
  673. // Single-line yank: advance column
  674. new_cursor.column += text.size();
  675. }
  676. last_yank_end_ = new_cursor;
  677. active_window_->set_cursor(new_cursor);
  678. emit_event(EditorEvent::BufferModified);
  679. emit_event(EditorEvent::CursorMoved);
  680. std::cerr << "[DEBUG] Yanked: '" << text << "'" << std::endl;
  681. }
  682. void EditorCore::yank_pop() {
  683. if (kill_ring_.empty()) {
  684. set_message("Kill ring is empty");
  685. return;
  686. }
  687. if (!last_yank_start_.has_value() || !last_yank_end_.has_value()) {
  688. set_message("Previous command was not a yank");
  689. return;
  690. }
  691. // Delete the previously yanked text
  692. auto& buf = active_window_->buffer();
  693. Range yank_range = {last_yank_start_.value(), last_yank_end_.value()};
  694. buf.erase(yank_range);
  695. // Get previous entry in kill ring
  696. std::string text = kill_ring_.previous();
  697. // Restore cursor to yank start
  698. auto cursor = last_yank_start_.value();
  699. active_window_->set_cursor(cursor);
  700. // Insert new text
  701. buf.insert(cursor, text);
  702. // Calculate new end position
  703. Position new_cursor = cursor;
  704. size_t newline_count = std::count(text.begin(), text.end(), '\n');
  705. if (newline_count > 0) {
  706. new_cursor.line += newline_count;
  707. size_t last_newline = text.rfind('\n');
  708. new_cursor.column = (last_newline != std::string::npos) ? (text.size() - last_newline - 1) : 0;
  709. } else {
  710. new_cursor.column += text.size();
  711. }
  712. last_yank_end_ = new_cursor;
  713. active_window_->set_cursor(new_cursor);
  714. emit_event(EditorEvent::BufferModified);
  715. emit_event(EditorEvent::CursorMoved);
  716. std::cerr << "[DEBUG] Yank-pop: '" << text << "'" << std::endl;
  717. }
  718. // === Registers ===
  719. void EditorCore::copy_to_register(char register_name, const std::string& text) {
  720. // Validate register name (a-z, A-Z, 0-9)
  721. if (!std::isalnum(register_name)) {
  722. set_message("Invalid register name");
  723. return;
  724. }
  725. registers_[register_name] = text;
  726. set_message(std::string("Saved text to register ") + register_name);
  727. }
  728. bool EditorCore::insert_register(char register_name) {
  729. // Validate register name
  730. if (!std::isalnum(register_name)) {
  731. set_message("Invalid register name");
  732. return false;
  733. }
  734. auto it = registers_.find(register_name);
  735. if (it == registers_.end()) {
  736. set_message(std::string("Register ") + register_name + " is empty");
  737. return false;
  738. }
  739. auto& buf = buffer();
  740. Position cursor = active_window_->cursor();
  741. buf.insert(cursor, it->second);
  742. // Move cursor to end of inserted text
  743. size_t newline_count = std::count(it->second.begin(), it->second.end(), '\n');
  744. if (newline_count > 0) {
  745. cursor.line += newline_count;
  746. size_t last_newline = it->second.rfind('\n');
  747. cursor.column = (last_newline != std::string::npos) ? (it->second.size() - last_newline - 1) : 0;
  748. } else {
  749. cursor.column += it->second.size();
  750. }
  751. active_window_->set_cursor(cursor);
  752. emit_event(EditorEvent::BufferModified);
  753. emit_event(EditorEvent::CursorMoved);
  754. set_message(std::string("Inserted register ") + register_name);
  755. return true;
  756. }
  757. void EditorCore::copy_region_to_register(char register_name) {
  758. auto& buf = buffer();
  759. Position cursor = active_window_->cursor();
  760. auto region = buf.get_region(cursor);
  761. if (!region) {
  762. set_message("No active region");
  763. return;
  764. }
  765. std::string text = buf.get_text_in_range(*region);
  766. copy_to_register(register_name, text);
  767. }
  768. bool EditorCore::yank_from_register(char register_name) {
  769. return insert_register(register_name);
  770. }
  771. // === Keyboard Macros ===
  772. void EditorCore::start_kbd_macro() {
  773. if (recording_macro_) {
  774. set_message("Already recording macro");
  775. return;
  776. }
  777. recording_macro_ = true;
  778. current_macro_.clear();
  779. set_message("Recording macro...");
  780. }
  781. void EditorCore::end_kbd_macro_or_call() {
  782. if (recording_macro_) {
  783. // End recording
  784. recording_macro_ = false;
  785. last_macro_ = current_macro_;
  786. current_macro_.clear();
  787. if (last_macro_.empty()) {
  788. set_message("Macro recorded (empty)");
  789. } else {
  790. set_message(std::string("Macro recorded (") + std::to_string(last_macro_.size()) + " keys)");
  791. }
  792. } else {
  793. // Call last macro
  794. if (last_macro_.empty()) {
  795. set_message("No macro recorded");
  796. return;
  797. }
  798. set_message(std::string("Executing macro (") + std::to_string(last_macro_.size()) + " keys)");
  799. // This is a simplified approach - in a real implementation, you'd need
  800. // to replay the actual commands through the key binding system
  801. // For now, we just show that the macro system is set up
  802. for (const auto& key : last_macro_) {
  803. // TODO: Execute the actual key binding for this key
  804. // This would require access to the LuaApi from EditorCore
  805. std::cerr << "[MACRO] Would execute key: " << key << std::endl;
  806. }
  807. }
  808. }
  809. void EditorCore::record_key_sequence(const std::string& key_sequence) {
  810. if (recording_macro_) {
  811. current_macro_.push_back(key_sequence);
  812. }
  813. }
  814. // === Rectangles ===
  815. void EditorCore::kill_rectangle() {
  816. auto& buf = buffer();
  817. Position cursor = active_window_->cursor();
  818. auto region = buf.get_region(cursor);
  819. if (!region) {
  820. set_message("No active region");
  821. return;
  822. }
  823. // Ensure start is top-left, end is bottom-right
  824. Position start = region->start;
  825. Position end = region->end;
  826. if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
  827. std::swap(start, end);
  828. }
  829. rectangle_kill_ring_.clear();
  830. // Extract rectangle line by line
  831. for (size_t line = start.line; line <= end.line; ++line) {
  832. if (line >= buf.line_count()) break;
  833. std::string line_text = buf.line(line);
  834. size_t start_col = (line == start.line) ? start.column : std::min(start.column, end.column);
  835. size_t end_col = (line == end.line) ? end.column : std::max(start.column, end.column);
  836. // Ensure we don't go beyond the line length
  837. start_col = std::min(start_col, line_text.size());
  838. end_col = std::min(end_col, line_text.size());
  839. if (start_col < end_col) {
  840. rectangle_kill_ring_.push_back(line_text.substr(start_col, end_col - start_col));
  841. } else {
  842. rectangle_kill_ring_.push_back("");
  843. }
  844. }
  845. // Now delete the rectangle content (from bottom to top to preserve line indices)
  846. for (int line = static_cast<int>(end.line); line >= static_cast<int>(start.line); --line) {
  847. if (line >= static_cast<int>(buf.line_count())) continue;
  848. std::string line_text = buf.line(line);
  849. size_t start_col = (line == static_cast<int>(start.line)) ? start.column : std::min(start.column, end.column);
  850. size_t end_col = (line == static_cast<int>(end.line)) ? end.column : std::max(start.column, end.column);
  851. start_col = std::min(start_col, line_text.size());
  852. end_col = std::min(end_col, line_text.size());
  853. if (start_col < end_col) {
  854. Range del_range{Position(line, start_col), Position(line, end_col)};
  855. buf.erase(del_range);
  856. }
  857. }
  858. buf.deactivate_mark();
  859. emit_event(EditorEvent::BufferModified);
  860. set_message(std::string("Rectangle killed (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
  861. }
  862. void EditorCore::yank_rectangle() {
  863. if (rectangle_kill_ring_.empty()) {
  864. set_message("No rectangle in kill ring");
  865. return;
  866. }
  867. auto& buf = buffer();
  868. Position cursor = active_window_->cursor();
  869. // Insert rectangle starting at cursor position
  870. for (size_t i = 0; i < rectangle_kill_ring_.size(); ++i) {
  871. Position insert_pos{cursor.line + i, cursor.column};
  872. // Ensure we have enough lines
  873. while (buf.line_count() <= insert_pos.line) {
  874. buf.insert_newline(Position{buf.line_count() - 1, buf.line(buf.line_count() - 1).size()});
  875. }
  876. // Pad line with spaces if necessary
  877. std::string current_line = buf.line(insert_pos.line);
  878. if (current_line.size() < insert_pos.column) {
  879. std::string padding(insert_pos.column - current_line.size(), ' ');
  880. buf.insert(Position{insert_pos.line, current_line.size()}, padding);
  881. }
  882. buf.insert(insert_pos, rectangle_kill_ring_[i]);
  883. }
  884. emit_event(EditorEvent::BufferModified);
  885. set_message(std::string("Rectangle yanked (") + std::to_string(rectangle_kill_ring_.size()) + " lines)");
  886. }
  887. void EditorCore::string_rectangle(const std::string& text) {
  888. auto& buf = buffer();
  889. Position cursor = active_window_->cursor();
  890. auto region = buf.get_region(cursor);
  891. if (!region) {
  892. set_message("No active region");
  893. return;
  894. }
  895. Position start = region->start;
  896. Position end = region->end;
  897. if (start.line > end.line || (start.line == end.line && start.column > end.column)) {
  898. std::swap(start, end);
  899. }
  900. // Fill rectangle with the given text
  901. for (size_t line = start.line; line <= end.line; ++line) {
  902. if (line >= buf.line_count()) break;
  903. std::string line_text = buf.line(line);
  904. size_t start_col = std::min(start.column, end.column);
  905. size_t end_col = std::max(start.column, end.column);
  906. // Pad line if necessary
  907. if (line_text.size() < end_col) {
  908. std::string padding(end_col - line_text.size(), ' ');
  909. buf.insert(Position{line, line_text.size()}, padding);
  910. line_text = buf.line(line); // Refresh
  911. }
  912. // Replace rectangle content with text
  913. if (start_col < line_text.size()) {
  914. end_col = std::min(end_col, line_text.size());
  915. if (start_col < end_col) {
  916. Range replace_range{Position(line, start_col), Position(line, end_col)};
  917. std::string fill_text = text;
  918. if (fill_text.size() > end_col - start_col) {
  919. fill_text = fill_text.substr(0, end_col - start_col);
  920. } else if (fill_text.size() < end_col - start_col) {
  921. fill_text += std::string(end_col - start_col - fill_text.size(), ' ');
  922. }
  923. buf.replace(replace_range, fill_text);
  924. }
  925. }
  926. }
  927. buf.deactivate_mark();
  928. emit_event(EditorEvent::BufferModified);
  929. set_message("Rectangle filled");
  930. }
  931. // === Private ===
  932. void EditorCore::emit_event(EditorEvent event) {
  933. for (const auto& callback : event_callbacks_) {
  934. callback(event);
  935. }
  936. }
  937. } // namespace lumacs