#include "lumacs/lua_api.hpp" #include "lumacs/command_system.hpp" #include "lumacs/minibuffer_mode.hpp" // Added for MinibufferMode enum binding #include "lumacs/completion_system.hpp" // Added for CompletionSystem access #include "lumacs/plugin_manager.hpp" // Added for PluginManager access #include "lumacs/buffer_manager.hpp" // Added for BufferManager::BufferInfo #include "lumacs/logger.hpp" #include "../src/defaults.hpp" // Include embedded defaults #include #include // Kept for now if needed by sol2 internals or other parts, but trying to remove direct usage #include #include // For std::filesystem::path namespace lumacs { // Helper class to adapt a lambda/function to ICompletionSource class LambdaCompletionSource : public ICompletionSource { public: explicit LambdaCompletionSource(sol::function provider) : provider_(std::move(provider)) {} std::vector get_candidates(const std::string& input) override { sol::protected_function_result lua_result = provider_(input); // Get the raw Lua result std::vector candidates; if (lua_result.valid()) { sol::type result_type = lua_result.get_type(); if (result_type == sol::type::table) { sol::table result_table = lua_result; // Convert to sol::table for (auto& pair : result_table) { if (pair.second.get_type() == sol::type::table) { sol::table candidate_table = pair.second; std::string text = ""; int score = 50; std::string desc = ""; if (candidate_table["text"].valid()) { text = candidate_table["text"]; } if (candidate_table["score"].valid()) { score = candidate_table["score"]; } if (candidate_table["description"].valid()) { desc = candidate_table["description"].get(); // Get the string } candidates.emplace_back(text, score, "", desc); // Fix: Explicitly pass empty string for display_text } } } } return candidates; } private: sol::function provider_; }; CommandFunction LuaApi::wrap_lua_callback(sol::function callback, const std::string& error_context) { return [callback, error_context, this](CommandContext& context) -> CommandResult { try { // Pass args from context to Lua function sol::table args_table = get_lua_state().create_table(); const auto& args = context.get_args(); for (size_t i = 0; i < args.size(); ++i) { args_table[i + 1] = args[i]; } auto result = callback(args_table); if (result.valid()) { if (result.get_type() == sol::type::table) { sol::table res_table = result; CommandStatus status = CommandStatus::Success; std::string message; if (res_table["success"].valid()) { status = res_table["success"].get() ? CommandStatus::Success : CommandStatus::Failure; } if (res_table["message"].valid()) { message = res_table["message"]; } return CommandResult{status, message}; } else if (result.get_type() == sol::type::string) { return CommandResult{CommandStatus::Success, result.get()}; } else { return CommandResult{CommandStatus::Success, ""}; } } else { return CommandResult{CommandStatus::Success, ""}; } } catch (const sol::error& e) { return CommandResult{CommandStatus::Failure, "Lua error in " + error_context + ": " + std::string(e.what())}; } }; } LuaApi::LuaApi() { lua_.open_libraries( sol::lib::base, sol::lib::package, sol::lib::string, sol::lib::math, sol::lib::table, sol::lib::io, sol::lib::os, sol::lib::debug // Open debug library early for MobDebug ); } void LuaApi::set_core(EditorCore& core) { core_ = &core; setup_api(); } bool LuaApi::load_file(const std::filesystem::path& path) { try { spdlog::debug("Loading Lua file: {}", path.string()); lua_.script_file(path.string()); spdlog::debug("Lua file loaded successfully"); spdlog::info("Loaded Lua file: {}", path.string()); return true; } catch (const sol::error& e) { spdlog::error("Lua error loading {}: {}", path.string(), e.what()); return false; } catch (const std::exception& e) { spdlog::error("Exception loading {}: {}", path.string(), e.what()); return false; } } bool LuaApi::execute(std::string_view code) { try { lua_.script(code); return true; } catch (const sol::error& e) { spdlog::error("Lua error: {}", e.what()); return false; } } bool LuaApi::load_init_file() { // 1. ALWAYS load the embedded defaults first spdlog::info("Loading embedded defaults..."); try { lua_.script(LUA_DEFAULTS); } catch (const sol::error& e) { spdlog::critical("FAILED TO LOAD EMBEDDED DEFAULTS: {}", e.what()); // Continue anyway } // Get home directory const char* home = getenv("HOME"); std::string home_dir = home ? home : "."; // Try multiple locations in order std::vector search_paths = { std::filesystem::current_path() / "init.lua", std::filesystem::path(home_dir) / ".config" / "lumacs" / "init.lua", std::filesystem::path(home_dir) / ".lumacs" / "init.lua", }; for (const auto& path : search_paths) { if (std::filesystem::exists(path)) { spdlog::info("Found init file: {}", path.string()); return load_file(path); } } spdlog::warn("No init.lua found (searched: ./init.lua, ~/.config/lumacs/init.lua, ~/.lumacs/init.lua). Using defaults only."); return true; // Return true because defaults were loaded } void LuaApi::bind_key(std::string key, sol::function callback, std::string description) { spdlog::debug("Registering key binding: {}", key); // Create a unique command name for the Lua function std::string command_name = "lua_cmd_" + key; // Register a C++ CommandFunction that wraps the Lua callback core_->command_system().register_command( command_name, wrap_lua_callback(callback, "key binding '" + key + "'"), description, false, // Not interactive directly via spec "" // No interactive spec ); // Now bind the key to the newly registered C++ command's name core_->keybinding_manager().bind(KeySequence(key), command_name, description); } KeyProcessingResult LuaApi::process_key(const std::string& key) { // Corrected return type return core_->keybinding_manager().process_key(key); } // Legacy methods for backward compatibility bool LuaApi::has_key_binding(const std::string& key) const { return legacy_key_bindings_.find(key) != legacy_key_bindings_.end() || core_->keybinding_manager().has_exact_binding(KeySequence(key)); } bool LuaApi::execute_key_binding(const std::string& key) { auto it = legacy_key_bindings_.find(key); if (it == legacy_key_bindings_.end()) { return false; } try { it->second(); return true; } catch (const sol::error& e) { spdlog::error("Lua error executing key binding '{}': {}", key, e.what()); return false; } } // Manual C functions implementations int LuaApi::get_active_buffer_line_count_lua() { if (!core_ || !core_->active_window() || !core_->active_window()->buffer_ptr()) { spdlog::warn("get_active_buffer_line_count_lua: Core, active window or buffer is null!"); return 0; } return static_cast(core_->buffer().line_count()); } void LuaApi::lua_editor_move_right() { if (!core_) { spdlog::warn("lua_editor_move_right: Core is null!"); return; } core_->move_right(); } void LuaApi::lua_editor_new_buffer(const std::string& name) { if (!core_) { spdlog::warn("lua_editor_new_buffer: Core is null!"); return; } core_->new_buffer(name); } sol::optional LuaApi::lua_editor_get_buffer_by_name(const std::string& name) { if (!core_) { spdlog::warn("lua_editor_get_buffer_by_name: Core is null!"); return sol::nullopt; } if (auto buffer = core_->get_buffer_by_name(name)) { return buffer->name(); // Return name as a string to avoid complex Buffer usertype issues } return sol::nullopt; } void LuaApi::lua_editor_set_message(const std::string& message, const std::string& severity_str) { if (!core_) { spdlog::warn("lua_editor_set_message: Core is null!"); return; } // Parse severity string MessageSeverity severity = MessageSeverity::Info; if (severity_str == "warning" || severity_str == "warn") { severity = MessageSeverity::Warning; } else if (severity_str == "error") { severity = MessageSeverity::Error; } else if (severity_str == "debug") { severity = MessageSeverity::Debug; } core_->set_message(message, severity); } void LuaApi::lua_config_set_string(const std::string& key, const std::string& value) { if (!core_) { spdlog::warn("lua_config_set_string: Core is null!"); return; } core_->config().set(key, value); } std::string LuaApi::lua_config_get_string(const std::string& key, const std::string& default_val) { if (!core_) { spdlog::warn("lua_config_get_string: Core is null!"); return default_val; } return core_->config().get(key, default_val); } void LuaApi::lua_config_set_bool(const std::string& key, bool value) { if (!core_) { spdlog::warn("lua_config_set_bool: Core is null!"); return; } core_->config().set(key, value); } bool LuaApi::lua_config_get_bool(const std::string& key, bool default_val) { if (!core_) { spdlog::warn("lua_config_get_bool: Core is null!"); return default_val; } return core_->config().get(key, default_val); } void LuaApi::lua_config_set_int(const std::string& key, int value) { if (!core_) { spdlog::warn("lua_config_set_int: Core is null!"); return; } core_->config().set(key, value); } int LuaApi::lua_config_get_int(const std::string& key, int default_val) { if (!core_) { spdlog::warn("lua_config_get_int: Core is null!"); return default_val; } return core_->config().get(key, default_val); } // New: Implement lua_create_and_register_theme sol::object LuaApi::lua_create_and_register_theme(const std::string& name) { if (!core_) { spdlog::warn("lua_create_and_register_theme: Core is null!"); return sol::lua_nil; // Return nil in Lua if core is not available } // Create a new Theme shared_ptr auto new_theme = std::make_shared(name); // Register it with the ThemeManager core_->theme_manager().register_theme(new_theme); spdlog::info("Registered new theme: {}", name); // Return the new_theme as a sol::object. Sol2 will automatically handle the shared_ptr and usertype conversion. // The Theme usertype is defined in register_types(), so we need access to the lua_ state. // We pass a reference to the shared_ptr so that the Lua object holds a reference, preventing early destruction. return sol::make_object(lua_, new_theme); } void LuaApi::setup_api() { register_types(); register_functions(); // Set version info and add constructor functions to lumacs namespace auto lumacs_table = lua_.create_table(); lumacs_table["version"] = "0.1.0"; lumacs_table["name"] = "Lumacs"; // Add constructor functions lumacs_table["Position"] = [](size_t line, size_t column) { return Position{line, column}; }; lumacs_table["Range"] = [](Position start, Position end) { return Range{start, end}; }; lumacs_table["Color"] = [](int r, int g, int b) { // Explicitly define Color constructor return Color(r, g, b); }; lumacs_table["TextAttribute"] = [](lumacs::TextAttribute::ColorType color, int style) { return TextAttribute{color, style}; }; lumacs_table["FaceAttributes"] = []() { return FaceAttributes(); }; // Explicitly define FaceAttributes constructor lumacs_table["StyledRange"] = [](Range range, TextAttribute attr) { return StyledRange{range, attr}; }; // Add enums to lumacs namespace for convenience lumacs_table["ColorType"] = lua_["ColorType"]; lumacs_table["Style"] = lua_["Style"]; lumacs_table["BufferEvent"] = lua_["BufferEvent"]; lumacs_table["ThemeElement"] = lua_["ThemeElement"]; lumacs_table["FontWeight"] = lua_["FontWeight"]; lumacs_table["FontSlant"] = lua_["FontSlant"]; // lumacs_table["FaceAttributes"] = lua_["FaceAttributes"]; // Not needed here if constructor is explicit lumacs_table["MinibufferMode"] = lua_["MinibufferMode"]; // Expose MinibufferMode to Lua lua_["lumacs"] = lumacs_table; // Initialize MobDebug if enabled if (core_->config().get("debug_lua", false)) { // Corrected Config access spdlog::debug("Lua debugging enabled."); std::string debug_host = core_->config().get("debug_host", "127.0.0.1"); // Corrected Config access int debug_port = core_->config().get("debug_port", 8171); // Corrected Config access try { // MobDebug requires socket library, which is already opened in constructor. // Explicitly require 'mobdebug' module sol::optional mobdebug = lua_["require"]("mobdebug"); if (mobdebug) { spdlog::debug("MobDebug module loaded."); sol::function mobdebug_start = mobdebug.value()["start"]; if (mobdebug_start.valid()) { mobdebug_start(debug_host, debug_port); spdlog::info("MobDebug started on {}:{}", debug_host, debug_port); } else { spdlog::error("MobDebug.start function not found."); } } else { spdlog::error("Failed to load MobDebug module. Is LuaRocks installed and configured?"); } } catch (const sol::error& e) { spdlog::error("Lua error during MobDebug initialization: {}", e.what()); } } } void LuaApi::register_types() { // Position type lua_.new_usertype("Position", sol::constructors(), "line", &Position::line, "column", &Position::column ); // Range type lua_.new_usertype("Range", sol::constructors(), "start", &Range::start, "end", &Range::end ); // Font weight lua_.new_enum("FontWeight", { {"Normal", FontWeight::Normal}, {"Bold", FontWeight::Bold}, {"Light", FontWeight::Light} } ); // Font slant lua_.new_enum("FontSlant", { {"Normal", FontSlant::Normal}, {"Italic", FontSlant::Italic}, {"Oblique", FontSlant::Oblique} } ); // FaceAttributes type lua_.new_usertype("FaceAttributes", sol::constructors(), "family", &FaceAttributes::family, "height", &FaceAttributes::height, "weight", &FaceAttributes::weight, "slant", &FaceAttributes::slant, "foreground", &FaceAttributes::foreground, "background", &FaceAttributes::background, "underline", &FaceAttributes::underline, "inverse", &FaceAttributes::inverse, "inherit", &FaceAttributes::inherit ); // TextAttribute type lua_.new_usertype("TextAttribute", sol::constructors(), // Namespace fix "face_name", &TextAttribute::face_name ); // TextAttribute::ColorType enum lua_.new_enum("ColorType", { {"Default", TextAttribute::ColorType::Default}, {"Keyword", TextAttribute::ColorType::Keyword}, {"String", TextAttribute::ColorType::String}, {"Comment", TextAttribute::ColorType::Comment}, {"Function", TextAttribute::ColorType::Function}, {"Type", TextAttribute::ColorType::Type}, {"Number", TextAttribute::ColorType::Number}, {"Operator", TextAttribute::ColorType::Operator}, {"Variable", TextAttribute::ColorType::Variable}, {"Constant", TextAttribute::ColorType::Constant}, {"Error", TextAttribute::ColorType::Error} } ); // TextAttribute::Style enum lua_.new_enum("Style", { {"Normal", TextAttribute::Style::Normal}, {"Bold", TextAttribute::Style::Bold}, {"Italic", TextAttribute::Style::Italic}, {"Underline", TextAttribute::Style::Underline} } ); // BufferEvent enum lua_.new_enum("BufferEvent", { {"Created", BufferEvent::Created}, {"Loaded", BufferEvent::Loaded}, {"Closed", BufferEvent::Closed}, {"BeforeChange", BufferEvent::BeforeChange}, {"AfterChange", BufferEvent::AfterChange}, {"LineChanged", BufferEvent::LineChanged}, {"BeforeSave", BufferEvent::BeforeSave}, {"AfterSave", BufferEvent::AfterSave}, {"LanguageChanged", BufferEvent::LanguageChanged} } ); // BufferEventData type lua_.new_usertype("BufferEventData", sol::no_constructor, "event", &BufferEventData::event, "line", &BufferEventData::line, "language", &BufferEventData::language ); // StyledRange type lua_.new_usertype("StyledRange", sol::constructors(), "range", &StyledRange::range, "attr", &StyledRange::attr ); // BufferInfo type lua_.new_usertype("BufferInfo", // Corrected sol::no_constructor, "name", &BufferManager::BufferInfo::name, // Corrected "size", &BufferManager::BufferInfo::size, // Corrected "modified", &BufferManager::BufferInfo::modified, // Corrected "mode", &BufferManager::BufferInfo::mode, // Corrected "filepath", sol::property([](const BufferManager::BufferInfo& info) -> sol::optional { // Corrected if (info.filepath.has_value()) { return info.filepath->string(); } return sol::nullopt; }) ); // Color type lua_.new_usertype("Color", sol::constructors(), "r", &Color::r, "g", &Color::g, "b", &Color::b, "from_hex", &Color::from_hex ); // ThemeElement enum lua_.new_enum("ThemeElement", { {"Normal", ThemeElement::Normal}, {"Keyword", ThemeElement::Keyword}, {"String", ThemeElement::String}, {"Comment", ThemeElement::Comment}, {"Function", ThemeElement::Function}, {"Type", ThemeElement::Type}, {"Number", ThemeElement::Number}, {"Constant", ThemeElement::Constant}, {"Error", ThemeElement::Error}, {"StatusLine", ThemeElement::StatusLine}, {"StatusLineInactive", ThemeElement::StatusLineInactive}, {"MessageLine", ThemeElement::MessageLine}, {"LineNumber", ThemeElement::LineNumber}, {"Cursor", ThemeElement::Cursor}, {"Selection", ThemeElement::Selection}, {"SearchMatch", ThemeElement::SearchMatch}, {"SearchFail", ThemeElement::SearchFail}, // Corrected {"WindowBorder", ThemeElement::WindowBorder}, {"WindowBorderActive", ThemeElement::WindowBorderActive}, {"Background", ThemeElement::Background} } ); // Theme type lua_.new_usertype("Theme", sol::no_constructor, "set_color", [](Theme& theme, ThemeElement element, Color fg, sol::optional bg) { Color bg_color = bg.value_or(Color(-1, -1, -1)); theme.set_color(element, fg, bg_color); }, "set_face", &Theme::set_face, "get_fg_color", &Theme::get_fg_color, "get_bg_color", &Theme::get_bg_color, "name", &Theme::name ); // ThemeManager type lua_.new_usertype("ThemeManager", sol::no_constructor, "set_active_theme", &ThemeManager::set_active_theme, "theme_names", &ThemeManager::theme_names, "active_theme", &ThemeManager::active_theme ); // Config type lua_.new_usertype("Config", sol::no_constructor, "set", [](Config& config, const std::string& key, sol::object value) { if (value.is()) { config.set(key, value.as()); } else if (value.is()) { config.set(key, value.as()); } else if (value.is()) { config.set(key, value.as()); } }, "get_bool", [](const Config& config, const std::string& key, sol::optional default_val) { return config.get(key, default_val.value_or(false)); }, "get_int", [](const Config& config, const std::string& key, sol::optional default_val) { return config.get(key, default_val.value_or(0)); }, "get_string", [](const Config& config, const std::string& key, sol::optional default_val) { return config.get(key, default_val.value_or("")); }, "has", &Config::has, "keys", &Config::keys ); // MinibufferMode enum lua_.new_enum("MinibufferMode", { {"None", MinibufferMode::None}, {"Command", MinibufferMode::Command}, {"FilePath", MinibufferMode::FilePath}, {"BufferName", MinibufferMode::BufferName}, {"ThemeName", MinibufferMode::ThemeName}, {"ISearch", MinibufferMode::ISearch}, {"ystring", MinibufferMode::ystring}, {"y_or_n_p", MinibufferMode::y_or_n_p} }); // Buffer type lua_.new_usertype("Buffer", sol::no_constructor, "name", &Buffer::name, "set_name", &Buffer::set_name, "filepath", [](const Buffer& b) -> sol::optional { auto path = b.file_path(); if (!path.has_value()) return sol::nullopt; return path->string(); }, "line_count", &Buffer::line_count, "is_modified", &Buffer::is_modified, "line", [](Buffer& b, size_t idx) { if (idx >= b.line_count()) return std::string(""); return b.line(idx); }, "content", &Buffer::content, "insert", &Buffer::insert, "insert_newline", &Buffer::insert_newline, "erase", &Buffer::erase, "erase_char", &Buffer::erase_char, "clear", &Buffer::clear, "save", &Buffer::save, // Mark & Region "set_mark", &Buffer::set_mark, "deactivate_mark", &Buffer::deactivate_mark, "has_mark", [](const Buffer& b) { return b.mark().has_value(); }, "mark", sol::property([](const Buffer& b) -> sol::optional { auto m = b.mark(); if (m.has_value()) return *m; return sol::nullopt; }), "get_region", [](Buffer& b, Position p) -> sol::optional { auto r = b.get_region(p); if (r.has_value()) return *r; return sol::nullopt; }, "get_text_in_range", &Buffer::get_text_in_range, "get_all_text", &Buffer::content, // Alias "replace", [](Buffer& b, Range r, const std::string& text) { b.erase(r); b.insert(r.start, text); }, // Styling "set_style", &Buffer::set_style, "clear_styles", &Buffer::clear_styles, "get_line_styles", &Buffer::get_line_styles, // Events "on_buffer_event", [](Buffer& b, sol::function callback) { b.on_buffer_event([callback](const BufferEventData& data) { try { callback(data); } catch (const sol::error& e) { spdlog::error("Lua error in buffer event listener: {}", e.what()); } }); }, // Search "find", [](Buffer& b, const std::string& query, sol::optional start_pos) -> sol::optional { auto r = b.find(query, start_pos.value_or(Position{0,0})); if (r.has_value()) return *r; return sol::nullopt; } ); // EditorCore type lua_.new_usertype("EditorCore", sol::no_constructor, "cursor", sol::property(&EditorCore::cursor, &EditorCore::set_cursor), // Added cursor property "set_cursor", &EditorCore::set_cursor, // Explicit setter method // Movement "move_up", &EditorCore::move_up, "move_down", &EditorCore::move_down, "move_left", &EditorCore::move_left, "move_right", &EditorCore::move_right, "move_to_line_start", &EditorCore::move_to_line_start, "move_to_line_end", &EditorCore::move_to_line_end, "move_forward_word", &EditorCore::move_forward_word, "move_backward_word", &EditorCore::move_backward_word, "page_up", &EditorCore::page_up, "page_down", &EditorCore::page_down, "goto_beginning", &EditorCore::goto_beginning, "goto_end", &EditorCore::goto_end, "goto_line", &EditorCore::goto_line, "load_file", &EditorCore::load_file, "split_horizontally", &EditorCore::split_horizontally, "split_vertically", &EditorCore::split_vertically, "close_window", &EditorCore::close_active_window, "next_window", &EditorCore::next_window, // Corrected "set_active_window", &EditorCore::set_active_window, "undo", &EditorCore::undo, "redo", &EditorCore::redo, "command_mode", &EditorCore::enter_command_mode, "buffer_switch_mode", &EditorCore::enter_buffer_switch_mode, "kill_buffer_mode", &EditorCore::enter_kill_buffer_mode, "find_file_mode", &EditorCore::enter_find_file_mode, "isearch_mode", &EditorCore::enter_isearch_mode, "isearch_backward_mode", &EditorCore::enter_isearch_backward_mode, "quit", &EditorCore::request_quit, // Kill ring "kill_line", &EditorCore::kill_line, "kill_region", &EditorCore::kill_region, "kill_word", &EditorCore::kill_word, "backward_kill_word", &EditorCore::backward_kill_word, "copy_region_as_kill", &EditorCore::copy_region_as_kill, "yank", &EditorCore::yank, "yank_pop", &EditorCore::yank_pop, // Registers "copy_to_register", &EditorCore::copy_to_register, "insert_register", &EditorCore::insert_register, "copy_region_to_register", &EditorCore::copy_region_to_register, "yank_from_register", &EditorCore::yank_from_register, // Keyboard macros "start_kbd_macro", &EditorCore::start_kbd_macro, "end_kbd_macro_or_call", &EditorCore::end_kbd_macro_or_call, "record_key_sequence", &EditorCore::record_key_sequence, "is_recording_macro", &EditorCore::is_recording_macro, // Rectangles "kill_rectangle", &EditorCore::kill_rectangle, "yank_rectangle", &EditorCore::yank_rectangle, "string_rectangle", &EditorCore::string_rectangle, // Buffer management "get_buffer_names", &EditorCore::get_buffer_names, "buffer", sol::property([](EditorCore& e) -> Buffer* { try { return &e.buffer(); } catch (const std::exception& ex) { spdlog::error("Exception in editor.buffer: {}", ex.what()); return nullptr; } }), "switch_buffer_in_window", &EditorCore::switch_buffer_in_window, "close_buffer", &EditorCore::close_buffer, "get_all_buffer_info", &EditorCore::get_all_buffer_info, // Theme management "theme_manager", sol::property([](EditorCore& e) -> ThemeManager& { return e.theme_manager(); }), "config", sol::property([](EditorCore& e) -> Config& { return e.config(); }), // Added config property "set_theme", [](EditorCore& e, const std::string& theme_name) { e.set_theme(theme_name); }, // Corrected "active_theme", sol::property([](EditorCore& e) -> const Theme& { return *e.theme_manager().active_theme(); }), // Corrected "message", [](EditorCore& e, const std::string& msg, sol::optional severity_str) { MessageSeverity severity = MessageSeverity::Info; if (severity_str) { const std::string& s = *severity_str; if (s == "warning" || s == "warn") severity = MessageSeverity::Warning; else if (s == "error") severity = MessageSeverity::Error; else if (s == "debug") severity = MessageSeverity::Debug; } e.set_message(msg, severity); }, // Message with optional severity // Message history navigation "previous_message", &EditorCore::previous_message, "next_message", &EditorCore::next_message, "exit_message_history", &EditorCore::exit_message_history, "is_browsing_message_history", &EditorCore::is_browsing_message_history, "message_history_size", &EditorCore::message_history_size, // Key binding information "get_all_bindings", [this](EditorCore& core) { sol::table bindings = lua_.create_table(); auto all = core.keybinding_manager().get_all_bindings(); int idx = 1; for (const auto& binding : all) { sol::table b = lua_.create_table(); b["sequence"] = binding.sequence.to_string(); b["command"] = binding.command_name; b["description"] = binding.description; bindings[idx++] = b; } return bindings; }, "is_building_sequence", [](EditorCore& core) { return core.keybinding_manager().is_building_sequence(); }, "current_sequence", [](EditorCore& core) { return core.keybinding_manager().current_sequence_display(); }, "has_prefix_bindings", [](EditorCore& core, const std::string& prefix) { return core.keybinding_manager().has_prefix_bindings(KeySequence(prefix)); }, // Extended echo area (for packages like which-key) "set_echo_area", [](EditorCore& core, sol::table lines_table) { std::vector lines; for (size_t i = 1; i <= lines_table.size(); ++i) { sol::optional line = lines_table[i]; if (line) { lines.push_back(*line); } } core.set_echo_area_lines(lines); }, "clear_echo_area", [](EditorCore& core) { core.clear_echo_area_lines(); }, "echo_area_lines", [this](EditorCore& core) { sol::table result = lua_.create_table(); const auto& lines = core.echo_area_lines(); for (size_t i = 0; i < lines.size(); ++i) { result[i + 1] = lines[i]; } return result; }, // Idle time tracking (for packages like which-key) "idle_time_ms", [](EditorCore& core) { return core.idle_time_ms(); }, "notify_activity", [](EditorCore& core) { core.notify_user_activity(); }, // Key binding (method on EditorCore) "bind_key", [this](EditorCore& core, std::string key, sol::object callback_or_cmd, sol::optional description) { if (callback_or_cmd.is()) { // Bind directly to an existing command name std::string cmd_name = callback_or_cmd.as(); core.keybinding_manager().bind(KeySequence(key), cmd_name, description.value_or("")); } else if (callback_or_cmd.is()) { // Create a unique command name for the Lua function std::string command_name = "lua_cmd_" + key; sol::function callback = callback_or_cmd.as(); // Register using the helper function core.command_system().register_command( command_name, wrap_lua_callback(callback, "key binding '" + key + "'"), description.value_or(""), false, // Not interactive "" // No interactive spec ); core.keybinding_manager().bind(KeySequence(key), command_name, description.value_or("")); } else { spdlog::error("bind_key: Invalid argument type for callback. Expected string or function."); } }, // Register command (method on EditorCore) "register_command", [this](EditorCore& core, std::string name, std::string description, sol::function func, sol::optional aliases_table, sol::optional interactive, sol::optional interactive_spec) { std::vector aliases; if (aliases_table) { for (auto& pair : aliases_table.value()) { aliases.push_back(pair.second.as()); } } core.command_system().register_command( name, wrap_lua_callback(func, "command '" + name + "'"), description, interactive.value_or(true), interactive_spec.value_or(""), aliases ); }, // Define face (method on EditorCore) "define_face", [](EditorCore& core, std::string name, const FaceAttributes& attrs) { if (auto theme = core.theme_manager().active_theme()) { theme->set_face(name, attrs); } }, // New: create_and_register_theme method "create_and_register_theme", [this](EditorCore& /* core */, const std::string& name) -> sol::object { return this->lua_create_and_register_theme(name); }, // Command system (method on EditorCore) "execute_command", [](EditorCore& core, std::string command_name, sol::optional args_table) { std::vector args; if (args_table) { for (auto& pair : args_table.value()) { args.push_back(pair.second.as()); } } auto result = core.command_system().execute(command_name, args); return std::make_tuple((bool)(result.status == lumacs::CommandStatus::Success), result.message); // Namespace fix } ); } void LuaApi::register_functions() { // Global editor instance lua_["editor"] = std::ref(*core_); // Print function that goes to stderr (since stdout is used by TUI) lua_.set_function("print", [](sol::variadic_args args) { // Use set_function std::string message; for (auto arg : args) { message += lua_tostring(arg.lua_state(), arg.stack_index()); message += "\t"; } spdlog::info("[Lua] {}", message); }); // Command system functions lua_.set_function("execute_command_line", [this](std::string command_string) { // Use set_function auto result = core_->minibuffer_manager().parse_and_execute_command_string(command_string); return std::make_tuple((bool)(result.status == lumacs::CommandStatus::Success), result.message); // Namespace fix }); lua_.set_function("get_command_names", [this]() { // Use set_function return core_->command_system().get_command_names(); }); // New binding for executing interactive commands from Lua lua_.set_function("execute_interactive_command", [this](std::string command_name) { // Use set_function auto result = core_->command_system().execute_interactive(command_name); return std::make_tuple((bool)(result.status == lumacs::CommandStatus::Success), result.message); // Namespace fix }); // New binding for getting interactive spec lua_.set_function("get_command_interactive_spec", [this](std::string command_name) { // Use set_function return core_->command_system().get_command_interactive_spec(command_name); }); // New manual bindings for core interactions lua_.set_function("lumacs_get_active_buffer_line_count", [this]() { // Use set_function return this->get_active_buffer_line_count_lua(); }); lua_.set_function("lumacs_editor_move_right", [this]() { // Use set_function this->lua_editor_move_right(); }); lua_.set_function("lumacs_editor_new_buffer", [this](const std::string& name) { // Use set_function this->lua_editor_new_buffer(name); }); lua_.set_function("lumacs_editor_get_buffer_by_name", [this](const std::string& name) { // Use set_function return this->lua_editor_get_buffer_by_name(name); }); lua_.set_function("lumacs_editor_set_message", [this](const std::string& message, sol::optional severity) { // Use set_function this->lua_editor_set_message(message, severity.value_or("info")); }); lua_.set_function("lumacs_editor_previous_message", [this]() { core_->previous_message(); }); lua_.set_function("lumacs_editor_next_message", [this]() { core_->next_message(); }); lua_.set_function("lumacs_editor_exit_message_history", [this]() { core_->exit_message_history(); }); lua_.set_function("lumacs_config_set_string", [this](const std::string& key, const std::string& value) { // Use set_function this->lua_config_set_string(key, value); }); lua_.set_function("lumacs_config_get_string", [this](const std::string& key, const std::string& default_val) { // Use set_function return this->lua_config_get_string(key, default_val); }); lua_.set_function("lumacs_config_set_bool", [this](const std::string& key, bool value) { // Use set_function this->lua_config_set_bool(key, value); }); lua_.set_function("lumacs_config_get_bool", [this](const std::string& key, bool default_val) { // Use set_function return this->lua_config_get_bool(key, default_val); }); lua_.set_function("lumacs_config_set_int", [this](const std::string& key, int value) { // Use set_function this->lua_config_set_int(key, value); }); lua_.set_function("lumacs_config_get_int", [this](const std::string& key, int default_val) { // Use set_function return this->lua_config_get_int(key, default_val); }); // Completion functions lua_.set_function("get_completion_candidates", [this](int mode_int, std::string input) { // Use set_function MinibufferMode mode = static_cast(mode_int); auto candidates = (*core_).completion_system().get_candidates_for_mode(mode, input); sol::table result = lua_.create_table(); for (size_t i = 0; i < candidates.size(); ++i) { sol::table candidate = lua_.create_table(); candidate["text"] = candidates[i].text; candidate["score"] = candidates[i].score; candidate["description"] = candidates[i].description; result[i + 1] = candidate; } return result; }); lua_.set_function("register_completion_provider", [this](int mode_int, sol::function provider_func) { // Use set_function MinibufferMode mode = static_cast(mode_int); // Pass the sol::function directly to the LambdaCompletionSource constructor (*core_).completion_system().register_source(mode, std::make_unique(provider_func)); }); // Plugin Manager functions lua_["plugin_manager"] = std::ref(core_->plugin_manager()); // Expose PluginManager to Lua // PluginManager types lua_.new_usertype("PluginManager", sol::no_constructor, "discover_plugins", &PluginManager::discover_plugins, "load_plugin", &PluginManager::load_plugin, "unload_plugin", &PluginManager::unload_plugin, "get_discovered_plugins", sol::overload( [] (PluginManager& pm) { return pm.get_discovered_plugins(); }, [] (PluginManager& pm, sol::this_state s) { // Convert to Lua table sol::state_view lua(s); sol::table t = lua.create_table(); auto names = pm.get_discovered_plugins(); for(size_t i = 0; i < names.size(); ++i) { t[i+1] = names[i]; } return t; } ), "get_loaded_plugins", sol::overload( [] (PluginManager& pm) { return pm.get_loaded_plugins(); }, [] (PluginManager& pm, sol::this_state s) { // Convert to Lua table sol::state_view lua(s); sol::table t = lua.create_table(); auto names = pm.get_loaded_plugins(); for(size_t i = 0; i < names.size(); ++i) { t[i+1] = names[i]; } return t; } ) ); } void LuaApi::call_idle_hooks() { // Call the which-key idle check if registered try { sol::optional which_key_check = lua_["lumacs"]["which_key_check_idle"]; if (which_key_check) { (*which_key_check)(); } } catch (const sol::error& e) { spdlog::error("Error in idle hook: {}", e.what()); } } } // namespace lumacs