Add "vibe-coded" go-helper utils
This commit is contained in:
353
lua/scripts/go-gen-constructor.lua
Normal file
353
lua/scripts/go-gen-constructor.lua
Normal file
@ -0,0 +1,353 @@
|
||||
local M = {}
|
||||
|
||||
-- Get the struct name from the current line
|
||||
local function get_struct_name()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
|
||||
-- Try to match: type StructName struct
|
||||
local struct_name = line:match("^type%s+([%w_]+)%s+struct")
|
||||
|
||||
-- If not found, try to match if cursor is on struct definition
|
||||
if not struct_name then
|
||||
struct_name = line:match("type%s+([%w_]+)%s+struct")
|
||||
end
|
||||
|
||||
return struct_name
|
||||
end
|
||||
|
||||
-- Check if constructor already exists and find its location
|
||||
local function find_existing_constructor(constructor_name, bufnr)
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
-- Look for function definition with constructor name
|
||||
if line:match("^func%s+" .. constructor_name .. "%s*%(") then
|
||||
local start_line = i
|
||||
local end_line = i
|
||||
|
||||
-- Find the end of the function (matching braces)
|
||||
local bracket_count = 0
|
||||
for j = i, #lines do
|
||||
for char in lines[j]:gmatch(".") do
|
||||
if char == "{" then
|
||||
bracket_count = bracket_count + 1
|
||||
elseif char == "}" then
|
||||
bracket_count = bracket_count - 1
|
||||
end
|
||||
end
|
||||
|
||||
if bracket_count == 0 and lines[j]:match("}") then
|
||||
end_line = j
|
||||
break
|
||||
end
|
||||
end
|
||||
return { start_line = start_line, end_line = end_line, line = line }
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Extract return type from existing constructor
|
||||
local function extract_return_type_from_existing(constructor_line)
|
||||
-- Match: func ConstructorName(params) ReturnType {
|
||||
local return_type = constructor_line:match("%)%s*([%w%.]+)%s*{")
|
||||
return return_type
|
||||
end
|
||||
|
||||
local function remove_existing_constructor(constructor_name, bufnr)
|
||||
local location = find_existing_constructor(constructor_name, bufnr)
|
||||
if location then
|
||||
-- Remove the constructor
|
||||
vim.api.nvim_buf_set_lines(bufnr, location.start_line - 1, location.end_line, false, {})
|
||||
return location
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Find interface that the struct should implement
|
||||
local function find_interface_for_struct(struct_name, bufnr)
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
|
||||
-- Look for interfaces defined in the file
|
||||
for i, line in ipairs(lines) do
|
||||
-- Match interface definition
|
||||
local interface_name = line:match("^type%s+([%w_]+)%s+interface")
|
||||
if interface_name then
|
||||
-- Check if this interface has methods that match our struct
|
||||
-- Simple check: if interface name contains struct name or vice versa
|
||||
if
|
||||
string.find(interface_name, struct_name)
|
||||
or string.find(struct_name, interface_name)
|
||||
or string.lower(struct_name) == string.lower(interface_name)
|
||||
then
|
||||
return interface_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If no matching interface found, check if struct has an interface with same name + "er" or "Service"
|
||||
local possible_interfaces = {
|
||||
string.gsub(struct_name, "Service$", "Service"),
|
||||
struct_name .. "er",
|
||||
"I" .. struct_name,
|
||||
string.gsub(struct_name, "^%l", string.upper),
|
||||
}
|
||||
|
||||
for _, interface_name in ipairs(possible_interfaces) do
|
||||
for i, line in ipairs(lines) do
|
||||
if line:match("^type%s+" .. interface_name .. "%s+interface") then
|
||||
return interface_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Parse struct fields
|
||||
local function parse_struct_fields(struct_name, bufnr)
|
||||
local fields = {}
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local in_struct = false
|
||||
local bracket_count = 0
|
||||
local start_line = 0
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
-- Find the struct definition
|
||||
if line:match("^type%s+" .. struct_name .. "%s+struct") then
|
||||
in_struct = true
|
||||
start_line = i
|
||||
-- Check if opening brace is on same line
|
||||
if line:match("{") then
|
||||
bracket_count = 1
|
||||
end
|
||||
if line:match("}") then
|
||||
break
|
||||
end
|
||||
elseif in_struct then
|
||||
-- Count brackets to know when we're done
|
||||
if not line:match("^%s*//") then -- Skip comment lines for bracket counting
|
||||
for char in line:gmatch(".") do
|
||||
if char == "{" then
|
||||
bracket_count = bracket_count + 1
|
||||
elseif char == "}" then
|
||||
bracket_count = bracket_count - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse field if we're inside the struct and it's not the closing brace
|
||||
if bracket_count > 0 and i > start_line then
|
||||
-- Remove comments and trim
|
||||
local clean_line = line:gsub("//.*$", ""):gsub("^%s*(.-)%s*$", "%1")
|
||||
print("Clean line: ", clean_line)
|
||||
|
||||
-- Skip empty lines and closing braces
|
||||
if clean_line ~= "" and not clean_line:match("^}") and not clean_line:match("^{") then
|
||||
-- Extract field name and type
|
||||
-- Handle multiple fields with same type: field1, field2 Type
|
||||
local field_part = clean_line
|
||||
|
||||
-- Check if line contains a type declaration
|
||||
if field_part:match("[%w_]+%s+[%w%.%*%[%]]+$") then
|
||||
-- Split by last space to get type and field names
|
||||
local last_space = field_part:match("^.*()%s[%w%.%*%[%]]+$")
|
||||
if last_space then
|
||||
local type_part = field_part:sub(last_space + 1)
|
||||
local fields_part = field_part:sub(1, last_space - 1)
|
||||
|
||||
-- Split multiple field names by comma
|
||||
for field_name in fields_part:gmatch("([%w_]+),?%s*") do
|
||||
if field_name ~= "" then
|
||||
table.insert(fields, {
|
||||
name = field_name,
|
||||
type = type_part,
|
||||
line = clean_line,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Stop parsing when we exit the struct
|
||||
if bracket_count == 0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return fields
|
||||
end
|
||||
|
||||
-- Generate constructor function
|
||||
local function generate_constructor(struct_name, interface_name, fields, existing_constructor)
|
||||
local constructor_name = "New" .. struct_name
|
||||
if string.match(struct_name, "^[a-z]") and interface_name then
|
||||
constructor_name = "New" .. interface_name
|
||||
else
|
||||
constructor_name = "New" .. string.upper(string.sub(struct_name, 1, 1)) .. string.sub(struct_name, 2)
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
|
||||
-- Function signature
|
||||
local return_type
|
||||
if existing_constructor and existing_constructor.line then
|
||||
return_type = extract_return_type_from_existing(existing_constructor.line)
|
||||
end
|
||||
|
||||
if not return_type then
|
||||
return_type = interface_name or struct_name
|
||||
end
|
||||
|
||||
-- Build parameters
|
||||
local params = {}
|
||||
local field_params = {}
|
||||
for _, field in ipairs(fields) do
|
||||
local param_name = field.name
|
||||
local param_type = field.type
|
||||
|
||||
table.insert(params, param_name .. " " .. param_type)
|
||||
field_params[param_name] = param_name
|
||||
end
|
||||
|
||||
-- Build function signature
|
||||
local func_sig = "func " .. constructor_name .. "("
|
||||
if #params > 0 then
|
||||
func_sig = func_sig .. table.concat(params, ", ")
|
||||
end
|
||||
func_sig = func_sig .. ") " .. return_type .. " {"
|
||||
table.insert(lines, func_sig)
|
||||
|
||||
-- Function body
|
||||
table.insert(lines, "\treturn &" .. struct_name .. "{")
|
||||
|
||||
-- Field assignments
|
||||
local field_assignments = {}
|
||||
for _, field in ipairs(fields) do
|
||||
local value = field_params[field.name] or field.name
|
||||
table.insert(field_assignments, "\t\t" .. field.name .. ": " .. value .. ",")
|
||||
end
|
||||
|
||||
for _, assignment in ipairs(field_assignments) do
|
||||
table.insert(lines, assignment)
|
||||
end
|
||||
|
||||
table.insert(lines, "\t}")
|
||||
table.insert(lines, "}")
|
||||
|
||||
return lines, constructor_name
|
||||
end
|
||||
|
||||
-- Main function to generate constructor
|
||||
function M.generate_constructor()
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
|
||||
-- Check if we're in a Go file
|
||||
local filetype = vim.bo[bufnr].filetype
|
||||
if filetype ~= "go" then
|
||||
vim.notify("This command only works in Go files", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Get struct name under cursor
|
||||
local struct_name = get_struct_name()
|
||||
if not struct_name then
|
||||
vim.notify("Cursor is not on a struct definition", vim.log.levels.ERROR)
|
||||
return
|
||||
end
|
||||
|
||||
-- Find interface if exists
|
||||
local interface_name = find_interface_for_struct(struct_name, bufnr)
|
||||
|
||||
-- Parse struct fields
|
||||
local fields = parse_struct_fields(struct_name, bufnr)
|
||||
|
||||
if #fields == 0 then
|
||||
vim.notify("No fields found in struct " .. struct_name, vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for existing constructor and remove it (but save its info)
|
||||
local constructor_name = "New" .. struct_name
|
||||
if string.match(struct_name, "^[a-z]") and interface_name then
|
||||
constructor_name = "New" .. interface_name
|
||||
else
|
||||
constructor_name = "New" .. string.upper(string.sub(struct_name, 1, 1)) .. string.sub(struct_name, 2)
|
||||
end
|
||||
|
||||
-- Remove existing constructor if it exists
|
||||
local removed = remove_existing_constructor(constructor_name, bufnr)
|
||||
|
||||
-- Generate constructor
|
||||
local constructor_lines = generate_constructor(struct_name, interface_name, fields, removed)
|
||||
|
||||
-- Find where to insert the constructor (after the struct)
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local insert_line = 0
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
if line:match("^type%s+" .. struct_name .. "%s+struct") then
|
||||
-- Find the end of the struct
|
||||
local bracket_count = 0
|
||||
for j = i, #lines do
|
||||
local current_line = lines[j]
|
||||
-- Skip comment lines for bracket counting
|
||||
if not current_line:match("^%s*//") then
|
||||
for char in current_line:gmatch(".") do
|
||||
if char == "{" then
|
||||
bracket_count = bracket_count + 1
|
||||
elseif char == "}" then
|
||||
bracket_count = bracket_count - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if bracket_count == 0 then
|
||||
insert_line = j
|
||||
break
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if insert_line > 0 then
|
||||
-- Check if we need to add an empty line
|
||||
if insert_line < #lines and lines[insert_line + 1] and lines[insert_line + 1]:match("^%s*$") then
|
||||
-- Next line is already empty, insert after it
|
||||
vim.api.nvim_buf_set_lines(bufnr, insert_line + 1, insert_line + 1, false, constructor_lines)
|
||||
else
|
||||
-- Add an empty line before constructor
|
||||
table.insert(constructor_lines, 1, "")
|
||||
vim.api.nvim_buf_set_lines(bufnr, insert_line, insert_line, false, constructor_lines)
|
||||
end
|
||||
|
||||
if removed then
|
||||
vim.notify("Replaced existing constructor: " .. constructor_name, vim.log.levels.INFO)
|
||||
else
|
||||
vim.notify("Generated constructor: " .. constructor_name, vim.log.levels.INFO)
|
||||
end
|
||||
else
|
||||
vim.notify("Could not find where to insert constructor", vim.log.levels.ERROR)
|
||||
end
|
||||
end
|
||||
|
||||
-- Setup function
|
||||
function M.setup()
|
||||
-- Create command
|
||||
vim.api.nvim_create_user_command("GoGenerateConstructor", M.generate_constructor, {
|
||||
desc = "Generate constructor for Go struct under cursor",
|
||||
})
|
||||
|
||||
-- Optional keybinding
|
||||
vim.keymap.set("n", "<leader>cec", M.generate_constructor, {
|
||||
desc = "Generate constructor for struct",
|
||||
silent = true,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user