#ifdef _DEBUG #include "lua/autocomplete.hpp" #include #include #include #include #include #include #include "lua/metadoc.hpp" #include "utils/algorithm/container.hpp" #include "utils/str_cat.hpp" #include "utils/str_split.hpp" namespace devilution { namespace { std::string_view GetLastToken(std::string_view text) { if (text.empty()) return {}; size_t i = text.size(); while (i > 0 && text[i - 1] != ' ' && text[i - 1] != '(' && text[i - 1] != ',') --i; return text.substr(i); } struct ValueInfo { bool callable = false; std::string signature; std::string docstring; }; ValueInfo GetValueInfo(const sol::table &table, std::string_view key, const sol::object &value) { ValueInfo info; if (std::optional signature = GetSignature(table, key); signature.has_value()) { info.signature = *std::move(signature); } if (std::optional docstring = GetDocstring(table, key); docstring.has_value()) { info.docstring = *std::move(docstring); } if (value.get_type() == sol::type::function) { info.callable = true; return info; } if (!value.is()) return info; const auto valueAsTable = value.as(); const auto metatable = valueAsTable.get>(sol::metatable_key); if (!metatable || !metatable->is()) return info; const auto metatableTbl = metatable->as(); const auto callFn = metatableTbl.get>(sol::meta_function::call); info.callable = callFn.has_value(); return info; } void SuggestionsFromTable(const sol::table &table, std::string_view prefix, size_t maxSuggestions, std::unordered_set &out) { for (const auto &[key, value] : table) { if (key.get_type() == sol::type::string) { std::string keyStr = key.as(); if (!keyStr.starts_with(prefix) || keyStr.size() == prefix.size()) continue; if (keyStr.starts_with("__") && !prefix.starts_with("__")) continue; // sol-internal keys -- we don't have fonts for these so skip them. if (keyStr.find("♻") != std::string::npos || keyStr.find("☢") != std::string::npos || keyStr.find("🔩") != std::string::npos) continue; ValueInfo info = GetValueInfo(table, keyStr, value); std::string completionText = keyStr.substr(prefix.size()); LuaAutocompleteSuggestion suggestion { std::move(keyStr), std::move(completionText) }; if (info.callable) { suggestion.completionText.append("()"); suggestion.cursorAdjust = -1; } if (!info.signature.empty()) { StrAppend(suggestion.displayText, info.signature); } if (!info.docstring.empty()) { std::string_view firstLine = info.docstring; if (const size_t newlinePos = firstLine.find('\n'); newlinePos != std::string_view::npos) { firstLine = firstLine.substr(0, newlinePos); } StrAppend(suggestion.displayText, " - ", firstLine); } out.insert(std::move(suggestion)); if (out.size() == maxSuggestions) break; } } const auto fallback = table.get>(sol::metatable_key); if (fallback.has_value() && fallback->get_type() == sol::type::table) { SuggestionsFromTable(fallback->as(), prefix, maxSuggestions, out); } } } // namespace void GetLuaAutocompleteSuggestions(std::string_view text, const sol::environment &lua, size_t maxSuggestions, std::vector &out) { out.clear(); if (text.empty()) return; std::string_view token = GetLastToken(text); const char prevChar = token.data() == text.data() ? '\0' : *(token.data() - 1); if (prevChar == '(' || prevChar == ',') return; const size_t dotPos = token.rfind('.'); const std::string_view prefix = token.substr(dotPos + 1); token.remove_suffix(token.size() - (dotPos == std::string_view::npos ? 0 : dotPos)); std::unordered_set suggestions; const auto addSuggestions = [&](const sol::table &table) { SuggestionsFromTable(table, prefix, maxSuggestions, suggestions); }; if (token.empty()) { addSuggestions(lua); const auto fallback = lua.get>("_G"); if (fallback.has_value() && fallback->get_type() == sol::type::table) { addSuggestions(fallback->as()); } } else { std::optional obj = lua; for (const std::string_view part : SplitByChar(token, '.')) { obj = obj->as().get>(part); if (!obj.has_value() || obj->get_type() != sol::type::table) return; } if (obj->get_type() == sol::type::table) { addSuggestions(obj->as()); } } out.insert(out.end(), suggestions.begin(), suggestions.end()); c_sort(out); } } // namespace devilution #endif // _DEBUG