lua_api.cpp 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. #include "lumacs/lua_api.hpp"
  2. #include "lumacs/command_system.hpp"
  3. #include "lumacs/minibuffer_mode.hpp" // Added for MinibufferMode enum binding
  4. #include "lumacs/completion_system.hpp" // Added for CompletionSystem access
  5. #include "lumacs/plugin_manager.hpp" // Added for PluginManager access
  6. #include "lumacs/buffer_manager.hpp" // Added for BufferManager::BufferInfo
  7. #include "lumacs/logger.hpp"
  8. #include "../src/defaults.hpp" // Include embedded defaults
  9. #include <spdlog/spdlog.h>
  10. #include <iostream> // Kept for now if needed by sol2 internals or other parts, but trying to remove direct usage
  11. #include <fstream>
  12. #include <filesystem> // For std::filesystem::path
  13. namespace lumacs {
  14. // Helper class to adapt a lambda/function to ICompletionSource
  15. class LambdaCompletionSource : public ICompletionSource {
  16. public:
  17. explicit LambdaCompletionSource(sol::function provider) : provider_(std::move(provider)) {}
  18. std::vector<CompletionCandidate> get_candidates(const std::string& input) override {
  19. sol::protected_function_result lua_result = provider_(input); // Get the raw Lua result
  20. std::vector<CompletionCandidate> candidates;
  21. if (lua_result.valid()) {
  22. sol::type result_type = lua_result.get_type();
  23. if (result_type == sol::type::table) {
  24. sol::table result_table = lua_result; // Convert to sol::table
  25. for (auto& pair : result_table) {
  26. if (pair.second.get_type() == sol::type::table) {
  27. sol::table candidate_table = pair.second;
  28. std::string text = "";
  29. int score = 50;
  30. std::string desc = "";
  31. if (candidate_table["text"].valid()) {
  32. text = candidate_table["text"];
  33. }
  34. if (candidate_table["score"].valid()) {
  35. score = candidate_table["score"];
  36. }
  37. if (candidate_table["description"].valid()) {
  38. desc = candidate_table["description"].get<std::string>(); // Get the string
  39. }
  40. candidates.emplace_back(text, score, "", desc); // Fix: Explicitly pass empty string for display_text
  41. }
  42. }
  43. }
  44. }
  45. return candidates;
  46. }
  47. private:
  48. sol::function provider_;
  49. };
  50. LuaApi::LuaApi() {
  51. lua_.open_libraries(
  52. sol::lib::base,
  53. sol::lib::package,
  54. sol::lib::string,
  55. sol::lib::math,
  56. sol::lib::table,
  57. sol::lib::io,
  58. sol::lib::os,
  59. sol::lib::debug // Open debug library early for MobDebug
  60. );
  61. }
  62. void LuaApi::set_core(EditorCore& core) {
  63. core_ = &core;
  64. setup_api();
  65. }
  66. bool LuaApi::load_file(const std::filesystem::path& path) {
  67. try {
  68. spdlog::debug("Loading Lua file: {}", path.string());
  69. lua_.script_file(path.string());
  70. spdlog::debug("Lua file loaded successfully");
  71. spdlog::info("Loaded Lua file: {}", path.string());
  72. return true;
  73. } catch (const sol::error& e) {
  74. spdlog::error("Lua error loading {}: {}", path.string(), e.what());
  75. return false;
  76. } catch (const std::exception& e) {
  77. spdlog::error("Exception loading {}: {}", path.string(), e.what());
  78. return false;
  79. }
  80. }
  81. bool LuaApi::execute(std::string_view code) {
  82. try {
  83. lua_.script(code);
  84. return true;
  85. } catch (const sol::error& e) {
  86. spdlog::error("Lua error: {}", e.what());
  87. return false;
  88. }
  89. }
  90. bool LuaApi::load_init_file() {
  91. // 1. ALWAYS load the embedded defaults first
  92. spdlog::info("Loading embedded defaults...");
  93. try {
  94. lua_.script(LUA_DEFAULTS);
  95. } catch (const sol::error& e) {
  96. spdlog::critical("FAILED TO LOAD EMBEDDED DEFAULTS: {}", e.what());
  97. // Continue anyway
  98. }
  99. // Get home directory
  100. const char* home = getenv("HOME");
  101. std::string home_dir = home ? home : ".";
  102. // Try multiple locations in order
  103. std::vector<std::filesystem::path> search_paths = {
  104. std::filesystem::current_path() / "init.lua",
  105. std::filesystem::path(home_dir) / ".config" / "lumacs" / "init.lua",
  106. std::filesystem::path(home_dir) / ".lumacs" / "init.lua",
  107. };
  108. for (const auto& path : search_paths) {
  109. if (std::filesystem::exists(path)) {
  110. spdlog::info("Found init file: {}", path.string());
  111. return load_file(path);
  112. }
  113. }
  114. spdlog::warn("No init.lua found (searched: ./init.lua, ~/.config/lumacs/init.lua, ~/.lumacs/init.lua). Using defaults only.");
  115. return true; // Return true because defaults were loaded
  116. }
  117. void LuaApi::bind_key(std::string key, sol::function callback, std::string description) {
  118. spdlog::debug("Registering key binding: {}", key);
  119. // Create a unique command name for the Lua function
  120. // This is a simple way; a more robust system might handle conflicts or namespaces
  121. std::string command_name = "lua_cmd_" + key; // Use key itself as part of name
  122. // Register a C++ CommandFunction that wraps the Lua callback
  123. core_->command_system().register_command(command_name,
  124. [callback, this](CommandContext& context) -> CommandResult { // Added 'this' to capture
  125. try {
  126. // Pass args from context to Lua function
  127. sol::table args_table = get_lua_state().create_table(); // Use get_lua_state()
  128. const auto& args = context.get_args(); // Use getter
  129. for (size_t i = 0; i < args.size(); ++i) {
  130. args_table[i + 1] = args[i];
  131. }
  132. auto result = callback(args_table);
  133. if (result.valid()) {
  134. if (result.get_type() == sol::type::table) {
  135. sol::table res_table = result;
  136. lumacs::CommandStatus status = lumacs::CommandStatus::Success; // Namespace fix
  137. std::string message = "";
  138. if (res_table["success"].valid()) {
  139. status = res_table["success"].get<bool>() ? lumacs::CommandStatus::Success : lumacs::CommandStatus::Failure;
  140. }
  141. if (res_table["message"].valid()) {
  142. message = res_table["message"];
  143. }
  144. return CommandResult{status, message};
  145. } else if (result.get_type() == sol::type::string) {
  146. std::string message = result.get<std::string>();
  147. return CommandResult{lumacs::CommandStatus::Success, message};
  148. } else {
  149. return CommandResult{lumacs::CommandStatus::Success, ""};
  150. }
  151. } else {
  152. return CommandResult{lumacs::CommandStatus::Success, ""};
  153. }
  154. } catch (const sol::error& e) {
  155. return CommandResult{lumacs::CommandStatus::Failure, "Lua error in key binding callback: " + std::string(e.what())};
  156. }
  157. },
  158. description, // Use original description
  159. false, // Not interactive directly via spec, Lua callback handles interactivity
  160. "" // No interactive spec for this wrapper command
  161. );
  162. // Now bind the key to the newly registered C++ command's name
  163. core_->keybinding_manager().bind(KeySequence(key), command_name, description);
  164. }
  165. KeyProcessingResult LuaApi::process_key(const std::string& key) { // Corrected return type
  166. return core_->keybinding_manager().process_key(key);
  167. }
  168. // Legacy methods for backward compatibility
  169. bool LuaApi::has_key_binding(const std::string& key) const {
  170. return legacy_key_bindings_.find(key) != legacy_key_bindings_.end() ||
  171. core_->keybinding_manager().has_exact_binding(KeySequence(key));
  172. }
  173. bool LuaApi::execute_key_binding(const std::string& key) {
  174. auto it = legacy_key_bindings_.find(key);
  175. if (it == legacy_key_bindings_.end()) {
  176. return false;
  177. }
  178. try {
  179. it->second();
  180. return true;
  181. } catch (const sol::error& e) {
  182. spdlog::error("Lua error executing key binding '{}': {}", key, e.what());
  183. return false;
  184. }
  185. }
  186. // Manual C functions implementations
  187. int LuaApi::get_active_buffer_line_count_lua() {
  188. if (!core_ || !core_->active_window() || !core_->active_window()->buffer_ptr()) {
  189. spdlog::warn("get_active_buffer_line_count_lua: Core, active window or buffer is null!");
  190. return 0;
  191. }
  192. return static_cast<int>(core_->buffer().line_count());
  193. }
  194. void LuaApi::lua_editor_move_right() {
  195. if (!core_) {
  196. spdlog::warn("lua_editor_move_right: Core is null!");
  197. return;
  198. }
  199. core_->move_right();
  200. }
  201. void LuaApi::lua_editor_new_buffer(const std::string& name) {
  202. if (!core_) {
  203. spdlog::warn("lua_editor_new_buffer: Core is null!");
  204. return;
  205. }
  206. core_->new_buffer(name);
  207. }
  208. sol::optional<std::string> LuaApi::lua_editor_get_buffer_by_name(const std::string& name) {
  209. if (!core_) {
  210. spdlog::warn("lua_editor_get_buffer_by_name: Core is null!");
  211. return sol::nullopt;
  212. }
  213. if (auto buffer = core_->get_buffer_by_name(name)) {
  214. return buffer->name(); // Return name as a string to avoid complex Buffer usertype issues
  215. }
  216. return sol::nullopt;
  217. }
  218. void LuaApi::lua_editor_set_message(const std::string& message) {
  219. if (!core_) {
  220. spdlog::warn("lua_editor_set_message: Core is null!");
  221. return;
  222. }
  223. core_->set_message(message);
  224. }
  225. void LuaApi::lua_config_set_string(const std::string& key, const std::string& value) {
  226. if (!core_) {
  227. spdlog::warn("lua_config_set_string: Core is null!");
  228. return;
  229. }
  230. core_->config().set(key, value);
  231. }
  232. std::string LuaApi::lua_config_get_string(const std::string& key, const std::string& default_val) {
  233. if (!core_) {
  234. spdlog::warn("lua_config_get_string: Core is null!");
  235. return default_val;
  236. }
  237. return core_->config().get<std::string>(key, default_val);
  238. }
  239. void LuaApi::lua_config_set_bool(const std::string& key, bool value) {
  240. if (!core_) {
  241. spdlog::warn("lua_config_set_bool: Core is null!");
  242. return;
  243. }
  244. core_->config().set(key, value);
  245. }
  246. bool LuaApi::lua_config_get_bool(const std::string& key, bool default_val) {
  247. if (!core_) {
  248. spdlog::warn("lua_config_get_bool: Core is null!");
  249. return default_val;
  250. }
  251. return core_->config().get<bool>(key, default_val);
  252. }
  253. void LuaApi::lua_config_set_int(const std::string& key, int value) {
  254. if (!core_) {
  255. spdlog::warn("lua_config_set_int: Core is null!");
  256. return;
  257. }
  258. core_->config().set(key, value);
  259. }
  260. int LuaApi::lua_config_get_int(const std::string& key, int default_val) {
  261. if (!core_) {
  262. spdlog::warn("lua_config_get_int: Core is null!");
  263. return default_val;
  264. }
  265. return core_->config().get<int>(key, default_val);
  266. }
  267. // New: Implement lua_create_and_register_theme
  268. sol::object LuaApi::lua_create_and_register_theme(const std::string& name) {
  269. if (!core_) {
  270. spdlog::warn("lua_create_and_register_theme: Core is null!");
  271. return sol::nil; // Return nil in Lua if core is not available
  272. }
  273. // Create a new Theme shared_ptr
  274. auto new_theme = std::make_shared<Theme>(name);
  275. // Register it with the ThemeManager
  276. core_->theme_manager().register_theme(new_theme);
  277. spdlog::info("Registered new theme: {}", name);
  278. // Return the new_theme as a sol::object. Sol2 will automatically handle the shared_ptr and usertype conversion.
  279. // The Theme usertype is defined in register_types(), so we need access to the lua_ state.
  280. // We pass a reference to the shared_ptr so that the Lua object holds a reference, preventing early destruction.
  281. return sol::make_object(lua_, new_theme);
  282. }
  283. void LuaApi::setup_api() {
  284. register_types();
  285. register_functions();
  286. // Set version info and add constructor functions to lumacs namespace
  287. auto lumacs_table = lua_.create_table();
  288. lumacs_table["version"] = "0.1.0";
  289. lumacs_table["name"] = "Lumacs";
  290. // Add constructor functions
  291. lumacs_table["Position"] = [](size_t line, size_t column) {
  292. return Position{line, column};
  293. };
  294. lumacs_table["Range"] = [](Position start, Position end) {
  295. return Range{start, end};
  296. };
  297. lumacs_table["Color"] = [](int r, int g, int b) { // Explicitly define Color constructor
  298. return Color(r, g, b);
  299. };
  300. lumacs_table["TextAttribute"] = [](lumacs::TextAttribute::ColorType color, int style) {
  301. return TextAttribute{color, style};
  302. };
  303. lumacs_table["FaceAttributes"] = []() { return FaceAttributes(); }; // Explicitly define FaceAttributes constructor
  304. lumacs_table["StyledRange"] = [](Range range, TextAttribute attr) {
  305. return StyledRange{range, attr};
  306. };
  307. // Add enums to lumacs namespace for convenience
  308. lumacs_table["ColorType"] = lua_["ColorType"];
  309. lumacs_table["Style"] = lua_["Style"];
  310. lumacs_table["BufferEvent"] = lua_["BufferEvent"];
  311. lumacs_table["ThemeElement"] = lua_["ThemeElement"];
  312. lumacs_table["FontWeight"] = lua_["FontWeight"];
  313. lumacs_table["FontSlant"] = lua_["FontSlant"];
  314. // lumacs_table["FaceAttributes"] = lua_["FaceAttributes"]; // Not needed here if constructor is explicit
  315. lumacs_table["MinibufferMode"] = lua_["MinibufferMode"]; // Expose MinibufferMode to Lua
  316. lua_["lumacs"] = lumacs_table;
  317. // Initialize MobDebug if enabled
  318. if (core_->config().get<bool>("debug_lua", false)) { // Corrected Config access
  319. spdlog::debug("Lua debugging enabled.");
  320. std::string debug_host = core_->config().get<std::string>("debug_host", "127.0.0.1"); // Corrected Config access
  321. int debug_port = core_->config().get<int>("debug_port", 8171); // Corrected Config access
  322. try {
  323. // MobDebug requires socket library, which is already opened in constructor.
  324. // Explicitly require 'mobdebug' module
  325. sol::optional<sol::table> mobdebug = lua_["require"]("mobdebug");
  326. if (mobdebug) {
  327. spdlog::debug("MobDebug module loaded.");
  328. sol::function mobdebug_start = mobdebug.value()["start"];
  329. if (mobdebug_start.valid()) {
  330. mobdebug_start(debug_host, debug_port);
  331. spdlog::info("MobDebug started on {}:{}", debug_host, debug_port);
  332. } else {
  333. spdlog::error("MobDebug.start function not found.");
  334. }
  335. } else {
  336. spdlog::error("Failed to load MobDebug module. Is LuaRocks installed and configured?");
  337. }
  338. } catch (const sol::error& e) {
  339. spdlog::error("Lua error during MobDebug initialization: {}", e.what());
  340. }
  341. }
  342. }
  343. void LuaApi::register_types() {
  344. // Position type
  345. lua_.new_usertype<Position>("Position",
  346. sol::constructors<Position(), Position(size_t, size_t)>(),
  347. "line", &Position::line,
  348. "column", &Position::column
  349. );
  350. // Range type
  351. lua_.new_usertype<Range>("Range",
  352. sol::constructors<Range(), Range(Position, Position)>(),
  353. "start", &Range::start,
  354. "end", &Range::end
  355. );
  356. // Font weight
  357. lua_.new_enum<FontWeight>("FontWeight",
  358. {
  359. {"Normal", FontWeight::Normal},
  360. {"Bold", FontWeight::Bold},
  361. {"Light", FontWeight::Light}
  362. }
  363. );
  364. // Font slant
  365. lua_.new_enum<FontSlant>("FontSlant",
  366. {
  367. {"Normal", FontSlant::Normal},
  368. {"Italic", FontSlant::Italic},
  369. {"Oblique", FontSlant::Oblique}
  370. }
  371. );
  372. // FaceAttributes type
  373. lua_.new_usertype<FaceAttributes>("FaceAttributes",
  374. sol::constructors<FaceAttributes()>(),
  375. "family", &FaceAttributes::family,
  376. "height", &FaceAttributes::height,
  377. "weight", &FaceAttributes::weight,
  378. "slant", &FaceAttributes::slant,
  379. "foreground", &FaceAttributes::foreground,
  380. "background", &FaceAttributes::background,
  381. "underline", &FaceAttributes::underline,
  382. "inverse", &FaceAttributes::inverse,
  383. "inherit", &FaceAttributes::inherit
  384. );
  385. // TextAttribute type
  386. lua_.new_usertype<TextAttribute>("TextAttribute",
  387. sol::constructors<TextAttribute(), TextAttribute(std::string), TextAttribute(lumacs::TextAttribute::ColorType, int)>(), // Namespace fix
  388. "face_name", &TextAttribute::face_name
  389. );
  390. // TextAttribute::ColorType enum
  391. lua_.new_enum<TextAttribute::ColorType>("ColorType",
  392. {
  393. {"Default", TextAttribute::ColorType::Default},
  394. {"Keyword", TextAttribute::ColorType::Keyword},
  395. {"String", TextAttribute::ColorType::String},
  396. {"Comment", TextAttribute::ColorType::Comment},
  397. {"Function", TextAttribute::ColorType::Function},
  398. {"Type", TextAttribute::ColorType::Type},
  399. {"Number", TextAttribute::ColorType::Number},
  400. {"Operator", TextAttribute::ColorType::Operator},
  401. {"Variable", TextAttribute::ColorType::Variable},
  402. {"Constant", TextAttribute::ColorType::Constant},
  403. {"Error", TextAttribute::ColorType::Error}
  404. }
  405. );
  406. // TextAttribute::Style enum
  407. lua_.new_enum<TextAttribute::Style>("Style",
  408. {
  409. {"Normal", TextAttribute::Style::Normal},
  410. {"Bold", TextAttribute::Style::Bold},
  411. {"Italic", TextAttribute::Style::Italic},
  412. {"Underline", TextAttribute::Style::Underline}
  413. }
  414. );
  415. // BufferEvent enum
  416. lua_.new_enum<BufferEvent>("BufferEvent",
  417. {
  418. {"Created", BufferEvent::Created},
  419. {"Loaded", BufferEvent::Loaded},
  420. {"Closed", BufferEvent::Closed},
  421. {"BeforeChange", BufferEvent::BeforeChange},
  422. {"AfterChange", BufferEvent::AfterChange},
  423. {"LineChanged", BufferEvent::LineChanged},
  424. {"BeforeSave", BufferEvent::BeforeSave},
  425. {"AfterSave", BufferEvent::AfterSave},
  426. {"LanguageChanged", BufferEvent::LanguageChanged}
  427. }
  428. );
  429. // BufferEventData type
  430. lua_.new_usertype<BufferEventData>("BufferEventData",
  431. sol::no_constructor,
  432. "event", &BufferEventData::event,
  433. "line", &BufferEventData::line,
  434. "language", &BufferEventData::language
  435. );
  436. // StyledRange type
  437. lua_.new_usertype<StyledRange>("StyledRange",
  438. sol::constructors<StyledRange(), StyledRange(Range, TextAttribute)>(),
  439. "range", &StyledRange::range,
  440. "attr", &StyledRange::attr
  441. );
  442. // BufferInfo type
  443. lua_.new_usertype<BufferManager::BufferInfo>("BufferInfo", // Corrected
  444. sol::no_constructor,
  445. "name", &BufferManager::BufferInfo::name, // Corrected
  446. "size", &BufferManager::BufferInfo::size, // Corrected
  447. "modified", &BufferManager::BufferInfo::modified, // Corrected
  448. "mode", &BufferManager::BufferInfo::mode, // Corrected
  449. "filepath", sol::property([](const BufferManager::BufferInfo& info) -> sol::optional<std::string> { // Corrected
  450. if (info.filepath.has_value()) {
  451. return info.filepath->string();
  452. }
  453. return sol::nullopt;
  454. })
  455. );
  456. // Color type
  457. lua_.new_usertype<Color>("Color",
  458. sol::constructors<Color(), Color(int, int, int)>(),
  459. "r", &Color::r,
  460. "g", &Color::g,
  461. "b", &Color::b
  462. );
  463. // ThemeElement enum
  464. lua_.new_enum<ThemeElement>("ThemeElement",
  465. {
  466. {"Normal", ThemeElement::Normal},
  467. {"Keyword", ThemeElement::Keyword},
  468. {"String", ThemeElement::String},
  469. {"Comment", ThemeElement::Comment},
  470. {"Function", ThemeElement::Function},
  471. {"Type", ThemeElement::Type},
  472. {"Number", ThemeElement::Number},
  473. {"Constant", ThemeElement::Constant},
  474. {"Error", ThemeElement::Error},
  475. {"StatusLine", ThemeElement::StatusLine},
  476. {"StatusLineInactive", ThemeElement::StatusLineInactive},
  477. {"MessageLine", ThemeElement::MessageLine},
  478. {"LineNumber", ThemeElement::LineNumber},
  479. {"Cursor", ThemeElement::Cursor},
  480. {"Selection", ThemeElement::Selection},
  481. {"SearchMatch", ThemeElement::SearchMatch},
  482. {"SearchFail", ThemeElement::SearchFail}, // Corrected
  483. {"WindowBorder", ThemeElement::WindowBorder},
  484. {"WindowBorderActive", ThemeElement::WindowBorderActive},
  485. {"Background", ThemeElement::Background}
  486. }
  487. );
  488. // Theme type
  489. lua_.new_usertype<Theme>("Theme",
  490. sol::no_constructor,
  491. "set_color", [](Theme& theme, ThemeElement element, Color fg, sol::optional<Color> bg) {
  492. Color bg_color = bg.value_or(Color(-1, -1, -1));
  493. theme.set_color(element, fg, bg_color);
  494. },
  495. "set_face", &Theme::set_face,
  496. "get_fg_color", &Theme::get_fg_color,
  497. "get_bg_color", &Theme::get_bg_color,
  498. "name", &Theme::name
  499. );
  500. // ThemeManager type
  501. lua_.new_usertype<ThemeManager>("ThemeManager",
  502. sol::no_constructor,
  503. "set_active_theme", &ThemeManager::set_active_theme,
  504. "theme_names", &ThemeManager::theme_names,
  505. "active_theme", &ThemeManager::active_theme
  506. );
  507. // Config type
  508. lua_.new_usertype<Config>("Config",
  509. sol::no_constructor,
  510. "set", [](Config& config, const std::string& key, sol::object value) {
  511. if (value.is<bool>()) {
  512. config.set(key, value.as<bool>());
  513. } else if (value.is<int>()) {
  514. config.set(key, value.as<int>());
  515. } else if (value.is<std::string>()) {
  516. config.set(key, value.as<std::string>());
  517. }
  518. },
  519. "get_bool", [](const Config& config, const std::string& key, sol::optional<bool> default_val) {
  520. return config.get<bool>(key, default_val.value_or(false));
  521. },
  522. "get_int", [](const Config& config, const std::string& key, sol::optional<int> default_val) {
  523. return config.get<int>(key, default_val.value_or(0));
  524. },
  525. "get_string", [](const Config& config, const std::string& key, sol::optional<std::string> default_val) {
  526. return config.get<std::string>(key, default_val.value_or(""));
  527. },
  528. "has", &Config::has,
  529. "keys", &Config::keys
  530. );
  531. // MinibufferMode enum
  532. lua_.new_enum<MinibufferMode>("MinibufferMode", {
  533. {"None", MinibufferMode::None},
  534. {"Command", MinibufferMode::Command},
  535. {"FilePath", MinibufferMode::FilePath},
  536. {"BufferName", MinibufferMode::BufferName},
  537. {"ThemeName", MinibufferMode::ThemeName},
  538. {"ISearch", MinibufferMode::ISearch},
  539. {"ystring", MinibufferMode::ystring},
  540. {"y_or_n_p", MinibufferMode::y_or_n_p}
  541. });
  542. // Buffer type
  543. lua_.new_usertype<Buffer>("Buffer",
  544. sol::no_constructor,
  545. "name", &Buffer::name,
  546. "set_name", &Buffer::set_name,
  547. "filepath", [](const Buffer& b) -> sol::optional<std::string> {
  548. auto path = b.file_path();
  549. if (!path.has_value()) return sol::nullopt;
  550. return path->string();
  551. },
  552. "line_count", &Buffer::line_count,
  553. "is_modified", &Buffer::is_modified,
  554. "line", [](Buffer& b, size_t idx) {
  555. if (idx >= b.line_count()) return std::string("");
  556. return b.line(idx);
  557. },
  558. "content", &Buffer::content,
  559. "insert", &Buffer::insert,
  560. "insert_newline", &Buffer::insert_newline,
  561. "erase", &Buffer::erase,
  562. "erase_char", &Buffer::erase_char,
  563. "clear", &Buffer::clear,
  564. "save", &Buffer::save,
  565. // Mark & Region
  566. "set_mark", &Buffer::set_mark,
  567. "deactivate_mark", &Buffer::deactivate_mark,
  568. "has_mark", [](const Buffer& b) { return b.mark().has_value(); },
  569. "mark", sol::property([](const Buffer& b) -> sol::optional<Position> {
  570. auto m = b.mark();
  571. if (m.has_value()) return *m;
  572. return sol::nullopt;
  573. }),
  574. "get_region", [](Buffer& b, Position p) -> sol::optional<Range> {
  575. auto r = b.get_region(p);
  576. if (r.has_value()) return *r;
  577. return sol::nullopt;
  578. },
  579. "get_text_in_range", &Buffer::get_text_in_range,
  580. "get_all_text", &Buffer::content, // Alias
  581. "replace", [](Buffer& b, Range r, const std::string& text) {
  582. b.erase(r);
  583. b.insert(r.start, text);
  584. },
  585. // Styling
  586. "set_style", &Buffer::set_style,
  587. "clear_styles", &Buffer::clear_styles,
  588. "get_line_styles", &Buffer::get_line_styles,
  589. // Events
  590. "on_buffer_event", [](Buffer& b, sol::function callback) {
  591. b.on_buffer_event([callback](const BufferEventData& data) {
  592. try {
  593. callback(data);
  594. } catch (const sol::error& e) {
  595. spdlog::error("Lua error in buffer event listener: {}", e.what());
  596. }
  597. });
  598. },
  599. // Search
  600. "find", [](Buffer& b, const std::string& query, sol::optional<Position> start_pos) -> sol::optional<Range> {
  601. auto r = b.find(query, start_pos.value_or(Position{0,0}));
  602. if (r.has_value()) return *r;
  603. return sol::nullopt;
  604. }
  605. );
  606. // EditorCore type
  607. lua_.new_usertype<EditorCore>("EditorCore",
  608. sol::no_constructor,
  609. "cursor", sol::property(&EditorCore::cursor, &EditorCore::set_cursor), // Added cursor property
  610. "set_cursor", &EditorCore::set_cursor, // Explicit setter method
  611. // Movement
  612. "move_up", &EditorCore::move_up,
  613. "move_down", &EditorCore::move_down,
  614. "move_left", &EditorCore::move_left,
  615. "move_right", &EditorCore::move_right,
  616. "move_to_line_start", &EditorCore::move_to_line_start,
  617. "move_to_line_end", &EditorCore::move_to_line_end,
  618. "move_forward_word", &EditorCore::move_forward_word,
  619. "move_backward_word", &EditorCore::move_backward_word,
  620. "page_up", &EditorCore::page_up,
  621. "page_down", &EditorCore::page_down,
  622. "goto_beginning", &EditorCore::goto_beginning,
  623. "goto_end", &EditorCore::goto_end,
  624. "goto_line", &EditorCore::goto_line,
  625. "load_file", &EditorCore::load_file,
  626. "split_horizontally", &EditorCore::split_horizontally,
  627. "split_vertically", &EditorCore::split_vertically,
  628. "close_window", &EditorCore::close_active_window,
  629. "next_window", &EditorCore::next_window, // Corrected
  630. "set_active_window", &EditorCore::set_active_window,
  631. "undo", &EditorCore::undo,
  632. "redo", &EditorCore::redo,
  633. "command_mode", &EditorCore::enter_command_mode,
  634. "buffer_switch_mode", &EditorCore::enter_buffer_switch_mode,
  635. "kill_buffer_mode", &EditorCore::enter_kill_buffer_mode,
  636. "find_file_mode", &EditorCore::enter_find_file_mode,
  637. "isearch_mode", &EditorCore::enter_isearch_mode,
  638. "isearch_backward_mode", &EditorCore::enter_isearch_backward_mode,
  639. "quit", &EditorCore::request_quit,
  640. // Kill ring
  641. "kill_line", &EditorCore::kill_line,
  642. "kill_region", &EditorCore::kill_region,
  643. "kill_word", &EditorCore::kill_word,
  644. "backward_kill_word", &EditorCore::backward_kill_word,
  645. "copy_region_as_kill", &EditorCore::copy_region_as_kill,
  646. "yank", &EditorCore::yank,
  647. "yank_pop", &EditorCore::yank_pop,
  648. // Registers
  649. "copy_to_register", &EditorCore::copy_to_register,
  650. "insert_register", &EditorCore::insert_register,
  651. "copy_region_to_register", &EditorCore::copy_region_to_register,
  652. "yank_from_register", &EditorCore::yank_from_register,
  653. // Keyboard macros
  654. "start_kbd_macro", &EditorCore::start_kbd_macro,
  655. "end_kbd_macro_or_call", &EditorCore::end_kbd_macro_or_call,
  656. "record_key_sequence", &EditorCore::record_key_sequence,
  657. "is_recording_macro", &EditorCore::is_recording_macro,
  658. // Rectangles
  659. "kill_rectangle", &EditorCore::kill_rectangle,
  660. "yank_rectangle", &EditorCore::yank_rectangle,
  661. "string_rectangle", &EditorCore::string_rectangle,
  662. // Buffer management
  663. "get_buffer_names", &EditorCore::get_buffer_names,
  664. "buffer", sol::property([](EditorCore& e) -> Buffer* {
  665. try {
  666. return &e.buffer();
  667. } catch (const std::exception& ex) {
  668. spdlog::error("Exception in editor.buffer: {}", ex.what());
  669. return nullptr;
  670. }
  671. }),
  672. "switch_buffer_in_window", &EditorCore::switch_buffer_in_window,
  673. "close_buffer", &EditorCore::close_buffer,
  674. "get_all_buffer_info", &EditorCore::get_all_buffer_info,
  675. // Theme management
  676. "theme_manager", sol::property([](EditorCore& e) -> ThemeManager& { return e.theme_manager(); }),
  677. "config", sol::property([](EditorCore& e) -> Config& { return e.config(); }), // Added config property
  678. "set_theme", [](EditorCore& e, const std::string& theme_name) { e.theme_manager().set_active_theme(theme_name); }, // Corrected
  679. "active_theme", sol::property([](EditorCore& e) -> const Theme& { return *e.theme_manager().active_theme(); }), // Corrected
  680. "message", &EditorCore::set_message, // Added message alias for set_message
  681. // Key binding (method on EditorCore)
  682. "bind_key", [this](EditorCore& core, std::string key, sol::object callback_or_cmd, sol::optional<std::string> description) {
  683. if (callback_or_cmd.is<std::string>()) {
  684. // Bind directly to an existing command name
  685. std::string cmd_name = callback_or_cmd.as<std::string>();
  686. core.keybinding_manager().bind(KeySequence(key), cmd_name, description.value_or(""));
  687. }
  688. else if (callback_or_cmd.is<sol::function>()) {
  689. // Create a unique command name for the Lua function
  690. std::string command_name = "lua_cmd_" + key; // Use key itself as part of name
  691. sol::function callback = callback_or_cmd.as<sol::function>();
  692. // Register a C++ CommandFunction that wraps the Lua callback
  693. core.command_system().register_command(command_name,
  694. [callback, this](CommandContext& context) -> lumacs::CommandResult { // Namespace fix
  695. try {
  696. // Pass args from context to Lua function
  697. sol::table args_table = get_lua_state().create_table(); // Use get_lua_state()
  698. const auto& args = context.get_args(); // Use getter
  699. for (size_t i = 0; i < args.size(); ++i) {
  700. args_table[i + 1] = args[i];
  701. }
  702. auto result = callback(args_table);
  703. if (result.valid()) {
  704. if (result.get_type() == sol::type::table) {
  705. sol::table res_table = result;
  706. lumacs::CommandStatus status = lumacs::CommandStatus::Success; // Namespace fix
  707. std::string message = "";
  708. if (res_table["success"].valid()) {
  709. status = res_table["success"].get<bool>() ? lumacs::CommandStatus::Success : lumacs::CommandStatus::Failure;
  710. }
  711. if (res_table["message"].valid()) {
  712. message = res_table["message"];
  713. }
  714. return lumacs::CommandResult{status, message};
  715. } else if (result.get_type() == sol::type::string) {
  716. std::string message = result.get<std::string>();
  717. return lumacs::CommandResult{lumacs::CommandStatus::Success, message};
  718. } else {
  719. return lumacs::CommandResult{lumacs::CommandStatus::Success, ""};
  720. }
  721. } else {
  722. return lumacs::CommandResult{lumacs::CommandStatus::Success, ""};
  723. }
  724. } catch (const sol::error& e) {
  725. return lumacs::CommandResult{lumacs::CommandStatus::Failure, "Lua error in key binding callback: " + std::string(e.what())};
  726. }
  727. },
  728. description.value_or(""), // Use original description
  729. false, // Not interactive directly via spec, Lua callback handles interactivity
  730. "" // No interactive spec for this wrapper command
  731. );
  732. // Now bind the key to the newly registered C++ command's name
  733. core.keybinding_manager().bind(KeySequence(key), command_name, description.value_or(""));
  734. }
  735. else {
  736. spdlog::error("bind_key: Invalid argument type for callback. Expected string or function.");
  737. }
  738. },
  739. // Register command (method on EditorCore)
  740. "register_command", [this](EditorCore& core, std::string name, std::string description, sol::function func, sol::optional<sol::table> aliases_table, sol::optional<bool> interactive, sol::optional<std::string> interactive_spec) {
  741. std::vector<std::string> aliases;
  742. if (aliases_table) {
  743. for (auto& pair : aliases_table.value()) {
  744. aliases.push_back(pair.second.as<std::string>());
  745. }
  746. }
  747. CommandFunction command_func = [this, func](CommandContext& context) -> lumacs::CommandResult { // Namespace fix
  748. try {
  749. // Pass args from context to Lua function
  750. sol::table args_table = get_lua_state().create_table(); // Use get_lua_state()
  751. const auto& args = context.get_args(); // Use getter
  752. for (size_t i = 0; i < args.size(); ++i) {
  753. args_table[i + 1] = args[i];
  754. }
  755. auto result = func(args_table);
  756. if (result.valid()) {
  757. if (result.get_type() == sol::type::table) {
  758. sol::table res_table = result;
  759. lumacs::CommandStatus status = lumacs::CommandStatus::Success; // Namespace fix
  760. std::string message = "";
  761. if (res_table["success"].valid()) {
  762. status = res_table["success"].get<bool>() ? lumacs::CommandStatus::Success : lumacs::CommandStatus::Failure;
  763. }
  764. if (res_table["message"].valid()) {
  765. message = res_table["message"];
  766. }
  767. return lumacs::CommandResult{status, message};
  768. } else if (result.get_type() == sol::type::string) {
  769. std::string message = result.get<std::string>();
  770. return lumacs::CommandResult{lumacs::CommandStatus::Success, message};
  771. } else {
  772. return lumacs::CommandResult{lumacs::CommandStatus::Success, ""};
  773. }
  774. } else {
  775. return lumacs::CommandResult{lumacs::CommandStatus::Success, ""};
  776. }
  777. } catch (const sol::error& e) {
  778. return lumacs::CommandResult{lumacs::CommandStatus::Failure, "Lua error: " + std::string(e.what())};
  779. }
  780. };
  781. core.command_system().register_command(name, command_func, description, interactive.value_or(true), interactive_spec.value_or(""), aliases);
  782. },
  783. // Define face (method on EditorCore)
  784. "define_face", [](EditorCore& core, std::string name, const FaceAttributes& attrs) {
  785. if (auto theme = core.theme_manager().active_theme()) {
  786. theme->set_face(name, attrs);
  787. }
  788. },
  789. // New: create_and_register_theme method
  790. "create_and_register_theme", [this](EditorCore& /* core */, const std::string& name) -> sol::object {
  791. return this->lua_create_and_register_theme(name);
  792. },
  793. // Command system (method on EditorCore)
  794. "execute_command", [](EditorCore& core, std::string command_name, sol::optional<sol::table> args_table) {
  795. std::vector<std::string> args;
  796. if (args_table) {
  797. for (auto& pair : args_table.value()) {
  798. args.push_back(pair.second.as<std::string>());
  799. }
  800. }
  801. auto result = core.command_system().execute(command_name, args);
  802. return std::make_tuple((bool)(result.status == lumacs::CommandStatus::Success), result.message); // Namespace fix
  803. }
  804. );
  805. }
  806. void LuaApi::register_functions() {
  807. // Global editor instance
  808. lua_["editor"] = std::ref(*core_);
  809. // Print function that goes to stderr (since stdout is used by TUI)
  810. lua_.set_function("print", [](sol::variadic_args args) { // Use set_function
  811. std::string message;
  812. for (auto arg : args) {
  813. message += lua_tostring(arg.lua_state(), arg.stack_index());
  814. message += "\t";
  815. }
  816. spdlog::info("[Lua] {}", message);
  817. });
  818. // Command system functions
  819. lua_.set_function("execute_command_line", [this](std::string command_string) { // Use set_function
  820. auto result = core_->minibuffer_manager().parse_and_execute_command_string(command_string);
  821. return std::make_tuple((bool)(result.status == lumacs::CommandStatus::Success), result.message); // Namespace fix
  822. });
  823. lua_.set_function("get_command_names", [this]() { // Use set_function
  824. return core_->command_system().get_command_names();
  825. });
  826. // New binding for executing interactive commands from Lua
  827. lua_.set_function("execute_interactive_command", [this](std::string command_name) { // Use set_function
  828. auto result = core_->command_system().execute_interactive(command_name);
  829. return std::make_tuple((bool)(result.status == lumacs::CommandStatus::Success), result.message); // Namespace fix
  830. });
  831. // New binding for getting interactive spec
  832. lua_.set_function("get_command_interactive_spec", [this](std::string command_name) { // Use set_function
  833. return core_->command_system().get_command_interactive_spec(command_name);
  834. });
  835. // New manual bindings for core interactions
  836. lua_.set_function("lumacs_get_active_buffer_line_count", [this]() { // Use set_function
  837. return this->get_active_buffer_line_count_lua();
  838. });
  839. lua_.set_function("lumacs_editor_move_right", [this]() { // Use set_function
  840. this->lua_editor_move_right();
  841. });
  842. lua_.set_function("lumacs_editor_new_buffer", [this](const std::string& name) { // Use set_function
  843. this->lua_editor_new_buffer(name);
  844. });
  845. lua_.set_function("lumacs_editor_get_buffer_by_name", [this](const std::string& name) { // Use set_function
  846. return this->lua_editor_get_buffer_by_name(name);
  847. });
  848. lua_.set_function("lumacs_editor_set_message", [this](const std::string& message) { // Use set_function
  849. this->lua_editor_set_message(message);
  850. });
  851. lua_.set_function("lumacs_config_set_string", [this](const std::string& key, const std::string& value) { // Use set_function
  852. this->lua_config_set_string(key, value);
  853. });
  854. lua_.set_function("lumacs_config_get_string", [this](const std::string& key, const std::string& default_val) { // Use set_function
  855. return this->lua_config_get_string(key, default_val);
  856. });
  857. lua_.set_function("lumacs_config_set_bool", [this](const std::string& key, bool value) { // Use set_function
  858. this->lua_config_set_bool(key, value);
  859. });
  860. lua_.set_function("lumacs_config_get_bool", [this](const std::string& key, bool default_val) { // Use set_function
  861. return this->lua_config_get_bool(key, default_val);
  862. });
  863. lua_.set_function("lumacs_config_set_int", [this](const std::string& key, int value) { // Use set_function
  864. this->lua_config_set_int(key, value);
  865. });
  866. lua_.set_function("lumacs_config_get_int", [this](const std::string& key, int default_val) { // Use set_function
  867. return this->lua_config_get_int(key, default_val);
  868. });
  869. // Completion functions
  870. lua_.set_function("get_completion_candidates", [this](int mode_int, std::string input) { // Use set_function
  871. MinibufferMode mode = static_cast<MinibufferMode>(mode_int);
  872. auto candidates = (*core_).completion_system().get_candidates_for_mode(mode, input);
  873. sol::table result = lua_.create_table();
  874. for (size_t i = 0; i < candidates.size(); ++i) {
  875. sol::table candidate = lua_.create_table();
  876. candidate["text"] = candidates[i].text;
  877. candidate["score"] = candidates[i].score;
  878. candidate["description"] = candidates[i].description;
  879. result[i + 1] = candidate;
  880. }
  881. return result;
  882. });
  883. lua_.set_function("register_completion_provider", [this](int mode_int, sol::function provider_func) { // Use set_function
  884. MinibufferMode mode = static_cast<MinibufferMode>(mode_int);
  885. // Pass the sol::function directly to the LambdaCompletionSource constructor
  886. (*core_).completion_system().register_source(mode, std::make_unique<LambdaCompletionSource>(provider_func));
  887. });
  888. // Plugin Manager functions
  889. lua_["plugin_manager"] = std::ref(core_->plugin_manager()); // Expose PluginManager to Lua
  890. // PluginManager types
  891. lua_.new_usertype<PluginManager>("PluginManager",
  892. sol::no_constructor,
  893. "discover_plugins", &PluginManager::discover_plugins,
  894. "load_plugin", &PluginManager::load_plugin,
  895. "unload_plugin", &PluginManager::unload_plugin,
  896. "get_discovered_plugins", sol::overload(
  897. [] (PluginManager& pm) { return pm.get_discovered_plugins(); },
  898. [] (PluginManager& pm, sol::this_state s) {
  899. // Convert to Lua table
  900. sol::state_view lua(s);
  901. sol::table t = lua.create_table();
  902. auto names = pm.get_discovered_plugins();
  903. for(size_t i = 0; i < names.size(); ++i) {
  904. t[i+1] = names[i];
  905. }
  906. return t;
  907. }
  908. ),
  909. "get_loaded_plugins", sol::overload(
  910. [] (PluginManager& pm) { return pm.get_loaded_plugins(); },
  911. [] (PluginManager& pm, sol::this_state s) {
  912. // Convert to Lua table
  913. sol::state_view lua(s);
  914. sol::table t = lua.create_table();
  915. auto names = pm.get_loaded_plugins();
  916. for(size_t i = 0; i < names.size(); ++i) {
  917. t[i+1] = names[i];
  918. }
  919. return t;
  920. }
  921. )
  922. );
  923. }
  924. } // namespace lumacs