Skip to content

代码提示

在vscode中写lua代码,安装插件lua,这个插件是采用类似JSDoc的注释方式实现代码提示。
对于require导入的脚本都能得到有效提示,但有时候我们2个脚本之间调用没有产生关系,就无法进行跳转,这个时候可以手动添加@see标签解决。 例如在skynet中服务之间的调用:

lua
--这是gate服务,首先要在gate中添加@class标签,表明gate类型
---@class gate
local CMD = {}
function CMD.kick()
end

--这是调用gate服务的地方,添加@see标签
---@see gate.kick
skynet.call('gate', 'lua', 'kick', uid)

这个插件的注释功能很强大也很好用,不多说,详见文档。

protobuf中实现代码提示

做网络通讯常用protobuf,下面代码是我通过AI和修改实现的,为proto文件生成lua代码注释

lua
--- 解析 proto 文件并生成注解
local function parse_proto(proto_file)
    local annotations = {}
    local enums = {}
    local class_definitions = {}
    local current_class = nil
    local proto_lines = {}

    local proto_to_lua_types = {
        int32 = "number", uint32 = "number", sint32 = "number",
        int64 = "number", uint64 = "number", sint64 = "number",
        fixed32 = "number", sfixed32 = "number", fixed64 = "number", sfixed64 = "number",
        float = "number", double = "number",
        bool = "boolean",
        string = "string",
        bytes = "string"
    }

    -- 读取 proto 文件的所有行
    for line in io.lines(proto_file) do
        table.insert(proto_lines, line)
    end

    -- 第一阶段: 处理所有的 enum 定义
    for _, line in ipairs(proto_lines) do
        if not line:match("^%s*$") and not line:match("^%s*//") then
            local enum_name = line:match("^%s*enum%s+(%w+)")
            if enum_name then
                current_class = enum_name
                enums[current_class] = {}
                class_definitions[current_class] = "enum"
                goto continue
            end

            local enum_key, enum_value, enum_comment = line:match("^%s*(%w+)%s*=%s*(%d+);%s*///?%s*(.*)")
            if not enum_key and not enum_value and not enum_comment then
                enum_key, enum_value, enum_comment = line:match("^%s*(%w+)%s*=%s*(%d+);%s*/-%s*(.*)")
            end
            if enum_key and enum_value then
                if enums[current_class] then
                    enums[current_class][tonumber(enum_value)] = { key = enum_key, comment = enum_comment }
                end
                goto continue
            end

            if line:match("^%s*}") and class_definitions[current_class] == "enum" then
                current_class = nil
                goto continue
            end
        end

        ::continue::
    end

    -- 第二阶段: 处理 message 和字段
    current_class = nil
    for _, line in ipairs(proto_lines) do
        if not line:match("^%s*$") and not line:match("^%s*//") then
            local message_name = line:match("^%s*message%s+(%w+)")
            if message_name then
                current_class = message_name
                table.insert(annotations, string.format("---@class %s", current_class))
                class_definitions[current_class] = "message"
                goto continue
            end

            if current_class then
                local repeated_type, repeated_name, repeated_comment = line:match("^%s*repeated%s+(%w+)%s+(%w+)%s*=%s*%d+;?%s*///?%s*(.*)")
                if not repeated_type and not repeated_name and not repeated_comment then
                    repeated_type, repeated_name, repeated_comment = line:match("^%s*repeated%s+(%w+)%s+(%w+)%s*=%s*%d+;?%s*/-%s*(.*)")
                end
                if repeated_type and repeated_name then
                    local lua_type = proto_to_lua_types[repeated_type] or repeated_type
                    if enums[lua_type] then
                        lua_type = 'number'
                    end
                    table.insert(annotations, string.format("---@field %s? %s[] %s", repeated_name, lua_type, repeated_comment and ("@ " .. repeated_comment) or ""))
                    goto continue
                end

                local key_type, value_type, map_name, map_comment = line:match("^%s*map<(%w+),%s*(%w+)>%s+(%w+)%s*=%s*%d+;?%s*///?%s*(.*)")
                if not key_type and not value_type and not map_name and not map_comment then
                    key_type, value_type, map_name, map_comment = line:match("^%s*map<(%w+),%s*(%w+)>%s+(%w+)%s*=%s*%d+;?%s*/-%s*(.*)")
                end
                if key_type and value_type and map_name then
                    local lua_key_type = proto_to_lua_types[key_type] or key_type
                    local lua_value_type = proto_to_lua_types[value_type] or value_type
                    if enums[lua_key_type] then
                        lua_key_type = 'number'
                    end
                    if enums[lua_value_type] then
                        lua_value_type = 'number'
                    end
                    table.insert(annotations, string.format("---@field %s? table<%s, %s> %s", map_name, lua_key_type, lua_value_type, map_comment and ("@ " .. map_comment) or ""))
                    goto continue
                end

                local field_type, field_name, field_comment = line:match("^%s*(%w+)%s+(%w+)%s*=%s*%d+;?%s*///?%s*(.*)")
                if not field_type and not field_name and not field_comment then
                    field_type, field_name, field_comment = line:match("^%s*(%w+)%s+(%w+)%s*=%s*%d+;?%s*/-%s*(.*)")
                end
                if field_type and field_name then
                    local lua_type
                    if enums[field_type] then
                        local enum_values = {}
                        local enum_comments = {}
                        for value, enum_data in pairs(enums[field_type]) do
                            table.insert(enum_values, tostring(value))
                            table.insert(enum_comments, tostring(value) .. ':' .. enum_data.comment)
                        end
                        lua_type = table.concat(enum_values, "|")
                        field_comment = field_comment .. ' ' .. table.concat(enum_comments, "|")
                    else
                        lua_type = proto_to_lua_types[field_type] or field_type
                    end
                    table.insert(annotations, string.format("---@field %s? %s @ %s", field_name, lua_type, field_comment or ""))
                    goto continue
                end
            end

            if line:match("^%s*}") and class_definitions[current_class] == "message" then
                current_class = nil
                table.insert(annotations, "")
            end
        end

        ::continue::
    end

    -- 输出注解
    local output = {}
    for class, type in pairs(class_definitions) do
        if type == "enum" then
            if enums[class] then
                table.insert(output, string.format("---@class %s", class))
                for value, enum_data in pairs(enums[class]) do
                    table.insert(output, string.format("---@field %s %d @ %s", enum_data.key, value, enum_data.comment))
                end
                table.insert(output, "")
            end
        end
    end

    local final_annotations = table.concat(annotations, "\n") .. "\n" .. table.concat(output, "\n")
    return final_annotations
end

-- 使用该函数生成注解并打印
local proto_file = "./comm.proto"  -- 请确保此文件路径正确
local annotations = parse_proto(proto_file)
file = io.open('./annotation.lua', 'w+')
if not file then return end
file:write(annotations)
file:close()
print('----over')

示例

下面给出部分proto文件对应生成的lua代码注释

lua
--[[
message GameStart{
	uint32 index = 2;				///编号
	repeated uint32 cards = 3;		///卡牌
	GtType type = 4; 				///类型
	map<uint32, string> desc = 6;
	map<string, GtType> mapEnum = 8; ///怎样
	repeated GtType reEnum = 10;	///
}

enum GtType {
	idle = 1; ///待机
	begin = 2; ///开始
	over = 3; ///结束
}
]]

--生成的注释如下
---@class GameStart
---@field index? number @ 编号
---@field cards? number[] @ 卡牌
---@field type? 1|2|3 @ 类型 1:待机|2:开始|3:结束
---@field desc? table<number, string> @ 
---@field mapEnum? table<string, number> @ 怎样
---@field reEnum? number[] @ 

--使用时进行类型标注就可以有代码提示了
---@type GameStart
local data = {}