diff --git a/CHANGELOG.md b/CHANGELOG.md index 0447c834..d1c04e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ - Dev: Added the ability to show `ChannelView`s without a `Split`. (#4747) - Dev: Refactor Args to be less of a singleton. (#5041) - Dev: Channels without any animated elements on screen will skip updates from the GIF timer. (#5042, #5043, #5045) +- Dev: Autogenerate docs/plugin-meta.lua. (#5055) ## 2.4.6 diff --git a/docs/plugin-meta.lua b/docs/plugin-meta.lua index c2464749..78bdf759 100644 --- a/docs/plugin-meta.lua +++ b/docs/plugin-meta.lua @@ -1,10 +1,18 @@ ---@meta Chatterino2 +-- This file is automatically generated from src/controllers/plugins/LuaAPI.hpp by the scripts/make_luals_meta.py script -- This file is intended to be used with LuaLS (https://luals.github.io/). -- Add the folder this file is in to "Lua.workspace.library". ----@alias LogLevel integer +c2 = {} +---@alias LogLevel integer +---@type { Debug: LogLevel, Info: LogLevel, Warning: LogLevel, Critical: LogLevel } +c2.LogLevel = {} + +---@alias EventType integer +---@type { CompletionRequested: EventType } +c2.EventType = {} ---@class CommandContext ---@field words string[] The words typed when executing the command. For example `/foo bar baz` will result in `{"/foo", "bar", "baz"}`. ---@field channel_name string The name of the channel the command was executed in. @@ -13,22 +21,19 @@ ---@field values string[] The completions ---@field hide_others boolean Whether other completions from Chatterino should be hidden/ignored. -c2 = {} - ----@type { Debug: LogLevel, Info: LogLevel, Warning: LogLevel, Critical: LogLevel } -c2.LogLevel = {} - ---- Writes a message to the Chatterino log. ----@param level LogLevel The desired level. ----@param ... any Values to log. Should be convertible to a string with `tostring()`. -function c2.log(level, ...) end - --- Registers a new command called `name` which when executed will call `handler`. +--- ---@param name string The name of the command. ---@param handler fun(ctx: CommandContext) The handler to be invoked when the command gets executed. ---@return boolean ok Returns `true` if everything went ok, `false` if a command with this name exists. function c2.register_command(name, handler) end +--- Registers a callback to be invoked when completions for a term are requested. +--- +---@param type "CompletionRequested" +---@param func fun(query: string, full_text_content: string, cursor_position: integer, is_first_word: boolean): CompletionList The callback to be invoked. +function c2.register_callback(type, func) end + --- Sends a message to `channel` with the specified text. Also executes commands. --- --- **Warning**: It is possible to trigger your own Lua command with this causing a potentially infinite loop. @@ -39,12 +44,15 @@ function c2.register_command(name, handler) end function c2.send_msg(channel, text) end --- Creates a system message (gray message) and adds it to the Twitch channel specified by `channel`. +--- ---@param channel string ---@param text string ---@return boolean ok function c2.system_msg(channel, text) end ---- Registers a callback to be invoked when completions for a term are requested. ----@param type "CompletionRequested" ----@param func fun(query: string, full_text_content: string, cursor_position: integer, is_first_word: boolean): CompletionList The callback to be invoked. -function c2.register_callback(type, func) end +--- Writes a message to the Chatterino log. +--- +---@param level LogLevel The desired level. +---@param ... any Values to log. Should be convertible to a string with `tostring()`. +function c2.log(level, ...) end + diff --git a/scripts/make_luals_meta.py b/scripts/make_luals_meta.py new file mode 100644 index 00000000..6afa3c5e --- /dev/null +++ b/scripts/make_luals_meta.py @@ -0,0 +1,142 @@ +""" +This script generates docs/plugin-meta.lua. It accepts no arguments + +It assumes comments look like: +/** + * Thing + * + * @lua@param thing boolean + * @lua@returns boolean + * @exposed name + */ +- Do not have any useful info on '/**' and '*/' lines. +- Class members are not allowed to have non-@command lines and commands different from @lua@field + +Valid commands are: +1. @exposeenum [dotted.name.in_lua.last_part] + Define a table with keys of the enum. Values behind those keys aren't + written on purpose. + This generates three lines: + - An type alias of [last_part] to integer, + - A type description that describes available values of the enum, + - A global table definition for the num +2. @lua[@command] + Writes [@command] to the file as a comment, usually this is @class, @param, @return, ... + @lua@class and @lua@field have special treatment when it comes to generation of spacing new lines +3. @exposed [c2.name] + Generates a function definition line from the last `@lua@param`s. + +Non-command lines of comments are written with a space after '---' +""" +from pathlib import Path + +BOILERPLATE = """ +---@meta Chatterino2 + +-- This file is automatically generated from src/controllers/plugins/LuaAPI.hpp by the scripts/make_luals_meta.py script +-- This file is intended to be used with LuaLS (https://luals.github.io/). +-- Add the folder this file is in to "Lua.workspace.library". + +c2 = {} +""" + +repo_root = Path(__file__).parent.parent +lua_api_file = repo_root / "src" / "controllers" / "plugins" / "LuaAPI.hpp" +lua_meta = repo_root / "docs" / "plugin-meta.lua" + +print("Reading from", lua_api_file.relative_to(repo_root)) +print("Writing to", lua_meta.relative_to(repo_root)) +with lua_api_file.open("r") as f: + lines = f.read().splitlines() + +# Are we in a doc comment? +comment: bool = False + +# Last `@lua@param`s seen - for @exposed generation +last_params_names: list[str] = [] +# Are we in a `@lua@class` definition? - makes newlines around @lua@class and @lua@field prettier +is_class = False + +# The name of the next enum in lua world +expose_next_enum_as: str | None = None +# Name of the current enum in c++ world, used to generate internal typenames for +current_enum_name: str | None = None + +with lua_meta.open("w") as out: + out.write(BOILERPLATE[1:]) # skip the newline after triple quote + + for line in lines: + line = line.strip() + if line.startswith("enum class "): + line = line.removeprefix("enum class ") + temp = line.split(" ", 2) + current_enum_name = temp[0] + if not expose_next_enum_as: + print( + f"Skipping enum {current_enum_name}, there wasn't a @exposeenum command" + ) + current_enum_name = None + continue + current_enum_name = expose_next_enum_as.split(".", 1)[-1] + out.write("---@alias " + current_enum_name + " integer\n") + out.write("---@type { ") + # temp[1] is '{' + if len(temp) == 2: # no values on this line + continue + line = temp[2] + + if current_enum_name is not None: + for i, tok in enumerate(line.split(" ")): + if tok == "};": + break + entry = tok.removesuffix(",") + if i != 0: + out.write(", ") + out.write(entry + ": " + current_enum_name) + out.write(" }\n" f"{expose_next_enum_as} = {{}}\n") + print(f"Wrote enum {expose_next_enum_as} => {current_enum_name}") + current_enum_name = None + expose_next_enum_as = None + continue + + if line.startswith("/**"): + comment = True + continue + elif "*/" in line: + comment = False + if not is_class: + out.write("\n") + continue + if not comment: + continue + line = line.replace("*", "", 1).lstrip() + if line == "": + out.write("---\n") + elif line.startswith("@exposeenum "): + expose_next_enum_as = line.split(" ", 1)[1] + elif line.startswith("@exposed "): + exp = line.replace("@exposed ", "", 1) + params = ", ".join(last_params_names) + out.write(f"function {exp}({params}) end\n") + print(f"Wrote function {exp}(...)") + last_params_names = [] + elif line.startswith("@lua"): + command = line.replace("@lua", "", 1) + if command.startswith("@param"): + last_params_names.append(command.split(" ", 2)[1]) + elif command.startswith("@class"): + print(f"Writing {command}") + if is_class: + out.write("\n") + is_class = True + elif not command.startswith("@field"): + is_class = False + + out.write("---" + command + "\n") + else: + if is_class: + is_class = False + out.write("\n") + + # note the space difference from the branch above + out.write("--- " + line + "\n") diff --git a/src/controllers/plugins/LuaAPI.hpp b/src/controllers/plugins/LuaAPI.hpp index 155afc70..95a998f7 100644 --- a/src/controllers/plugins/LuaAPI.hpp +++ b/src/controllers/plugins/LuaAPI.hpp @@ -11,10 +11,91 @@ namespace chatterino::lua::api { // NOLINTBEGIN(readability-identifier-naming) // Following functions are exposed in c2 table. + +// Comments in this file are special, the docs/plugin-meta.lua file is generated from them +// All multiline comments will be added into that file. See scripts/make_luals_meta.py script for more info. + +/** + * @exposeenum c2.LogLevel + */ +// Represents "calls" to qCDebug, qCInfo ... +enum class LogLevel { Debug, Info, Warning, Critical }; + +/** + * @exposeenum c2.EventType + */ +enum class EventType { + CompletionRequested, +}; + +/** + * @lua@class CommandContext + * @lua@field words string[] The words typed when executing the command. For example `/foo bar baz` will result in `{"/foo", "bar", "baz"}`. + * @lua@field channel_name string The name of the channel the command was executed in. + */ + +/** + * @lua@class CompletionList + */ +struct CompletionList { + /** + * @lua@field values string[] The completions + */ + std::vector values{}; + + /** + * @lua@field hide_others boolean Whether other completions from Chatterino should be hidden/ignored. + */ + bool hideOthers{}; +}; + +/** + * Registers a new command called `name` which when executed will call `handler`. + * + * @lua@param name string The name of the command. + * @lua@param handler fun(ctx: CommandContext) The handler to be invoked when the command gets executed. + * @lua@return boolean ok Returns `true` if everything went ok, `false` if a command with this name exists. + * @exposed c2.register_command + */ int c2_register_command(lua_State *L); + +/** + * Registers a callback to be invoked when completions for a term are requested. + * + * @lua@param type "CompletionRequested" + * @lua@param func fun(query: string, full_text_content: string, cursor_position: integer, is_first_word: boolean): CompletionList The callback to be invoked. + * @exposed c2.register_callback + */ int c2_register_callback(lua_State *L); + +/** + * Sends a message to `channel` with the specified text. Also executes commands. + * + * **Warning**: It is possible to trigger your own Lua command with this causing a potentially infinite loop. + * + * @lua@param channel string The name of the Twitch channel + * @lua@param text string The text to be sent + * @lua@return boolean ok + * @exposed c2.send_msg + */ int c2_send_msg(lua_State *L); +/** + * Creates a system message (gray message) and adds it to the Twitch channel specified by `channel`. + * + * @lua@param channel string + * @lua@param text string + * @lua@return boolean ok + * @exposed c2.system_msg + */ int c2_system_msg(lua_State *L); + +/** + * Writes a message to the Chatterino log. + * + * @lua@param level LogLevel The desired level. + * @lua@param ... any Values to log. Should be convertible to a string with `tostring()`. + * @exposed c2.log + */ int c2_log(lua_State *L); // These ones are global @@ -26,28 +107,6 @@ int g_print(lua_State *L); int searcherAbsolute(lua_State *L); int searcherRelative(lua_State *L); -// Exposed as c2.LogLevel -// Represents "calls" to qCDebug, qCInfo ... -enum class LogLevel { Debug, Info, Warning, Critical }; - -// Exposed as c2.EventType -// Represents callbacks c2 can do into lua world -enum class EventType { - CompletionRequested, -}; - -/** - * This is for custom completion, a registered function returns this type - * however in Lua array part (value) and object part (hideOthers) are in the same - * table. - */ -struct CompletionList { - std::vector values{}; - - // exposed as hide_others - bool hideOthers{}; -}; - } // namespace chatterino::lua::api #endif