From 79a891a2d575c066b78d58d328a2d15b0d3249af Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 23 Mar 2026 11:09:48 +0300 Subject: [PATCH 01/35] wip --- waterbox/dsda/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waterbox/dsda/core b/waterbox/dsda/core index c0b95b8980f..dafa0543c59 160000 --- a/waterbox/dsda/core +++ b/waterbox/dsda/core @@ -1 +1 @@ -Subproject commit c0b95b8980fee368377768a56093343ff421d982 +Subproject commit dafa0543c59f901c15901e50e3dbdfc6c35e6534 From aedd2a33f6844f2b5a0c9d39ea79799cbcdbffcb Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 21 Aug 2023 09:05:52 +0200 Subject: [PATCH 02/35] Add support for generating LuaCATS definitions for the Lua API --- .../lua/Documentation/LuaCatsGenerator.cs | 162 ++++++++++++++++++ .../lua/LuaDocumentation.cs | 6 + .../tools/Lua/LuaConsole.Designer.cs | 10 +- .../tools/Lua/LuaConsole.cs | 12 ++ 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs new file mode 100644 index 00000000000..f5c68c92d2e --- /dev/null +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using BizHawk.Common; +using NLua; + +namespace BizHawk.Client.Common; + +/// +/// Generates API definitions in the LuaCATS format. +/// +/// +/// See https://luals.github.io/wiki/annotations +/// +internal class LuaCatsGenerator +{ + private static readonly Dictionary TypeConversions = new() + { + [typeof(object)] = "any", + [typeof(int)] = "integer", + [typeof(uint)] = "integer", + [typeof(short)] = "integer", + [typeof(ushort)] = "integer", + [typeof(long)] = "integer", + [typeof(ulong)] = "integer", + [typeof(float)] = "number", + [typeof(double)] = "number", + [typeof(string)] = "string", + [typeof(bool)] = "boolean", + [typeof(LuaFunction)] = "function", + [typeof(LuaTable)] = "table", + [typeof(System.Drawing.Color)] = "color", + }; + + private const string Preamble = @"---@meta _ + +---@class color : userdata + +---A color in one of the following formats: +--- - Number in the format `0xAARRGGBB` +--- - String in the format `""#RRGGBB""` or `""#AARRGGBB""` +--- - A CSS3/X11 color name e.g. `""blue""`, `""palegoldenrod""` +--- - Color created with `forms.createcolor` +---@alias luacolor integer | string | color +"; + + public string Generate(LuaDocumentation docs) + { + var sb = new StringBuilder(); + + sb.AppendLine($"--Generated with BizHawk {VersionInfo.MainVersion}"); + + sb.AppendLine(Preamble); + + foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) + { + string library = libraryGroup.Key; + string libraryDescription = libraryGroup.First().LibraryDescription; + + if (!string.IsNullOrEmpty(libraryDescription)) + sb.AppendLine(FormatDescription(libraryDescription)); + sb.AppendLine($"---@class {library}"); + sb.AppendLine($"{library} = {{}}"); + sb.AppendLine(); + + foreach (var func in libraryGroup.OrderBy(func => func.Name)) + { + if (!string.IsNullOrEmpty(func.Description)) + sb.AppendLine(FormatDescription(func.Description)); + + if (func.IsDeprecated) + sb.AppendLine("---@deprecated"); + + foreach (var parameter in func.Method.GetParameters()) + { + if (IsParams(parameter)) + { + sb.Append("---@vararg"); + } + else + { + sb.Append($"---@param {parameter.Name}"); + if (parameter.IsOptional || IsNullable(parameter.ParameterType)) + sb.Append('?'); + } + + sb.Append(' '); + sb.AppendLine(GetLuaType(parameter)); + } + + if (func.Method.ReturnType != typeof(void)) + { + sb.Append("---@return "); + sb.AppendLine(GetLuaType(func.Method.ReturnType)); + } + + sb.Append($"function {library}.{func.Name}("); + + foreach (var parameter in func.Method.GetParameters()) + { + if (parameter.Position > 0) + sb.Append(", "); + sb.Append(IsParams(parameter) ? "..." : parameter.Name); + } + + sb.AppendLine(") end"); + sb.AppendLine(); + } + sb.AppendLine(); + } + return sb.ToString(); + } + + private static string FormatDescription(string description) + { + // prefix every line with --- + description = Regex.Replace(description, "^", "---", RegexOptions.Multiline); + // replace {{wiki markup}} with `markdown` + description = Regex.Replace(description, @"{{(.+?)}}", "`$1`"); + // replace wiki image markup with markdown + description = Regex.Replace(description, @"\[(?.+?)\|alt=(?.+?)\]", "![${alt}](${url})"); + return description; + } + + private static string GetLuaType(ParameterInfo parameter) + { + if (parameter.ParameterType == typeof(object) && parameter.GetCustomAttribute() is not null) + return "luacolor"; // see Preamble + + // no [] array modifier for varargs + if (parameter.ParameterType.IsArray && IsParams(parameter)) + return GetLuaType(parameter.ParameterType.GetElementType()); + + // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used + // like `gui.text` and `forms.settext` instead of polluting the entire API surface + if (parameter.ParameterType == typeof(string) && parameter.Name is "message" or "caption") + return "string | number"; + + return GetLuaType(parameter.ParameterType); + } + + private static string GetLuaType(Type type) + { + if (type.IsArray) + return GetLuaType(type.GetElementType()) + "[]"; + + if (IsNullable(type)) + type = type.GetGenericArguments()[0]; + + if (TypeConversions.TryGetValue(type, out string luaType)) + return luaType; + else + throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); + } + + private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + + private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; +} diff --git a/src/BizHawk.Client.Common/lua/LuaDocumentation.cs b/src/BizHawk.Client.Common/lua/LuaDocumentation.cs index 2174d646916..3dd3b46b6a8 100644 --- a/src/BizHawk.Client.Common/lua/LuaDocumentation.cs +++ b/src/BizHawk.Client.Common/lua/LuaDocumentation.cs @@ -199,6 +199,12 @@ public string ToNotepadPlusPlusAutoComplete() { return ""; // TODO } + + public string ToLuaLanguageServerDefinitions() + { + var generator = new LuaCatsGenerator(); + return generator.Generate(this); + } } public class LibraryFunction diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs index 53f366891db..c9a590a6ac5 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs @@ -77,6 +77,7 @@ private void InitializeComponent() this.RegisterToTextEditorsSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.RegisterSublimeText2MenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.RegisterNotePadMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); + this.GenerateLuaCatsDefinitionMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.HelpSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.FunctionsListMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.OnlineDocsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); @@ -373,7 +374,8 @@ private void InitializeComponent() // this.RegisterToTextEditorsSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.RegisterSublimeText2MenuItem, - this.RegisterNotePadMenuItem}); + this.RegisterNotePadMenuItem, + this.GenerateLuaCatsDefinitionMenuItem}); this.RegisterToTextEditorsSubMenu.Text = "Register To Text Editors"; this.RegisterToTextEditorsSubMenu.DropDownOpened += new System.EventHandler(this.RegisterToTextEditorsSubMenu_DropDownOpened); // @@ -387,6 +389,11 @@ private void InitializeComponent() this.RegisterNotePadMenuItem.Text = "Notepad++"; this.RegisterNotePadMenuItem.Click += new System.EventHandler(this.RegisterNotePadMenuItem_Click); // + // GenerateLuaCatsDefinitionMenuItem + // + this.GenerateLuaCatsDefinitionMenuItem.Text = "LuaCATS (Lua Language Server)"; + this.GenerateLuaCatsDefinitionMenuItem.Click += new System.EventHandler(this.GenerateLuaCatsDefinitionMenuItem_Click); + // // HelpSubMenu // this.HelpSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -790,6 +797,7 @@ private void InitializeComponent() private BizHawk.WinForms.Controls.ToolStripMenuItemEx RegisterToTextEditorsSubMenu; private BizHawk.WinForms.Controls.ToolStripMenuItemEx RegisterSublimeText2MenuItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx RegisterNotePadMenuItem; + private BizHawk.WinForms.Controls.ToolStripMenuItemEx GenerateLuaCatsDefinitionMenuItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx SelectAllContextItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx CopyContextItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx ClearRegisteredFunctionsContextItem; diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index ff9ea53a77d..7d8b40a86ee 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -1205,6 +1205,18 @@ private void RegisterNotePadMenuItem_Click(object sender, EventArgs e) _luaAutoInstaller.InstallBizLua(LuaAutocompleteInstaller.TextEditors.NotePad, LuaImp.Docs); } + private void GenerateLuaCatsDefinitionMenuItem_Click(object sender, EventArgs e) + { + string initDir = !string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename) + ? Path.GetDirectoryName(LuaImp.ScriptList.Filename) + : Config!.PathEntries.LuaAbsolutePath(); + + if (this.ShowFileSaveDialog(initDir, initFileName: "BizHawk.lua", fileExt: ".lua", filter: JustScriptsFSFilterSet) is string path) + { + File.WriteAllText(path, LuaImp.Docs.ToLuaLanguageServerDefinitions()); + } + } + private void FunctionsListMenuItem_Click(object sender, EventArgs e) { new LuaFunctionsForm(LuaImp.Docs).Show(); From e605a18f454584a8a7dd895b846ce7ebf3931efb Mon Sep 17 00:00:00 2001 From: kalimag Date: Fri, 24 Oct 2025 10:29:33 +0200 Subject: [PATCH 03/35] Add missing numeric types --- .../lua/Documentation/LuaCatsGenerator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index f5c68c92d2e..cd03fb1f0b5 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -20,6 +20,8 @@ internal class LuaCatsGenerator private static readonly Dictionary TypeConversions = new() { [typeof(object)] = "any", + [typeof(byte)] = "integer", + [typeof(sbyte)] = "integer", [typeof(int)] = "integer", [typeof(uint)] = "integer", [typeof(short)] = "integer", @@ -28,6 +30,7 @@ internal class LuaCatsGenerator [typeof(ulong)] = "integer", [typeof(float)] = "number", [typeof(double)] = "number", + [typeof(decimal)] = "number", [typeof(string)] = "string", [typeof(bool)] = "boolean", [typeof(LuaFunction)] = "function", From fabb10cb4447b4e7da5203ec2049ef0e854ac292 Mon Sep 17 00:00:00 2001 From: kalimag Date: Fri, 24 Oct 2025 11:06:09 +0200 Subject: [PATCH 04/35] Throw error when definition file is executed as a script --- .../lua/Documentation/LuaCatsGenerator.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index cd03fb1f0b5..23c9ea55916 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -38,7 +38,9 @@ internal class LuaCatsGenerator [typeof(System.Drawing.Color)] = "color", }; - private const string Preamble = @"---@meta _ + private const string Preamble = @"error(""This is a definition file for Lua Language Server and not a usable script"") + +---@meta _ ---@class color : userdata From 554d0cfe9ac75526046c67c81e26a2bf0627f83b Mon Sep 17 00:00:00 2001 From: kalimag Date: Fri, 24 Oct 2025 11:07:11 +0200 Subject: [PATCH 05/35] Add support for bytes-as-string parameters --- .../lua/Documentation/LuaCatsGenerator.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 23c9ea55916..f8e60d2593d 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -33,6 +33,9 @@ internal class LuaCatsGenerator [typeof(decimal)] = "number", [typeof(string)] = "string", [typeof(bool)] = "boolean", + [typeof(byte[])] = "string", + [typeof(Memory)] = "string", + [typeof(ReadOnlyMemory)] = "string", [typeof(LuaFunction)] = "function", [typeof(LuaTable)] = "table", [typeof(System.Drawing.Color)] = "color", @@ -149,16 +152,20 @@ private static string GetLuaType(ParameterInfo parameter) private static string GetLuaType(Type type) { + // try this twice, before and after extracting the array/nullable type + if (TypeConversions.TryGetValue(type, out string luaType)) + return luaType; + if (type.IsArray) return GetLuaType(type.GetElementType()) + "[]"; if (IsNullable(type)) type = type.GetGenericArguments()[0]; - if (TypeConversions.TryGetValue(type, out string luaType)) + if (TypeConversions.TryGetValue(type, out luaType)) return luaType; - else - throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); + + throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); } private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); From 4301a88c74c45e2a1c472800f9e7df62653e0250 Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 23 Mar 2026 11:06:39 +0300 Subject: [PATCH 06/35] Apply suggestions from code review Co-authored-by: feos --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 1 - src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index f8e60d2593d..3e3c19bb556 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Reflection; diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index 7d8b40a86ee..efa2e0b052e 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -1213,7 +1213,7 @@ private void GenerateLuaCatsDefinitionMenuItem_Click(object sender, EventArgs e) if (this.ShowFileSaveDialog(initDir, initFileName: "BizHawk.lua", fileExt: ".lua", filter: JustScriptsFSFilterSet) is string path) { - File.WriteAllText(path, LuaImp.Docs.ToLuaLanguageServerDefinitions()); + File.WriteAllText(path, LuaImp.Docs.ToLuaLanguageServerDefinitions()); } } From fa6748b4f642d776670b572e3b7e0d22d46535cd Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 23 Mar 2026 12:46:00 +0300 Subject: [PATCH 07/35] add examples and hawk info I don't know regex, replacements probably can be done in a neater way --- .../lua/Documentation/LuaCatsGenerator.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 3e3c19bb556..cf012f5d6a9 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -43,6 +43,8 @@ internal class LuaCatsGenerator private const string Preamble = @"error(""This is a definition file for Lua Language Server and not a usable script"") ---@meta _ +--- Lua functions available for the BizHawk emulator +--- https://tasvideos.org/Bizhawk ---@class color : userdata @@ -78,6 +80,18 @@ public string Generate(LuaDocumentation docs) if (!string.IsNullOrEmpty(func.Description)) sb.AppendLine(FormatDescription(func.Description)); + if (func.Example != null) + { + sb.AppendLine("---"); + sb.AppendLine("---Example:"); + sb.AppendLine("---"); + sb.AppendFormat("---\t{0}", func.Example + .Replace("\r\n", "\r\n---\t") + .Replace("{{", "`") + .Replace("}}", "`")); + sb.AppendLine(); + } + if (func.IsDeprecated) sb.AppendLine("---@deprecated"); From 6d9f4ee402c0c22a15587857e5ace2ddd63e3aed Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 23 Mar 2026 12:48:07 +0300 Subject: [PATCH 08/35] line endings --- .../lua/Documentation/LuaCatsGenerator.cs | 372 +++++++++--------- 1 file changed, 186 insertions(+), 186 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index cf012f5d6a9..928ee3f9f03 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -1,187 +1,187 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using BizHawk.Common; -using NLua; - -namespace BizHawk.Client.Common; - -/// -/// Generates API definitions in the LuaCATS format. -/// -/// -/// See https://luals.github.io/wiki/annotations -/// -internal class LuaCatsGenerator -{ - private static readonly Dictionary TypeConversions = new() - { - [typeof(object)] = "any", - [typeof(byte)] = "integer", - [typeof(sbyte)] = "integer", - [typeof(int)] = "integer", - [typeof(uint)] = "integer", - [typeof(short)] = "integer", - [typeof(ushort)] = "integer", - [typeof(long)] = "integer", - [typeof(ulong)] = "integer", - [typeof(float)] = "number", - [typeof(double)] = "number", - [typeof(decimal)] = "number", - [typeof(string)] = "string", - [typeof(bool)] = "boolean", - [typeof(byte[])] = "string", - [typeof(Memory)] = "string", - [typeof(ReadOnlyMemory)] = "string", - [typeof(LuaFunction)] = "function", - [typeof(LuaTable)] = "table", - [typeof(System.Drawing.Color)] = "color", - }; - - private const string Preamble = @"error(""This is a definition file for Lua Language Server and not a usable script"") - ----@meta _ ---- Lua functions available for the BizHawk emulator ---- https://tasvideos.org/Bizhawk - ----@class color : userdata - ----A color in one of the following formats: ---- - Number in the format `0xAARRGGBB` ---- - String in the format `""#RRGGBB""` or `""#AARRGGBB""` ---- - A CSS3/X11 color name e.g. `""blue""`, `""palegoldenrod""` ---- - Color created with `forms.createcolor` ----@alias luacolor integer | string | color -"; - - public string Generate(LuaDocumentation docs) - { - var sb = new StringBuilder(); - - sb.AppendLine($"--Generated with BizHawk {VersionInfo.MainVersion}"); - - sb.AppendLine(Preamble); - - foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) - { - string library = libraryGroup.Key; - string libraryDescription = libraryGroup.First().LibraryDescription; - - if (!string.IsNullOrEmpty(libraryDescription)) - sb.AppendLine(FormatDescription(libraryDescription)); - sb.AppendLine($"---@class {library}"); - sb.AppendLine($"{library} = {{}}"); - sb.AppendLine(); - - foreach (var func in libraryGroup.OrderBy(func => func.Name)) - { - if (!string.IsNullOrEmpty(func.Description)) - sb.AppendLine(FormatDescription(func.Description)); - - if (func.Example != null) - { - sb.AppendLine("---"); - sb.AppendLine("---Example:"); - sb.AppendLine("---"); - sb.AppendFormat("---\t{0}", func.Example - .Replace("\r\n", "\r\n---\t") - .Replace("{{", "`") +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using BizHawk.Common; +using NLua; + +namespace BizHawk.Client.Common; + +/// +/// Generates API definitions in the LuaCATS format. +/// +/// +/// See https://luals.github.io/wiki/annotations +/// +internal class LuaCatsGenerator +{ + private static readonly Dictionary TypeConversions = new() + { + [typeof(object)] = "any", + [typeof(byte)] = "integer", + [typeof(sbyte)] = "integer", + [typeof(int)] = "integer", + [typeof(uint)] = "integer", + [typeof(short)] = "integer", + [typeof(ushort)] = "integer", + [typeof(long)] = "integer", + [typeof(ulong)] = "integer", + [typeof(float)] = "number", + [typeof(double)] = "number", + [typeof(decimal)] = "number", + [typeof(string)] = "string", + [typeof(bool)] = "boolean", + [typeof(byte[])] = "string", + [typeof(Memory)] = "string", + [typeof(ReadOnlyMemory)] = "string", + [typeof(LuaFunction)] = "function", + [typeof(LuaTable)] = "table", + [typeof(System.Drawing.Color)] = "color", + }; + + private const string Preamble = @"error(""This is a definition file for Lua Language Server and not a usable script"") + +---@meta _ +--- Lua functions available for the BizHawk emulator +--- https://tasvideos.org/Bizhawk + +---@class color : userdata + +---A color in one of the following formats: +--- - Number in the format `0xAARRGGBB` +--- - String in the format `""#RRGGBB""` or `""#AARRGGBB""` +--- - A CSS3/X11 color name e.g. `""blue""`, `""palegoldenrod""` +--- - Color created with `forms.createcolor` +---@alias luacolor integer | string | color +"; + + public string Generate(LuaDocumentation docs) + { + var sb = new StringBuilder(); + + sb.AppendLine($"--Generated with BizHawk {VersionInfo.MainVersion}"); + + sb.AppendLine(Preamble); + + foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) + { + string library = libraryGroup.Key; + string libraryDescription = libraryGroup.First().LibraryDescription; + + if (!string.IsNullOrEmpty(libraryDescription)) + sb.AppendLine(FormatDescription(libraryDescription)); + sb.AppendLine($"---@class {library}"); + sb.AppendLine($"{library} = {{}}"); + sb.AppendLine(); + + foreach (var func in libraryGroup.OrderBy(func => func.Name)) + { + if (!string.IsNullOrEmpty(func.Description)) + sb.AppendLine(FormatDescription(func.Description)); + + if (func.Example != null) + { + sb.AppendLine("---"); + sb.AppendLine("---Example:"); + sb.AppendLine("---"); + sb.AppendFormat("---\t{0}", func.Example + .Replace("\r\n", "\r\n---\t") + .Replace("{{", "`") .Replace("}}", "`")); - sb.AppendLine(); - } - - if (func.IsDeprecated) - sb.AppendLine("---@deprecated"); - - foreach (var parameter in func.Method.GetParameters()) - { - if (IsParams(parameter)) - { - sb.Append("---@vararg"); - } - else - { - sb.Append($"---@param {parameter.Name}"); - if (parameter.IsOptional || IsNullable(parameter.ParameterType)) - sb.Append('?'); - } - - sb.Append(' '); - sb.AppendLine(GetLuaType(parameter)); - } - - if (func.Method.ReturnType != typeof(void)) - { - sb.Append("---@return "); - sb.AppendLine(GetLuaType(func.Method.ReturnType)); - } - - sb.Append($"function {library}.{func.Name}("); - - foreach (var parameter in func.Method.GetParameters()) - { - if (parameter.Position > 0) - sb.Append(", "); - sb.Append(IsParams(parameter) ? "..." : parameter.Name); - } - - sb.AppendLine(") end"); - sb.AppendLine(); - } - sb.AppendLine(); - } - return sb.ToString(); - } - - private static string FormatDescription(string description) - { - // prefix every line with --- - description = Regex.Replace(description, "^", "---", RegexOptions.Multiline); - // replace {{wiki markup}} with `markdown` - description = Regex.Replace(description, @"{{(.+?)}}", "`$1`"); - // replace wiki image markup with markdown - description = Regex.Replace(description, @"\[(?.+?)\|alt=(?.+?)\]", "![${alt}](${url})"); - return description; - } - - private static string GetLuaType(ParameterInfo parameter) - { - if (parameter.ParameterType == typeof(object) && parameter.GetCustomAttribute() is not null) - return "luacolor"; // see Preamble - - // no [] array modifier for varargs - if (parameter.ParameterType.IsArray && IsParams(parameter)) - return GetLuaType(parameter.ParameterType.GetElementType()); - - // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used - // like `gui.text` and `forms.settext` instead of polluting the entire API surface - if (parameter.ParameterType == typeof(string) && parameter.Name is "message" or "caption") - return "string | number"; - - return GetLuaType(parameter.ParameterType); - } - - private static string GetLuaType(Type type) - { - // try this twice, before and after extracting the array/nullable type - if (TypeConversions.TryGetValue(type, out string luaType)) - return luaType; - - if (type.IsArray) - return GetLuaType(type.GetElementType()) + "[]"; - - if (IsNullable(type)) - type = type.GetGenericArguments()[0]; - - if (TypeConversions.TryGetValue(type, out luaType)) - return luaType; - - throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); - } - - private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - - private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; -} + sb.AppendLine(); + } + + if (func.IsDeprecated) + sb.AppendLine("---@deprecated"); + + foreach (var parameter in func.Method.GetParameters()) + { + if (IsParams(parameter)) + { + sb.Append("---@vararg"); + } + else + { + sb.Append($"---@param {parameter.Name}"); + if (parameter.IsOptional || IsNullable(parameter.ParameterType)) + sb.Append('?'); + } + + sb.Append(' '); + sb.AppendLine(GetLuaType(parameter)); + } + + if (func.Method.ReturnType != typeof(void)) + { + sb.Append("---@return "); + sb.AppendLine(GetLuaType(func.Method.ReturnType)); + } + + sb.Append($"function {library}.{func.Name}("); + + foreach (var parameter in func.Method.GetParameters()) + { + if (parameter.Position > 0) + sb.Append(", "); + sb.Append(IsParams(parameter) ? "..." : parameter.Name); + } + + sb.AppendLine(") end"); + sb.AppendLine(); + } + sb.AppendLine(); + } + return sb.ToString(); + } + + private static string FormatDescription(string description) + { + // prefix every line with --- + description = Regex.Replace(description, "^", "---", RegexOptions.Multiline); + // replace {{wiki markup}} with `markdown` + description = Regex.Replace(description, @"{{(.+?)}}", "`$1`"); + // replace wiki image markup with markdown + description = Regex.Replace(description, @"\[(?.+?)\|alt=(?.+?)\]", "![${alt}](${url})"); + return description; + } + + private static string GetLuaType(ParameterInfo parameter) + { + if (parameter.ParameterType == typeof(object) && parameter.GetCustomAttribute() is not null) + return "luacolor"; // see Preamble + + // no [] array modifier for varargs + if (parameter.ParameterType.IsArray && IsParams(parameter)) + return GetLuaType(parameter.ParameterType.GetElementType()); + + // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used + // like `gui.text` and `forms.settext` instead of polluting the entire API surface + if (parameter.ParameterType == typeof(string) && parameter.Name is "message" or "caption") + return "string | number"; + + return GetLuaType(parameter.ParameterType); + } + + private static string GetLuaType(Type type) + { + // try this twice, before and after extracting the array/nullable type + if (TypeConversions.TryGetValue(type, out string luaType)) + return luaType; + + if (type.IsArray) + return GetLuaType(type.GetElementType()) + "[]"; + + if (IsNullable(type)) + type = type.GetGenericArguments()[0]; + + if (TypeConversions.TryGetValue(type, out luaType)) + return luaType; + + throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); + } + + private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + + private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; +} From 535c79f93af352e8de1bcdb5ed83fd5c69fee0c3 Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 23 Mar 2026 14:35:36 +0300 Subject: [PATCH 09/35] move editor/ide integration to its own menu we gotta promote this! --- .../tools/Lua/LuaAutocompleteInstaller.cs | 2 +- .../tools/Lua/LuaConsole.Designer.cs | 55 +++++++++++-------- .../tools/Lua/LuaConsole.cs | 2 +- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs index c2b6878b186..b64121bc68a 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs @@ -56,7 +56,7 @@ private bool IsSublimeInstalled() private bool IsNotepadInstalled() { // The most likely location of the app, eventually we should consider looking through the registry or installed apps as a more robust way to detect it; - string exePath = @"C:\Program Files (x86)\Notepad++\notepad++.exe"; + string exePath = @"C:\Program Files\Notepad++\notepad++.exe"; return File.Exists(exePath); } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs index c9a590a6ac5..23646d0f481 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.Designer.cs @@ -73,11 +73,11 @@ private void InitializeComponent() this.DisableScriptsOnLoadMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.ReturnAllIfNoneSelectedMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.ReloadWhenScriptFileChangesMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); - this.toolStripSeparator4 = new BizHawk.WinForms.Controls.ToolStripSeparatorEx(); - this.RegisterToTextEditorsSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); + this.integrationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GenerateLuaCatsDefinitionMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); this.RegisterSublimeText2MenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.RegisterNotePadMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); - this.GenerateLuaCatsDefinitionMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.HelpSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.FunctionsListMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.OnlineDocsMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); @@ -178,6 +178,7 @@ private void InitializeComponent() this.FileSubMenu, this.ScriptSubMenu, this.SettingsSubMenu, + this.integrationToolStripMenuItem, this.HelpSubMenu}); this.menuStrip1.TabIndex = 1; // @@ -349,9 +350,7 @@ private void InitializeComponent() this.SettingsSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.DisableScriptsOnLoadMenuItem, this.ReturnAllIfNoneSelectedMenuItem, - this.ReloadWhenScriptFileChangesMenuItem, - this.toolStripSeparator4, - this.RegisterToTextEditorsSubMenu}); + this.ReloadWhenScriptFileChangesMenuItem}); this.SettingsSubMenu.Text = "&Settings"; this.SettingsSubMenu.DropDownOpened += new System.EventHandler(this.OptionsSubMenu_DropDownOpened); // @@ -370,30 +369,38 @@ private void InitializeComponent() this.ReloadWhenScriptFileChangesMenuItem.Text = "Reload When Script File Changes"; this.ReloadWhenScriptFileChangesMenuItem.Click += new System.EventHandler(this.ReloadWhenScriptFileChangesMenuItem_Click); // - // RegisterToTextEditorsSubMenu + // integrationToolStripMenuItem // - this.RegisterToTextEditorsSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.integrationToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.GenerateLuaCatsDefinitionMenuItem, + this.toolStripSeparator1, this.RegisterSublimeText2MenuItem, - this.RegisterNotePadMenuItem, - this.GenerateLuaCatsDefinitionMenuItem}); - this.RegisterToTextEditorsSubMenu.Text = "Register To Text Editors"; - this.RegisterToTextEditorsSubMenu.DropDownOpened += new System.EventHandler(this.RegisterToTextEditorsSubMenu_DropDownOpened); + this.RegisterNotePadMenuItem}); + this.integrationToolStripMenuItem.Name = "integrationToolStripMenuItem"; + this.integrationToolStripMenuItem.Size = new System.Drawing.Size(82, 20); + this.integrationToolStripMenuItem.Text = "&Integration"; + this.integrationToolStripMenuItem.DropDownOpened += new System.EventHandler(this.IntegrationMenu_DropDownOpened); + // + // GenerateLuaCatsDefinitionMenuItem + // + this.GenerateLuaCatsDefinitionMenuItem.Text = "Export &LuaCATS (Lua Language Server) File"; + this.GenerateLuaCatsDefinitionMenuItem.Click += new System.EventHandler(this.GenerateLuaCatsDefinitionMenuItem_Click); + // + // toolStripSeparator1 + // + this.toolStripSeparator1.Name = "toolStripSeparator1"; + this.toolStripSeparator1.Size = new System.Drawing.Size(322, 6); // // RegisterSublimeText2MenuItem // - this.RegisterSublimeText2MenuItem.Text = "&Sublime Text 2"; + this.RegisterSublimeText2MenuItem.Text = "Register to &Sublime Text 2"; this.RegisterSublimeText2MenuItem.Click += new System.EventHandler(this.RegisterSublimeText2MenuItem_Click); // // RegisterNotePadMenuItem // - this.RegisterNotePadMenuItem.Text = "Notepad++"; + this.RegisterNotePadMenuItem.Text = "Register to &Notepad++"; this.RegisterNotePadMenuItem.Click += new System.EventHandler(this.RegisterNotePadMenuItem_Click); // - // GenerateLuaCatsDefinitionMenuItem - // - this.GenerateLuaCatsDefinitionMenuItem.Text = "LuaCATS (Lua Language Server)"; - this.GenerateLuaCatsDefinitionMenuItem.Click += new System.EventHandler(this.GenerateLuaCatsDefinitionMenuItem_Click); - // // HelpSubMenu // this.HelpSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -793,15 +800,15 @@ private void InitializeComponent() private System.Windows.Forms.SplitContainer splitContainer1; private BizHawk.WinForms.Controls.ToolStripMenuItemEx ReturnAllIfNoneSelectedMenuItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx ReloadWhenScriptFileChangesMenuItem; - private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator4; - private BizHawk.WinForms.Controls.ToolStripMenuItemEx RegisterToTextEditorsSubMenu; - private BizHawk.WinForms.Controls.ToolStripMenuItemEx RegisterSublimeText2MenuItem; - private BizHawk.WinForms.Controls.ToolStripMenuItemEx RegisterNotePadMenuItem; - private BizHawk.WinForms.Controls.ToolStripMenuItemEx GenerateLuaCatsDefinitionMenuItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx SelectAllContextItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx CopyContextItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx ClearRegisteredFunctionsContextItem; private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator5; private BizHawk.WinForms.Controls.ToolStripMenuItemEx ClearRegisteredFunctionsLogContextItem; + private System.Windows.Forms.ToolStripMenuItem integrationToolStripMenuItem; + private ToolStripMenuItemEx GenerateLuaCatsDefinitionMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private ToolStripMenuItemEx RegisterSublimeText2MenuItem; + private ToolStripMenuItemEx RegisterNotePadMenuItem; } } \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index efa2e0b052e..e20e0712dbb 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -1145,7 +1145,7 @@ private void ReloadWhenScriptFileChangesMenuItem_Click(object sender, EventArgs } } - private void RegisterToTextEditorsSubMenu_DropDownOpened(object sender, EventArgs e) + private void IntegrationMenu_DropDownOpened(object sender, EventArgs e) { // Hide until this one is implemented RegisterNotePadMenuItem.Visible = false; From fd7e68b53dae22598e5936a5f3c4bd15583073aa Mon Sep 17 00:00:00 2001 From: feos Date: Mon, 23 Mar 2026 19:17:22 +0300 Subject: [PATCH 10/35] does "Braces XOR single-line" mean this? --- .../lua/Documentation/LuaCatsGenerator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 928ee3f9f03..c8cc54b7d5a 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -70,7 +70,10 @@ public string Generate(LuaDocumentation docs) string libraryDescription = libraryGroup.First().LibraryDescription; if (!string.IsNullOrEmpty(libraryDescription)) + { sb.AppendLine(FormatDescription(libraryDescription)); + } + sb.AppendLine($"---@class {library}"); sb.AppendLine($"{library} = {{}}"); sb.AppendLine(); @@ -78,7 +81,9 @@ public string Generate(LuaDocumentation docs) foreach (var func in libraryGroup.OrderBy(func => func.Name)) { if (!string.IsNullOrEmpty(func.Description)) + { sb.AppendLine(FormatDescription(func.Description)); + } if (func.Example != null) { @@ -93,7 +98,9 @@ public string Generate(LuaDocumentation docs) } if (func.IsDeprecated) + { sb.AppendLine("---@deprecated"); + } foreach (var parameter in func.Method.GetParameters()) { From d729148096a6772cc82ed718f087a09354689704 Mon Sep 17 00:00:00 2001 From: feos Date: Tue, 24 Mar 2026 20:05:32 +0300 Subject: [PATCH 11/35] braces --- .../lua/Documentation/LuaCatsGenerator.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index c8cc54b7d5a..b7908584684 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -61,7 +61,6 @@ public string Generate(LuaDocumentation docs) var sb = new StringBuilder(); sb.AppendLine($"--Generated with BizHawk {VersionInfo.MainVersion}"); - sb.AppendLine(Preamble); foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) @@ -130,7 +129,9 @@ public string Generate(LuaDocumentation docs) foreach (var parameter in func.Method.GetParameters()) { if (parameter.Position > 0) + { sb.Append(", "); + } sb.Append(IsParams(parameter) ? "..." : parameter.Name); } @@ -156,16 +157,22 @@ private static string FormatDescription(string description) private static string GetLuaType(ParameterInfo parameter) { if (parameter.ParameterType == typeof(object) && parameter.GetCustomAttribute() is not null) + { return "luacolor"; // see Preamble + } // no [] array modifier for varargs if (parameter.ParameterType.IsArray && IsParams(parameter)) + { return GetLuaType(parameter.ParameterType.GetElementType()); + } // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used // like `gui.text` and `forms.settext` instead of polluting the entire API surface if (parameter.ParameterType == typeof(string) && parameter.Name is "message" or "caption") + { return "string | number"; + } return GetLuaType(parameter.ParameterType); } @@ -174,16 +181,24 @@ private static string GetLuaType(Type type) { // try this twice, before and after extracting the array/nullable type if (TypeConversions.TryGetValue(type, out string luaType)) + { return luaType; + } if (type.IsArray) + { return GetLuaType(type.GetElementType()) + "[]"; + } if (IsNullable(type)) + { type = type.GetGenericArguments()[0]; + } if (TypeConversions.TryGetValue(type, out luaType)) + { return luaType; + } throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); } From 9a338944f15e24d58e6eb43365f1f15578f87b28 Mon Sep 17 00:00:00 2001 From: feos Date: Tue, 24 Mar 2026 20:07:38 +0300 Subject: [PATCH 12/35] name the definitions file according to Lua Language Server spec --- src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index e20e0712dbb..bb884e2f057 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -1211,7 +1211,7 @@ private void GenerateLuaCatsDefinitionMenuItem_Click(object sender, EventArgs e) ? Path.GetDirectoryName(LuaImp.ScriptList.Filename) : Config!.PathEntries.LuaAbsolutePath(); - if (this.ShowFileSaveDialog(initDir, initFileName: "BizHawk.lua", fileExt: ".lua", filter: JustScriptsFSFilterSet) is string path) + if (this.ShowFileSaveDialog(initDir, initFileName: "emuhawk.d.lua", fileExt: ".d.lua", filter: JustScriptsFSFilterSet) is string path) { File.WriteAllText(path, LuaImp.Docs.ToLuaLanguageServerDefinitions()); } From 687b481d91346df146e9c118f5b28ec0a0cde0d9 Mon Sep 17 00:00:00 2001 From: feos Date: Tue, 24 Mar 2026 20:21:11 +0300 Subject: [PATCH 13/35] proper Program Files folder search --- .../tools/Lua/LuaAutocompleteInstaller.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs index b64121bc68a..aa18d23d1c4 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaAutocompleteInstaller.cs @@ -46,17 +46,19 @@ public void InstallBizLua(TextEditors editor, LuaDocumentation docs) private string AppDataFolder => Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + private string ProgramFilesFolder => Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + private bool IsSublimeInstalled() { // The most likely location of the app, eventually we should consider looking through the registry or installed apps as a more robust way to detect it; - string exePath = @"C:\Program Files\Sublime Text 2\sublime_text.exe"; + string exePath = Path.Combine(ProgramFilesFolder, @"Sublime Text 2\sublime_text.exe"); return File.Exists(exePath); } private bool IsNotepadInstalled() { // The most likely location of the app, eventually we should consider looking through the registry or installed apps as a more robust way to detect it; - string exePath = @"C:\Program Files\Notepad++\notepad++.exe"; + string exePath = Path.Combine(ProgramFilesFolder, @"Notepad++\notepad++.exe"); return File.Exists(exePath); } From f11aa9ce9a2bd0aadc54133adba2b9dfdd18d49e Mon Sep 17 00:00:00 2001 From: kalimag Date: Tue, 24 Mar 2026 23:51:43 +0100 Subject: [PATCH 14/35] Refactor example formatting --- .../lua/Documentation/LuaCatsGenerator.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index b7908584684..c5abdbf1892 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -70,7 +70,7 @@ public string Generate(LuaDocumentation docs) if (!string.IsNullOrEmpty(libraryDescription)) { - sb.AppendLine(FormatDescription(libraryDescription)); + sb.AppendLine(FormatMarkdown(libraryDescription)); } sb.AppendLine($"---@class {library}"); @@ -81,7 +81,7 @@ public string Generate(LuaDocumentation docs) { if (!string.IsNullOrEmpty(func.Description)) { - sb.AppendLine(FormatDescription(func.Description)); + sb.AppendLine(FormatMarkdown(func.Description)); } if (func.Example != null) @@ -89,11 +89,7 @@ public string Generate(LuaDocumentation docs) sb.AppendLine("---"); sb.AppendLine("---Example:"); sb.AppendLine("---"); - sb.AppendFormat("---\t{0}", func.Example - .Replace("\r\n", "\r\n---\t") - .Replace("{{", "`") - .Replace("}}", "`")); - sb.AppendLine(); + sb.AppendLine(FormatMarkdown(func.Example, "---\t")); } if (func.IsDeprecated) @@ -143,15 +139,15 @@ public string Generate(LuaDocumentation docs) return sb.ToString(); } - private static string FormatDescription(string description) + private static string FormatMarkdown(string value, string prefix = "---") { - // prefix every line with --- - description = Regex.Replace(description, "^", "---", RegexOptions.Multiline); + // prefix every line + value = Regex.Replace(value, "^", prefix, RegexOptions.Multiline); // replace {{wiki markup}} with `markdown` - description = Regex.Replace(description, @"{{(.+?)}}", "`$1`"); + value = Regex.Replace(value, "{{(.+?)}}", "`$1`"); // replace wiki image markup with markdown - description = Regex.Replace(description, @"\[(?.+?)\|alt=(?.+?)\]", "![${alt}](${url})"); - return description; + value = Regex.Replace(value, @"\[(?.+?)\|alt=(?.+?)\]", "![${alt}](${url})"); + return value; } private static string GetLuaType(ParameterInfo parameter) From ae30e6b6d9007c79252f559766d8d7f4efe7f39b Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 00:39:41 +0100 Subject: [PATCH 15/35] Preamble, line-ending stuff --- .../lua/Documentation/LuaCatsGenerator.cs | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index c5abdbf1892..26df59c1a53 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -1,3 +1,5 @@ +#pragma warning disable MA0136 // Raw String contains an implicit end of line character, line endings will be normalized + using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -40,27 +42,31 @@ internal class LuaCatsGenerator [typeof(System.Drawing.Color)] = "color", }; - private const string Preamble = @"error(""This is a definition file for Lua Language Server and not a usable script"") + private const string Preamble = """ + -- https://tasvideos.org/Bizhawk + + error("This is a definition file for Lua Language Server and not a usable script") + + ---@meta _ + + ---@class color : userdata + + ---A color in one of the following formats: + --- - Number in the format `0xAARRGGBB` + --- - String in the format `"#RRGGBB"` or `"#AARRGGBB"` + --- - A CSS3/X11 color name e.g. `"blue"`, `"palegoldenrod"` + --- - Color created with `forms.createcolor` + ---@alias luacolor integer | string | color ----@meta _ ---- Lua functions available for the BizHawk emulator ---- https://tasvideos.org/Bizhawk + """; ----@class color : userdata ----A color in one of the following formats: ---- - Number in the format `0xAARRGGBB` ---- - String in the format `""#RRGGBB""` or `""#AARRGGBB""` ---- - A CSS3/X11 color name e.g. `""blue""`, `""palegoldenrod""` ---- - Color created with `forms.createcolor` ----@alias luacolor integer | string | color -"; public string Generate(LuaDocumentation docs) { var sb = new StringBuilder(); - sb.AppendLine($"--Generated with BizHawk {VersionInfo.MainVersion}"); + sb.AppendLine($"-- Lua functions available in EmuHawk {VersionInfo.MainVersion}"); sb.AppendLine(Preamble); foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) @@ -136,7 +142,7 @@ public string Generate(LuaDocumentation docs) } sb.AppendLine(); } - return sb.ToString(); + return sb.ToString().ReplaceLineEndings(); } private static string FormatMarkdown(string value, string prefix = "---") From 9951d792ec7058e756e1b456da298cdf518722ff Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 02:49:04 +0100 Subject: [PATCH 16/35] Make `LuaCanvas` local --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 26df59c1a53..166bb55b7c1 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -73,6 +73,7 @@ public string Generate(LuaDocumentation docs) { string library = libraryGroup.Key; string libraryDescription = libraryGroup.First().LibraryDescription; + var libraryType = libraryGroup.First().Method.DeclaringType; if (!string.IsNullOrEmpty(libraryDescription)) { @@ -80,6 +81,7 @@ public string Generate(LuaDocumentation docs) } sb.AppendLine($"---@class {library}"); + if (!typeof(LuaLibraryBase).IsAssignableFrom(libraryType)) sb.Append("local "); // don't make LuaCanvas global sb.AppendLine($"{library} = {{}}"); sb.AppendLine(); From 3c4d8c286ac78bd40bad0170bdca932a5b97cbdf Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 17:21:10 +0100 Subject: [PATCH 17/35] Refactor `GetLuaType(ParameterInfo)` --- .../lua/Documentation/LuaCatsGenerator.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 166bb55b7c1..f401b3f96d5 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -1,3 +1,5 @@ +#nullable enable + #pragma warning disable MA0136 // Raw String contains an implicit end of line character, line endings will be normalized using System.Collections.Generic; @@ -60,7 +62,17 @@ internal class LuaCatsGenerator """; + private static string? GetHardcodedType(ParameterInfo parameter) + { + // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used + // like `gui.text` and `forms.settext` instead of polluting the entire API surface + if (parameter.Name is "message" or "caption" && parameter.ParameterType == typeof(string)) + { + return "string | number"; + } + return null; + } public string Generate(LuaDocumentation docs) { @@ -160,22 +172,20 @@ private static string FormatMarkdown(string value, string prefix = "---") private static string GetLuaType(ParameterInfo parameter) { - if (parameter.ParameterType == typeof(object) && parameter.GetCustomAttribute() is not null) + if (GetHardcodedType(parameter) is string hardcodedType) { - return "luacolor"; // see Preamble + return hardcodedType; } - // no [] array modifier for varargs - if (parameter.ParameterType.IsArray && IsParams(parameter)) + if (parameter.GetCustomAttribute() is not null) { - return GetLuaType(parameter.ParameterType.GetElementType()); + return "luacolor"; // see Preamble } - // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used - // like `gui.text` and `forms.settext` instead of polluting the entire API surface - if (parameter.ParameterType == typeof(string) && parameter.Name is "message" or "caption") + if (parameter.ParameterType.IsArray && IsParams(parameter)) { - return "string | number"; + // no [] array modifier for varargs + return GetLuaType(parameter.ParameterType.GetElementType()); } return GetLuaType(parameter.ParameterType); From cb486da76ad8d2d396962330c7397a10b0dcc561 Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 17:31:04 +0100 Subject: [PATCH 18/35] Enumerate valid drawingSurface values --- .../lua/Documentation/LuaCatsGenerator.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index f401b3f96d5..188ee099717 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -60,6 +60,9 @@ internal class LuaCatsGenerator --- - Color created with `forms.createcolor` ---@alias luacolor integer | string | color + ---@alias surface + ---| "emucore" # Draw on the emulated screen. Resolution depends on emulated system and game. Drawing is scaled with the rest of the display. + ---| "client" # Draw on the BizHawk window. Resolution depends on the window size. Drawing is not scaled. """; private static string? GetHardcodedType(ParameterInfo parameter) @@ -71,6 +74,11 @@ internal class LuaCatsGenerator return "string | number"; } + if (parameter.Member.DeclaringType.Name == "GuiLuaLibrary" && parameter.Name == "surfaceName" && parameter.ParameterType == typeof(string)) + { + return "surface"; + } + return null; } @@ -80,6 +88,7 @@ public string Generate(LuaDocumentation docs) sb.AppendLine($"-- Lua functions available in EmuHawk {VersionInfo.MainVersion}"); sb.AppendLine(Preamble); + sb.AppendLine(); foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) { From d8f7719adc80b309db1e4c667d19dfe451c53fc4 Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 17:34:05 +0100 Subject: [PATCH 19/35] It's free braces --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 188ee099717..b607e0018d3 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -136,7 +136,9 @@ public string Generate(LuaDocumentation docs) { sb.Append($"---@param {parameter.Name}"); if (parameter.IsOptional || IsNullable(parameter.ParameterType)) + { sb.Append('?'); + } } sb.Append(' '); From 03bbaab23b7dab06a002c84d5fd9d136c0830739 Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 17:35:41 +0100 Subject: [PATCH 20/35] Make class static --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index b607e0018d3..d7aeaaf8567 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -18,7 +18,7 @@ namespace BizHawk.Client.Common; /// /// See https://luals.github.io/wiki/annotations /// -internal class LuaCatsGenerator +internal static class LuaCatsGenerator { private static readonly Dictionary TypeConversions = new() { From 928d55a10f244dcaed48af335c56705e5cbafe6a Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 25 Mar 2026 17:38:41 +0100 Subject: [PATCH 21/35] Make class static Part II --- .../lua/Documentation/LuaCatsGenerator.cs | 2 +- src/BizHawk.Client.Common/lua/LuaDocumentation.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index d7aeaaf8567..7b29017d36d 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -82,7 +82,7 @@ internal static class LuaCatsGenerator return null; } - public string Generate(LuaDocumentation docs) + public static string Generate(LuaDocumentation docs) { var sb = new StringBuilder(); diff --git a/src/BizHawk.Client.Common/lua/LuaDocumentation.cs b/src/BizHawk.Client.Common/lua/LuaDocumentation.cs index 3dd3b46b6a8..6ab5c43fe7e 100644 --- a/src/BizHawk.Client.Common/lua/LuaDocumentation.cs +++ b/src/BizHawk.Client.Common/lua/LuaDocumentation.cs @@ -202,8 +202,7 @@ public string ToNotepadPlusPlusAutoComplete() public string ToLuaLanguageServerDefinitions() { - var generator = new LuaCatsGenerator(); - return generator.Generate(this); + return LuaCatsGenerator.Generate(this); } } From f77280942aec68ad0519c3dde61cd9416eb3ce72 Mon Sep 17 00:00:00 2001 From: feos Date: Fri, 27 Mar 2026 23:34:31 +0300 Subject: [PATCH 22/35] split into individual files? revert if we don't want this anymore, but with LLS's ability to take in entire folders via config this feels nicer IMO and yeah I wouldn't mind adding this output to releases under something like `_luacats_definitions` folder in `Assets/Lua` --- .../lua/Documentation/LuaCatsGenerator.cs | 57 ++++++++++++------- .../lua/LuaDocumentation.cs | 4 +- .../tools/Lua/LuaConsole.cs | 32 +++++++++-- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 7b29017d36d..b91b9289442 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -3,6 +3,7 @@ #pragma warning disable MA0136 // Raw String contains an implicit end of line character, line endings will be normalized using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -44,26 +45,28 @@ internal static class LuaCatsGenerator [typeof(System.Drawing.Color)] = "color", }; - private const string Preamble = """ - -- https://tasvideos.org/Bizhawk + private const string Classes = """ +---@class color : userdata - error("This is a definition file for Lua Language Server and not a usable script") +---A color in one of the following formats: +--- - Number in the format `0xAARRGGBB` +--- - String in the format `"#RRGGBB"` or `"#AARRGGBB"` +--- - A CSS3/X11 color name e.g. `"blue"`, `"palegoldenrod"` +--- - Color created with `forms.createcolor` +---@alias luacolor integer | string | color - ---@meta _ +---@alias surface +---| "emucore" # Draw on the emulated screen. Resolution depends on emulated system and game. Drawing is scaled with the rest of the display. +---| "client" # Draw on the BizHawk window. Resolution depends on the window size. Drawing is not scaled. +"""; - ---@class color : userdata + private const string Preamble = """ +-- https://tasvideos.org/Bizhawk - ---A color in one of the following formats: - --- - Number in the format `0xAARRGGBB` - --- - String in the format `"#RRGGBB"` or `"#AARRGGBB"` - --- - A CSS3/X11 color name e.g. `"blue"`, `"palegoldenrod"` - --- - Color created with `forms.createcolor` - ---@alias luacolor integer | string | color +error("This is a definition file for Lua Language Server and not a usable script") - ---@alias surface - ---| "emucore" # Draw on the emulated screen. Resolution depends on emulated system and game. Drawing is scaled with the rest of the display. - ---| "client" # Draw on the BizHawk window. Resolution depends on the window size. Drawing is not scaled. - """; +---@meta _ +"""; private static string? GetHardcodedType(ParameterInfo parameter) { @@ -82,19 +85,29 @@ internal static class LuaCatsGenerator return null; } - public static string Generate(LuaDocumentation docs) + public static void Generate(LuaDocumentation docs, string path) { - var sb = new StringBuilder(); + var sb0 = new StringBuilder(); + + sb0.AppendLine($"-- Lua functions available in EmuHawk {VersionInfo.MainVersion}"); + sb0.AppendLine(Preamble); + sb0.AppendLine(); + sb0.AppendLine(Classes); + sb0.AppendLine(); - sb.AppendLine($"-- Lua functions available in EmuHawk {VersionInfo.MainVersion}"); - sb.AppendLine(Preamble); - sb.AppendLine(); + File.WriteAllText(Path.Combine(path, "classes.d.lua"), sb0.ToString().ReplaceLineEndings()); foreach (var libraryGroup in docs.GroupBy(func => func.Library).OrderBy(group => group.Key)) { string library = libraryGroup.Key; string libraryDescription = libraryGroup.First().LibraryDescription; var libraryType = libraryGroup.First().Method.DeclaringType; + var filePath = Path.Combine(path, library + ".d.lua"); + var sb = new StringBuilder(); + + sb.AppendLine($"-- Lua functions available in EmuHawk {VersionInfo.MainVersion}"); + sb.AppendLine(Preamble); + sb.AppendLine(); if (!string.IsNullOrEmpty(libraryDescription)) { @@ -165,9 +178,9 @@ public static string Generate(LuaDocumentation docs) sb.AppendLine(") end"); sb.AppendLine(); } - sb.AppendLine(); + sb.AppendLine($"return {library}"); + File.WriteAllText(filePath, sb.ToString().ReplaceLineEndings()); } - return sb.ToString().ReplaceLineEndings(); } private static string FormatMarkdown(string value, string prefix = "---") diff --git a/src/BizHawk.Client.Common/lua/LuaDocumentation.cs b/src/BizHawk.Client.Common/lua/LuaDocumentation.cs index 6ab5c43fe7e..eff42860d74 100644 --- a/src/BizHawk.Client.Common/lua/LuaDocumentation.cs +++ b/src/BizHawk.Client.Common/lua/LuaDocumentation.cs @@ -200,9 +200,9 @@ public string ToNotepadPlusPlusAutoComplete() return ""; // TODO } - public string ToLuaLanguageServerDefinitions() + public void ToLuaLanguageServerDefinitions(string path) { - return LuaCatsGenerator.Generate(this); + LuaCatsGenerator.Generate(this, path); } } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs index bb884e2f057..9d485f63be7 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/LuaConsole.cs @@ -1207,13 +1207,33 @@ private void RegisterNotePadMenuItem_Click(object sender, EventArgs e) private void GenerateLuaCatsDefinitionMenuItem_Click(object sender, EventArgs e) { - string initDir = !string.IsNullOrWhiteSpace(LuaImp.ScriptList.Filename) - ? Path.GetDirectoryName(LuaImp.ScriptList.Filename) - : Config!.PathEntries.LuaAbsolutePath(); - - if (this.ShowFileSaveDialog(initDir, initFileName: "emuhawk.d.lua", fileExt: ".d.lua", filter: JustScriptsFSFilterSet) is string path) + DialogResult result; + string selectedPath; + string description = "Set the directory for LuaCATS definitions"; + if (OSTailoredCode.IsUnixHost) + { + // FolderBrowserEx doesn't work in Mono for obvious reasons + using var f = new FolderBrowserDialog + { + Description = description, + SelectedPath = Config!.PathEntries.LuaAbsolutePath(), + }; + result = f.ShowDialog(); + selectedPath = f.SelectedPath; + } + else + { + using var f = new FolderBrowserEx + { + Description = description, + SelectedPath = Config!.PathEntries.LuaAbsolutePath(), + }; + result = f.ShowDialog(); + selectedPath = f.SelectedPath; + } + if (result.IsOk()) { - File.WriteAllText(path, LuaImp.Docs.ToLuaLanguageServerDefinitions()); + LuaImp.Docs.ToLuaLanguageServerDefinitions(selectedPath); } } From 6cb96a23f3799d8099ff720cac0e8c1b1e9469f6 Mon Sep 17 00:00:00 2001 From: kalimag Date: Sun, 29 Mar 2026 18:25:19 +0200 Subject: [PATCH 23/35] Remove return statement from definition files These are globals rather than `require`'d imports --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index b91b9289442..5d981f969f2 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -178,7 +178,6 @@ public static void Generate(LuaDocumentation docs, string path) sb.AppendLine(") end"); sb.AppendLine(); } - sb.AppendLine($"return {library}"); File.WriteAllText(filePath, sb.ToString().ReplaceLineEndings()); } } From 6667a7bf0d1c76fe161508fdff2dd1ceeaac64c4 Mon Sep 17 00:00:00 2001 From: kalimag Date: Sun, 29 Mar 2026 20:53:57 +0200 Subject: [PATCH 24/35] Include default parameter values --- .../lua/Documentation/LuaCatsGenerator.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 5d981f969f2..0891c642c3f 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -155,7 +155,12 @@ public static void Generate(LuaDocumentation docs, string path) } sb.Append(' '); - sb.AppendLine(GetLuaType(parameter)); + sb.Append(GetLuaType(parameter)); + if (parameter.HasDefaultValue && parameter.DefaultValue is not null and not "") + { + sb.Append($" Defaults to `{FormatValue(parameter.DefaultValue)}`"); + } + sb.AppendLine(); } if (func.Method.ReturnType != typeof(void)) @@ -193,6 +198,15 @@ private static string FormatMarkdown(string value, string prefix = "---") return value; } + private static string FormatValue(object value) => value switch + { + string str => $"\"{str}\"", + true => "true", + false => "false", + null => "nil", + _ => value.ToString() + }; + private static string GetLuaType(ParameterInfo parameter) { if (GetHardcodedType(parameter) is string hardcodedType) From db4ce3445984f91c0adf584e9b11a815d4d6b613 Mon Sep 17 00:00:00 2001 From: kalimag Date: Sun, 29 Mar 2026 20:59:44 +0200 Subject: [PATCH 25/35] Fix code style --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 0891c642c3f..e45f8f6c833 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -204,7 +204,7 @@ private static string FormatMarkdown(string value, string prefix = "---") true => "true", false => "false", null => "nil", - _ => value.ToString() + _ => value.ToString(), }; private static string GetLuaType(ParameterInfo parameter) From eb210aedb12d1f4a8958d350f4ac25bdbb761efb Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 30 Mar 2026 21:23:20 +0200 Subject: [PATCH 26/35] Rename color aliases --- .../lua/Documentation/LuaCatsGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index e45f8f6c833..4f16f990cff 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -42,18 +42,18 @@ internal static class LuaCatsGenerator [typeof(ReadOnlyMemory)] = "string", [typeof(LuaFunction)] = "function", [typeof(LuaTable)] = "table", - [typeof(System.Drawing.Color)] = "color", + [typeof(System.Drawing.Color)] = "dotnetcolor", }; private const string Classes = """ ----@class color : userdata +---@class dotnetcolor : userdata ---A color in one of the following formats: --- - Number in the format `0xAARRGGBB` --- - String in the format `"#RRGGBB"` or `"#AARRGGBB"` --- - A CSS3/X11 color name e.g. `"blue"`, `"palegoldenrod"` --- - Color created with `forms.createcolor` ----@alias luacolor integer | string | color +---@alias color integer | string | dotnetcolor ---@alias surface ---| "emucore" # Draw on the emulated screen. Resolution depends on emulated system and game. Drawing is scaled with the rest of the display. @@ -216,7 +216,7 @@ private static string GetLuaType(ParameterInfo parameter) if (parameter.GetCustomAttribute() is not null) { - return "luacolor"; // see Preamble + return "color"; // see Preamble } if (parameter.ParameterType.IsArray && IsParams(parameter)) From 3b79e1fe6fd836da2279f0b649a4c3f20321dff0 Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 30 Mar 2026 21:28:05 +0200 Subject: [PATCH 27/35] Fix name collision with `userdata` --- .../lua/Documentation/LuaCatsGenerator.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 4f16f990cff..4c0758b0f39 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -114,7 +114,7 @@ public static void Generate(LuaDocumentation docs, string path) sb.AppendLine(FormatMarkdown(libraryDescription)); } - sb.AppendLine($"---@class {library}"); + sb.AppendLine($"---@class {SafeLibraryTypeName(library)}"); if (!typeof(LuaLibraryBase).IsAssignableFrom(libraryType)) sb.Append("local "); // don't make LuaCanvas global sb.AppendLine($"{library} = {{}}"); sb.AppendLine(); @@ -207,6 +207,17 @@ private static string FormatMarkdown(string value, string prefix = "---") _ => value.ToString(), }; + /// + /// Avoid name collisions with existing Lua types. + /// Only for the @class annotation, not the name of the global. + /// + /// + private static string SafeLibraryTypeName(string name) => name switch + { + "userdata" => $"biz{name}", + _ => name, + }; + private static string GetLuaType(ParameterInfo parameter) { if (GetHardcodedType(parameter) is string hardcodedType) From 12da1a4deafca20b02ca0caf522c722f03b91e92 Mon Sep 17 00:00:00 2001 From: kalimag Date: Thu, 2 Apr 2026 12:40:42 +0200 Subject: [PATCH 28/35] Mark zero-indexed return values --- .../lua/Documentation/LuaCatsGenerator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 4c0758b0f39..c091a2d79c0 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -166,7 +166,10 @@ public static void Generate(LuaDocumentation docs, string path) if (func.Method.ReturnType != typeof(void)) { sb.Append("---@return "); - sb.AppendLine(GetLuaType(func.Method.ReturnType)); + sb.Append(GetLuaType(func.Method.ReturnParameter)); + if (IsZeroIndexed(func.Method.ReturnParameter)) + sb.Append(" # Zero-indexed array."); + sb.AppendLine(); } sb.Append($"function {library}.{func.Name}("); @@ -268,4 +271,6 @@ private static string GetLuaType(Type type) private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; + + private static bool IsZeroIndexed(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; } From 29b153e8db96cfad98af57b8917543a12b533294 Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 13 Apr 2026 00:37:45 +0200 Subject: [PATCH 29/35] Handle zero-indexed parameters --- .../lua/Documentation/LuaCatsGenerator.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index c091a2d79c0..a616e0d8c4f 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -156,6 +156,10 @@ public static void Generate(LuaDocumentation docs, string path) sb.Append(' '); sb.Append(GetLuaType(parameter)); + if (IsZeroIndexed(parameter)) + { + sb.Append(" Zero-indexed array."); + } if (parameter.HasDefaultValue && parameter.DefaultValue is not null and not "") { sb.Append($" Defaults to `{FormatValue(parameter.DefaultValue)}`"); @@ -168,7 +172,9 @@ public static void Generate(LuaDocumentation docs, string path) sb.Append("---@return "); sb.Append(GetLuaType(func.Method.ReturnParameter)); if (IsZeroIndexed(func.Method.ReturnParameter)) + { sb.Append(" # Zero-indexed array."); + } sb.AppendLine(); } From 3350d27d4498f86af545ffe9cf780f729a64ea06 Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 15 Apr 2026 18:13:38 +0200 Subject: [PATCH 30/35] Support Nullable Reference Type annotations --- .../lua/Documentation/LuaCatsGenerator.cs | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index a616e0d8c4f..58adf3b1aef 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -148,7 +148,7 @@ public static void Generate(LuaDocumentation docs, string path) else { sb.Append($"---@param {parameter.Name}"); - if (parameter.IsOptional || IsNullable(parameter.ParameterType)) + if (CanBeNil(parameter)) { sb.Append('?'); } @@ -170,7 +170,13 @@ public static void Generate(LuaDocumentation docs, string path) if (func.Method.ReturnType != typeof(void)) { sb.Append("---@return "); - sb.Append(GetLuaType(func.Method.ReturnParameter)); + var luaType = GetLuaType(func.Method.ReturnParameter); + var nilable = CanBeNil(func.Method.ReturnParameter); + var wrapType = nilable && luaType.IndexOfAny([ ':', '|' ]) != -1; // ? is ambiguous on complex types like `string|int` or `fun(): string` + if (wrapType) sb.Append('('); + sb.Append(luaType); + if (wrapType) sb.Append(')'); + if (nilable) sb.Append('?'); if (IsZeroIndexed(func.Method.ReturnParameter)) { sb.Append(" # Zero-indexed array."); @@ -261,7 +267,7 @@ private static string GetLuaType(Type type) return GetLuaType(type.GetElementType()) + "[]"; } - if (IsNullable(type)) + if (IsNullableValueType(type)) { type = type.GetGenericArguments()[0]; } @@ -274,7 +280,48 @@ private static string GetLuaType(Type type) throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); } - private static bool IsNullable(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + private static bool CanBeNil(ParameterInfo parameter) => parameter.HasDefaultValue || IsNullableValueType(parameter.ParameterType) || IsNullableReferenceType(parameter); + + private static bool IsNullableValueType(Type type) => type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + + /// + /// Returns if is a reference type and is annotated as nullable or lacks NRT annotations. + /// + /// + /// Only handles "top-level" types, not array elements or generic type parameters. + /// + private static bool IsNullableReferenceType(ParameterInfo parameter) + { + if (parameter.ParameterType.IsValueType) return false; + + // https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md + const byte AnnotatedNotNull = 1; + + // Check [Nullable] on the parameter first + if (GetNullableFlags(parameter) is byte[] flags) + { + return flags[0] != AnnotatedNotNull; + } + + // Check [NullableContext] on the method and parent types + var parent = parameter.Member; + while (parent is not null) + { + if (GetNullableContext(parent) is byte flag) + { + return flag != AnnotatedNotNull; + } + parent = parent.DeclaringType; + } + + return true; + + // Attributes may be compiled into each assembly, so can't be strongly typed + static byte[]? GetNullableFlags(ParameterInfo parameter) => + ((dynamic) parameter.GetCustomAttributes().SingleOrDefault(attr => attr.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute"))?.NullableFlags; + static byte? GetNullableContext(MemberInfo member) => + ((dynamic) member.GetCustomAttributes().SingleOrDefault(attr => attr.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute"))?.Flag; + } private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; From 1db4234680ac8a4a77f1a1a0a05a9312cc729636 Mon Sep 17 00:00:00 2001 From: kalimag Date: Wed, 15 Apr 2026 18:18:30 +0200 Subject: [PATCH 31/35] Revert "wip" - unrelated submodule change This reverts commit c9fbcdcfc7c436bb0daa5f9053fcb6549519e475. --- waterbox/dsda/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waterbox/dsda/core b/waterbox/dsda/core index dafa0543c59..c0b95b8980f 160000 --- a/waterbox/dsda/core +++ b/waterbox/dsda/core @@ -1 +1 @@ -Subproject commit dafa0543c59f901c15901e50e3dbdfc6c35e6534 +Subproject commit c0b95b8980fee368377768a56093343ff421d982 From 06bdc0adab2ad0305c4d72ac8f5573955487589a Mon Sep 17 00:00:00 2001 From: kalimag Date: Fri, 17 Apr 2026 04:57:58 +0200 Subject: [PATCH 32/35] Switch to NRT extensions --- .../lua/Documentation/LuaCatsGenerator.cs | 52 ++----------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 58adf3b1aef..3bdc13fbc14 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.RegularExpressions; using BizHawk.Common; +using BizHawk.Common.ReflectionExtensions; using NLua; namespace BizHawk.Client.Common; @@ -148,7 +149,7 @@ public static void Generate(LuaDocumentation docs, string path) else { sb.Append($"---@param {parameter.Name}"); - if (CanBeNil(parameter)) + if (parameter.HasDefaultValue || (parameter.IsNRTOrNullableT() ?? true)) { sb.Append('?'); } @@ -171,7 +172,7 @@ public static void Generate(LuaDocumentation docs, string path) { sb.Append("---@return "); var luaType = GetLuaType(func.Method.ReturnParameter); - var nilable = CanBeNil(func.Method.ReturnParameter); + var nilable = func.Method.ReturnParameter.IsNRTOrNullableT() ?? true; var wrapType = nilable && luaType.IndexOfAny([ ':', '|' ]) != -1; // ? is ambiguous on complex types like `string|int` or `fun(): string` if (wrapType) sb.Append('('); sb.Append(luaType); @@ -267,9 +268,9 @@ private static string GetLuaType(Type type) return GetLuaType(type.GetElementType()) + "[]"; } - if (IsNullableValueType(type)) + if (Nullable.GetUnderlyingType(type) is Type underlyingType) { - type = type.GetGenericArguments()[0]; + type = underlyingType; } if (TypeConversions.TryGetValue(type, out luaType)) @@ -280,49 +281,6 @@ private static string GetLuaType(Type type) throw new NotSupportedException($"Unknown type {type.FullName} used in API. Generator must be updated to handle this."); } - private static bool CanBeNil(ParameterInfo parameter) => parameter.HasDefaultValue || IsNullableValueType(parameter.ParameterType) || IsNullableReferenceType(parameter); - - private static bool IsNullableValueType(Type type) => type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - - /// - /// Returns if is a reference type and is annotated as nullable or lacks NRT annotations. - /// - /// - /// Only handles "top-level" types, not array elements or generic type parameters. - /// - private static bool IsNullableReferenceType(ParameterInfo parameter) - { - if (parameter.ParameterType.IsValueType) return false; - - // https://github.com/dotnet/roslyn/blob/main/docs/features/nullable-metadata.md - const byte AnnotatedNotNull = 1; - - // Check [Nullable] on the parameter first - if (GetNullableFlags(parameter) is byte[] flags) - { - return flags[0] != AnnotatedNotNull; - } - - // Check [NullableContext] on the method and parent types - var parent = parameter.Member; - while (parent is not null) - { - if (GetNullableContext(parent) is byte flag) - { - return flag != AnnotatedNotNull; - } - parent = parent.DeclaringType; - } - - return true; - - // Attributes may be compiled into each assembly, so can't be strongly typed - static byte[]? GetNullableFlags(ParameterInfo parameter) => - ((dynamic) parameter.GetCustomAttributes().SingleOrDefault(attr => attr.GetType().FullName == "System.Runtime.CompilerServices.NullableAttribute"))?.NullableFlags; - static byte? GetNullableContext(MemberInfo member) => - ((dynamic) member.GetCustomAttributes().SingleOrDefault(attr => attr.GetType().FullName == "System.Runtime.CompilerServices.NullableContextAttribute"))?.Flag; - } - private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; private static bool IsZeroIndexed(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; From f1cf53fd755c8cbd99617193e5390a9398f80bd1 Mon Sep 17 00:00:00 2001 From: kalimag Date: Sun, 19 Apr 2026 19:03:47 +0200 Subject: [PATCH 33/35] Adjust annotation whitespace and comment --- .../lua/Documentation/LuaCatsGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 3bdc13fbc14..163e61df9aa 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -71,11 +71,11 @@ internal static class LuaCatsGenerator private static string? GetHardcodedType(ParameterInfo parameter) { - // technically any string parameter can be passed a number, but let's just focus on the ones where it's commonly used + // Technically any string parameter can be passed a number in BizHawk's Lua API, but let's just focus on the ones where it's commonly used // like `gui.text` and `forms.settext` instead of polluting the entire API surface if (parameter.Name is "message" or "caption" && parameter.ParameterType == typeof(string)) { - return "string | number"; + return "string|number"; } if (parameter.Member.DeclaringType.Name == "GuiLuaLibrary" && parameter.Name == "surfaceName" && parameter.ParameterType == typeof(string)) From a48d0754f58b2d9d04037c0c7a766e31b862e539 Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 20 Apr 2026 06:59:35 +0200 Subject: [PATCH 34/35] Sort color union --- src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index 163e61df9aa..f61ecee3213 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -54,7 +54,7 @@ internal static class LuaCatsGenerator --- - String in the format `"#RRGGBB"` or `"#AARRGGBB"` --- - A CSS3/X11 color name e.g. `"blue"`, `"palegoldenrod"` --- - Color created with `forms.createcolor` ----@alias color integer | string | dotnetcolor +---@alias color dotnetcolor | integer | string ---@alias surface ---| "emucore" # Draw on the emulated screen. Resolution depends on emulated system and game. Drawing is scaled with the rest of the display. From 4c08946a2443f38507b8cdbe2ea4c10ae842d95f Mon Sep 17 00:00:00 2001 From: kalimag Date: Mon, 20 Apr 2026 07:01:25 +0200 Subject: [PATCH 35/35] Annotate more specific `createcanvas` return type --- .../lua/Documentation/LuaCatsGenerator.cs | 7 +++++++ src/BizHawk.Client.Common/lua/LuaAttributes.cs | 10 ++++++++++ .../tools/Lua/Libraries/GuiLuaLibrary.cs | 1 + 3 files changed, 18 insertions(+) diff --git a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs index f61ecee3213..906a2b7f471 100644 --- a/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs +++ b/src/BizHawk.Client.Common/lua/Documentation/LuaCatsGenerator.cs @@ -236,6 +236,11 @@ private static string FormatMarkdown(string value, string prefix = "---") private static string GetLuaType(ParameterInfo parameter) { + if (GetOverrideType(parameter) is string overrideType) + { + return overrideType; + } + if (GetHardcodedType(parameter) is string hardcodedType) { return hardcodedType; @@ -284,4 +289,6 @@ private static string GetLuaType(Type type) private static bool IsParams(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; private static bool IsZeroIndexed(ParameterInfo parameter) => parameter.GetCustomAttribute() is not null; + + private static string? GetOverrideType(ParameterInfo parameter) => parameter.GetCustomAttribute()?.Type; } diff --git a/src/BizHawk.Client.Common/lua/LuaAttributes.cs b/src/BizHawk.Client.Common/lua/LuaAttributes.cs index ffa75dd81d0..cc9624f923c 100644 --- a/src/BizHawk.Client.Common/lua/LuaAttributes.cs +++ b/src/BizHawk.Client.Common/lua/LuaAttributes.cs @@ -44,4 +44,14 @@ public LuaLibraryAttribute(bool released) /// [AttributeUsage(AttributeTargets.ReturnValue | AttributeTargets.Parameter)] public sealed class LuaZeroIndexedAttribute : Attribute { } + + /// + /// Specifies a custom type to be used in LuaCATS annotations, for documentation purposes only. + /// See and for possible values. + /// + [AttributeUsage(AttributeTargets.ReturnValue | AttributeTargets.Parameter)] + public sealed class LuaCatsTypeAttribute(string Type) : Attribute + { + public string Type { get; } = Type; + } } diff --git a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs index 4c06f27caa2..17583a1b9ae 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Lua/Libraries/GuiLuaLibrary.cs @@ -360,6 +360,7 @@ public void Text( description: "Creates a dedicated canvas window, returning a table containing some callbacks for drawing. These are the LuaCanvas functions in the API reference." + " The width and height parameters determine the size of the canvas." + " If the x and y parameters are both nil/unset, the form (window) will appear at the default position. If both are specified, the form will be positioned at (x, y) on the screen.")] // technically x can be specified w/o y but let's leave that as UB + [return: LuaCatsType("LuaCanvas")] public LuaTable CreateCanvas(int width, int height, int? x = null, int? y = null) { var canvas = new LuaCanvas(APIs.Emulation, PathEntries, width, height, x, y, _th, LogOutputCallback);