Add "vibe-coded" go-helper utils

This commit is contained in:
2025-12-23 16:56:46 +05:00
parent a582bd6c05
commit 86796d6e12
4 changed files with 516 additions and 10 deletions

View File

@ -1,42 +1,43 @@
{ {
"LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" }, "LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" },
"SchemaStore.nvim": { "branch": "main", "commit": "10e90771990622daec50d14ef3e278f429b9419f" }, "SchemaStore.nvim": { "branch": "main", "commit": "8b92ea89835b8e5dbc779a675ebb0e5fcb9a1993" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"catppuccin": { "branch": "main", "commit": "ce8d176faa4643e026e597ae3c31db59b63cef09" }, "catppuccin": { "branch": "main", "commit": "ce8d176faa4643e026e597ae3c31db59b63cef09" },
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" }, "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" }, "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
"cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" }, "cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
"conform.nvim": { "branch": "master", "commit": "9b8fa5e0b78168f68bee9bf886dc20f287c61e02" }, "conform.nvim": { "branch": "master", "commit": "5420c4b5ea0aeb99c09cfbd4fd0b70d257b44f25" },
"flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" }, "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
"friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" }, "friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" },
"gitsigns.nvim": { "branch": "main", "commit": "5813e4878748805f1518cee7abb50fd7205a3a48" }, "gitsigns.nvim": { "branch": "main", "commit": "5813e4878748805f1518cee7abb50fd7205a3a48" },
"grug-far.nvim": { "branch": "main", "commit": "b58b2d65863f4ebad88b10a1ddd519e5380466e0" }, "go-tools.nvim": { "branch": "main", "commit": "f47209b9e58df1e13b42b1e2fd2817fce32ea0df" },
"grug-far.nvim": { "branch": "main", "commit": "bc589a1ba340a00ae40bf1436401eac5b1454687" },
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" }, "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" },
"lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "3b3571b4dadbcb464804466e9872e7246c316af7" }, "mason-lspconfig.nvim": { "branch": "main", "commit": "9f9c67795d0795a6e8612f5a899ca64a074a1076" },
"mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" }, "mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" },
"mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" }, "mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" },
"mini.ai": { "branch": "main", "commit": "bfb26d9072670c3aaefab0f53024b2f3729c8083" }, "mini.ai": { "branch": "main", "commit": "bfb26d9072670c3aaefab0f53024b2f3729c8083" },
"mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" }, "mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" },
"mini.pairs": { "branch": "main", "commit": "d5a29b6254dad07757832db505ea5aeab9aad43a" }, "mini.pairs": { "branch": "main", "commit": "d5a29b6254dad07757832db505ea5aeab9aad43a" },
"neo-tree.nvim": { "branch": "main", "commit": "2ab61f0a4f97e909f80b430f43a1ddae142c548d" }, "neo-tree.nvim": { "branch": "main", "commit": "b0b73273b4f5a1f4b4aac8accd6e0c3b4b5a6967" },
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" }, "noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
"nvim-cmp": { "branch": "main", "commit": "d97d85e01339f01b842e6ec1502f639b080cb0fc" }, "nvim-cmp": { "branch": "main", "commit": "d97d85e01339f01b842e6ec1502f639b080cb0fc" },
"nvim-dap": { "branch": "master", "commit": "5860c7c501eb428d3137ee22c522828d20cca0b3" }, "nvim-dap": { "branch": "master", "commit": "818cd8787a77a97703eb1d9090543a374f79a9ac" },
"nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" }, "nvim-dap-go": { "branch": "main", "commit": "b4421153ead5d726603b02743ea40cf26a51ed5f" },
"nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" }, "nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" },
"nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
"nvim-jdtls": { "branch": "master", "commit": "f73731b543f5971e0da9665eb1d7ceffe1fde71f" }, "nvim-jdtls": { "branch": "master", "commit": "f73731b543f5971e0da9665eb1d7ceffe1fde71f" },
"nvim-lint": { "branch": "master", "commit": "c47b71d146a0b638f46672e6704c322369385df6" }, "nvim-lint": { "branch": "master", "commit": "1f19dacd945a7b1a57f29f32b2d7168384df3d36" },
"nvim-lspconfig": { "branch": "master", "commit": "c4f67bf85b01a57e3c130352c0a0e453ab8cd5b9" }, "nvim-lspconfig": { "branch": "master", "commit": "5eeb45c8c469b84777a5bd8796b698c8a1c780a7" },
"nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" },
"nvim-snippets": { "branch": "main", "commit": "56b4052f71220144689caaa2e5b66222ba5661eb" }, "nvim-snippets": { "branch": "main", "commit": "56b4052f71220144689caaa2e5b66222ba5661eb" },
"nvim-treesitter": { "branch": "main", "commit": "4fc09bee78e91bf4ba471cdab4bf9dfa37fde51c" }, "nvim-treesitter": { "branch": "main", "commit": "6e42d823ce0a5a76180c473c119c7677738a09d1" },
"nvim-treesitter-context": { "branch": "master", "commit": "64dd4cf3f6fd0ab17622c5ce15c91fc539c3f24a" }, "nvim-treesitter-context": { "branch": "master", "commit": "64dd4cf3f6fd0ab17622c5ce15c91fc539c3f24a" },
"nvim-treesitter-textobjects": { "branch": "main", "commit": "0d7c800fadcfe2d33089f5726cb8907fc846eece" }, "nvim-treesitter-textobjects": { "branch": "main", "commit": "e91c585ac0ee760198dabc1fad2e6227effdcd5e" },
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" }, "nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" },
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" }, "persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },

15
lua/plugins/go-helper.lua Normal file
View File

@ -0,0 +1,15 @@
return {
{
"go-helper",
dir = vim.fn.stdpath("config") .. "/lua/scripts",
ft = "go",
keys = {
{ "<leader>cei", "<cmd>GoExtractInterface<cr>", desc = "Extract Interface", ft = "go" },
{ "<leader>cec", "<cmd>GoGenerateConstructor<cr>", desc = "Generate Constructor", ft = "go" },
},
config = function()
require("scripts.go-extract-interface").setup()
require("scripts.go-gen-constructor").setup()
end,
},
}

View File

@ -0,0 +1,137 @@
local M = {}
-- Parse method signature from a line
local function parse_method(line)
-- Match: func (receiver) MethodName(params) (returns)
local pattern = "^func%s+%([^)]+%)%s+([%w_]+)%s*(%b())%s*(.*)"
local name, params, returns = line:match(pattern)
if name then
-- Clean up returns (remove leading spaces and parentheses if single return)
returns = returns:gsub("^%s+", "")
if returns:match("^%b()") then
returns = " " .. returns
elseif returns ~= "" then
returns = " " .. returns
end
return name .. params .. returns
end
return nil
end
-- Find all methods for a given struct type
local function find_struct_methods(struct_name, bufnr)
local methods = {}
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-- Pattern to match methods with the struct as receiver
-- Matches both pointer and value receivers
local patterns = {
"^func%s+%(%s*%w+%s+%*?" .. struct_name .. "%s*%)", -- Standard pattern
}
for i, line in ipairs(lines) do
for _, pattern in ipairs(patterns) do
if line:match(pattern) then
local method_sig = parse_method(line)
if method_sig then
table.insert(methods, method_sig)
end
end
end
end
return methods
end
-- Get struct name under cursor
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 struct_name then
-- Try to match if cursor is on struct keyword
struct_name = line:match("type%s+([%w_]+)%s+struct")
end
return struct_name
end
-- Generate interface from methods
local function generate_interface(struct_name, methods)
if #methods == 0 then
return nil, "No methods found for struct " .. struct_name
end
-- Create interface name (conventionally, I + StructName or StructName + er)
local interface_name = "I" .. struct_name
local lines = {
"type " .. interface_name .. " interface {",
}
for _, method in ipairs(methods) do
local method_without_bracket = method:gsub("{$", "")
local cleaned_method = method_without_bracket:gsub("%s+$", "")
table.insert(lines, "\t" .. cleaned_method)
end
table.insert(lines, "}")
return lines, nil
end
-- Main function to extract interface
function M.extract_interface()
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 all methods for this struct
local methods = find_struct_methods(struct_name, bufnr)
-- Generate interface
local interface_lines, err = generate_interface(struct_name, methods)
if err then
vim.notify(err, vim.log.levels.WARN)
return
end
-- Insert interface above the struct definition
local cursor_line = vim.api.nvim_win_get_cursor(0)[1]
vim.api.nvim_buf_set_lines(bufnr, cursor_line - 1, cursor_line - 1, false, interface_lines)
vim.api.nvim_buf_set_lines(bufnr, cursor_line, cursor_line, false, { "" }) -- Empty line
vim.notify("Interface extracted: " .. #methods .. " methods found", vim.log.levels.INFO)
end
-- Setup function for lazy.nvim
function M.setup()
-- Create command
vim.api.nvim_create_user_command("GoExtractInterface", M.extract_interface, {
desc = "Extract interface from Go struct under cursor",
})
-- Optional: Add keybinding
vim.keymap.set("n", "<leader>cei", M.extract_interface, {
desc = "Extract interface from struct",
silent = true,
})
end
return M

View 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