editor_core.cpp 34 KB

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