editor_core.cpp 35 KB

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