all repos

gopher.nvim @ 7a18d9f

Minimalistic plugin for Go development
19 files changed, 300 insertions(+), 14 deletions(-)
feat(strct-tags): add support for tag options (#126)

* feat(struct_tags): add options support

* refactor(struct-tags): give input field better name

* feat(struct-tag): add default option

* refactor: make it work on neovim version below 0.12

* chore(struct-tags): update the demo

* refactor: unite struct_tags util with main logic
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2025-11-06 14:58:21 +0200
Change ID: ssxzxuuvnxqozqklwnynymvtxmnylsop
Parent: 0de1892
M README.md

@@ -72,6 +72,9 @@ ```vim

" add json tag :GoTagAdd json + " add json tag with omitempty option + :GoTagAdd json=omitempty + " remove yaml tag :GoTagRm yaml ```

@@ -233,6 +236,8 @@ gotag = {

transform = "snakecase", -- default tags to add to struct fields default_tag = "json", + -- default tag option added struct fields, set to nil to disable + option = nil, }, iferr = { -- choose a custom error message
M doc/gopher.nvim.txt

@@ -89,6 +89,10 @@ transform = "snakecase",

-- default tags to add to struct fields default_tag = "json", + + -- default tag option added struct fields, set to nil to disable + ---@type string|nil + option = nil, }, iferr = { -- choose a custom error message

@@ -123,6 +127,9 @@ How to add/remove tags to struct fields:

1. Place cursor on the struct 2. Run `:GoTagAdd json` to add json tags to struct fields 3. Run `:GoTagRm json` to remove json tags to struct fields + +If you want to add/remove tag with options, you can use `json=omitempty` (where json is tag, and omitempty is its option). +Example: `:GoTagAdd xml json=omitempty` To clear all tags from struct run: `:GoTagClear`
M lua/gopher/_utils/init.lua

@@ -50,4 +50,23 @@ end

return string.rep(char, indent) end +---@generic T +---@param tbl T[] +---@return T[] +function utils.list_unique(tbl) + if vim.fn.has "nvim-0.12" == 1 then + return vim.list.unique(tbl) + end + + for i = #tbl, 1, -1 do + for j = 1, i - 1 do + if tbl[i] == tbl[j] then + table.remove(tbl, i) + break + end + end + end + return tbl +end + return utils
M lua/gopher/config.lua

@@ -61,6 +61,10 @@ transform = "snakecase",

-- default tags to add to struct fields default_tag = "json", + + -- default tag option added struct fields, set to nil to disable + ---@type string|nil + option = nil, }, iferr = { -- choose a custom error message

@@ -105,6 +109,7 @@ vim.validate("gotests.named", _config.gotests.named, "boolean")

vim.validate("gotag", _config.gotag, "table") vim.validate("gotag.transform", _config.gotag.transform, "string") vim.validate("gotag.default_tag", _config.gotag.default_tag, "string") + vim.validate("gotag.option", _config.gotag.option, { "string", "nil" }) vim.validate("iferr", _config.iferr, "table") vim.validate("iferr.message", _config.iferr.message, { "string", "nil" }) end
M lua/gopher/struct_tags.lua

@@ -9,6 +9,9 @@ --- 1. Place cursor on the struct

--- 2. Run `:GoTagAdd json` to add json tags to struct fields --- 3. Run `:GoTagRm json` to remove json tags to struct fields --- +--- If you want to add/remove tag with options, you can use `json=omitempty` (where json is tag, and omitempty is its option). +--- Example: `:GoTagAdd xml json=omitempty` +--- --- To clear all tags from struct run: `:GoTagClear` --- --- NOTE: if you dont specify the tag it will use `json` as default

@@ -39,7 +42,7 @@ local struct_tags = {}

---@dochide ---@class gopher.StructTagInput ----@field tags string[] User provided tags +---@field input string[] User provided tags ---@field range? gopher.StructTagRange (optional) ---@dochide

@@ -102,18 +105,53 @@ res["lines"]

) end +---@dochide +---@param option string +local function option_to_tag(option) + return option:match "^(.-)=" +end + +---@dochide ---@param args string[] ----@return string +local function unwrap_if_needed(args) + local out = {} + for _, v in pairs(args) do + for _, p in pairs(vim.split(v, ",")) do + table.insert(out, p) + end + end + return out +end + +---@dochide +---@class gopher.StructTagsArgs +---@field tags string +---@field options string + ---@dochide -local function handler_user_tags(args) - if #args == 0 then - return c.gotag.default_tag +---@param args string[] +---@return gopher.StructTagsArgs +function struct_tags.parse_args(args) + args = unwrap_if_needed(args) + + local tags, options = {}, {} + for _, v in pairs(args) do + if string.find(v, "=") then + table.insert(options, v) + table.insert(tags, option_to_tag(v)) + else + table.insert(tags, v) + end end - return table.concat(args, ",") + + return { + tags = table.concat(u.list_unique(tags), ","), + options = table.concat(u.list_unique(options), ","), + } end -- Adds tags to a struct under the cursor --- See |gopher.nvim-struct-tags| +-- See `:h gopher.nvim-struct-tags` ---@param opts gopher.StructTagInput ---@dochide function struct_tags.add(opts)

@@ -122,8 +160,13 @@

local fpath = vim.fn.expand "%" local bufnr = vim.api.nvim_get_current_buf() - local user_tags = handler_user_tags(opts.tags) - handle_tags(fpath, bufnr, opts.range, { "-add-tags", user_tags }) + local user_args = struct_tags.parse_args(opts.input) + handle_tags(fpath, bufnr, opts.range, { + "-add-tags", + (user_args.tags ~= "") and user_args.tags or c.gotag.default_tag, + (user_args.options ~= "" or c.gotag.option) and "-add-options" or nil, + (user_args.options ~= "") and user_args.options or c.gotag.option, + }) end -- Removes tags from a struct under the cursor

@@ -136,8 +179,13 @@

local fpath = vim.fn.expand "%" local bufnr = vim.api.nvim_get_current_buf() - local user_tags = handler_user_tags(opts.tags) - handle_tags(fpath, bufnr, opts.range, { "-remove-tags", user_tags }) + local user_args = struct_tags.parse_args(opts.input) + handle_tags(fpath, bufnr, opts.range, { + "-remove-tags", + (user_args.tags ~= "") and user_args.tags or c.gotag.default_tag, + (user_args.options ~= "" or c.gotag.option ~= nil) and "-remove-options" or nil, + (user_args.options ~= "") and user_args.options or c.gotag.option, + }) end -- Removes all tags from a struct under the cursor
M plugin/gopher.lua

@@ -48,7 +48,7 @@

-- :GoTag cmd("GoTagAdd", function(opts) require("gopher").tags.add { - tags = opts.fargs, + input = opts.fargs, range = (opts.count ~= -1) and { start = opts.line1, end_ = opts.line2,

@@ -58,7 +58,7 @@ end, "*", true)

cmd("GoTagRm", function(opts) require("gopher").tags.rm { - tags = opts.fargs, + input = opts.fargs, range = (opts.count ~= -1) and { start = opts.line1, end_ = opts.line2,
A spec/fixtures/tags/overwrite_default_option_input.go

@@ -0,0 +1,8 @@

+package main + +type Test struct { + ID int + Another struct { + Second string + } +}
A spec/fixtures/tags/overwrite_default_option_output.go

@@ -0,0 +1,8 @@

+package main + +type Test struct { + ID int `xml:"id,otheroption"` + Another struct { + Second string `xml:"second,otheroption"` + } `xml:"another,otheroption"` +}
A spec/fixtures/tags/remove_with_option_input.go

@@ -0,0 +1,11 @@

+package main + +type Test struct { + ID int `json:"id,omitempty" xml:"id,someoption"` + Name string `json:"name,omitempty" xml:"name,someoption"` + Num int64 `json:"num,omitempty" xml:"num,someoption"` + Another struct { + First int `json:"first,omitempty" xml:"first,someoption"` + Second string `json:"second,omitempty" xml:"second,someoption"` + } `json:"another,omitempty" xml:"another,someoption"` +}
A spec/fixtures/tags/remove_with_option_output.go

@@ -0,0 +1,11 @@

+package main + +type Test struct { + ID int `xml:"id,someoption"` + Name string `xml:"name,someoption"` + Num int64 `xml:"num,someoption"` + Another struct { + First int `xml:"first,someoption"` + Second string `xml:"second,someoption"` + } `xml:"another,someoption"` +}
A spec/fixtures/tags/with_default_option_input.go

@@ -0,0 +1,8 @@

+package main + +type Test struct { + ID int + Another struct { + Second string + } +}
A spec/fixtures/tags/with_default_option_output.go

@@ -0,0 +1,8 @@

+package main + +type Test struct { + ID int `xml:"id,theoption"` + Another struct { + Second string `xml:"second,theoption"` + } `xml:"another,theoption"` +}
A spec/fixtures/tags/with_option_input.go

@@ -0,0 +1,11 @@

+package main + +type Test struct { + ID int + Name string + Num int64 + Another struct { + First int + Second string + } +}
A spec/fixtures/tags/with_option_output.go

@@ -0,0 +1,11 @@

+package main + +type Test struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Num int64 `json:"num,omitempty"` + Another struct { + First int `json:"first,omitempty"` + Second string `json:"second,omitempty"` + } `json:"another,omitempty"` +}
M spec/integration/struct_tags_test.lua

@@ -96,4 +96,52 @@ t.eq(t.readfile(rs.tmp), rs.fixtures.output)

t.cleanup(rs) end +struct_tags["should add tags with option"] = function() + local rs = t.setup_test("tags/with_option", child, { 3, 6 }) + child.cmd "GoTagAdd json=omitempty" + child.cmd "write" + + t.eq(t.readfile(rs.tmp), rs.fixtures.output) + t.cleanup(rs) +end + +struct_tags["should add tags with default option"] = function() + child.lua [[ + require("gopher").setup { + gotag = { option = "xml=theoption" }, + } + ]] + + local rs = t.setup_test("tags/with_default_option", child, { 3, 6 }) + child.cmd "GoTagAdd xml" + child.cmd "write" + + t.eq(t.readfile(rs.tmp), rs.fixtures.output) + t.cleanup(rs) +end + +struct_tags["should add tags and overwrite default option"] = function() + child.lua [[ + require("gopher").setup { + gotag = { option = "xml=theoption" }, + } + ]] + + local rs = t.setup_test("tags/overwrite_default_option", child, { 3, 6 }) + child.cmd "GoTagAdd xml=otheroption" + child.cmd "write" + + t.eq(t.readfile(rs.tmp), rs.fixtures.output) + t.cleanup(rs) +end + +struct_tags["should remove tag with specified option"] = function() + local rs = t.setup_test("tags/remove_with_option", child, { 3, 6 }) + child.cmd "GoTagRm json=omitempty" + child.cmd "write" + + t.eq(t.readfile(rs.tmp), rs.fixtures.output) + t.cleanup(rs) +end + return T
A spec/unit/struct_tag_test.lua

@@ -0,0 +1,68 @@

+local t = require "spec.testutils" +local _, T, st = t.setup "struct_tags" + +st["should parse tags"] = function() + local out = require("gopher.struct_tags").parse_args { "json", "yaml", "etc" } + + t.eq(out.tags, "json,yaml,etc") + t.eq(out.options, "") +end + +st["should parse tags separated by commas"] = function() + local out = require("gopher.struct_tags").parse_args { "json,yaml,etc" } + + t.eq(out.tags, "json,yaml,etc") + t.eq(out.options, "") +end + +st["should parse tags separated by command and spaces"] = function() + local out = require("gopher.struct_tags").parse_args { + "json,yaml", + "json=omitempty", + "xml=something", + } + + t.eq(out.tags, "json,yaml,xml") + t.eq(out.options, "json=omitempty,xml=something") +end + +st["should parse tag with an option"] = function() + local out = require("gopher.struct_tags").parse_args { + "json=omitempty", + "xml", + "xml=theoption", + } + + t.eq(out.tags, "json,xml") + t.eq(out.options, "json=omitempty,xml=theoption") +end + +st["should parse tags with an option"] = function() + local out = require("gopher.struct_tags").parse_args { "json=omitempty", "yaml" } + + t.eq(out.tags, "json,yaml") + t.eq(out.options, "json=omitempty") +end + +st["should parse tags with an option separated with comma"] = function() + local out = require("gopher.struct_tags").parse_args { "json=omitempty,yaml" } + + t.eq(out.tags, "json,yaml") + t.eq(out.options, "json=omitempty") +end + +st["should parse tags with options specified separately"] = function() + local out = require("gopher.struct_tags").parse_args { "json", "yaml", "json=omitempty" } + + t.eq(out.tags, "json,yaml") + t.eq(out.options, "json=omitempty") +end + +st["should parse tags with options specified separately and separated by comma"] = function() + local out = require("gopher.struct_tags").parse_args { "json,yaml,json=omitempty" } + + t.eq(out.tags, "json,yaml") + t.eq(out.options, "json=omitempty") +end + +return T
M spec/unit/utils_test.lua

@@ -46,4 +46,14 @@

t.eq("\t\t", u.indent(line, indent)) end +utils["should .list_unique on list with duplicates"] = function() + local u = require "gopher._utils" + t.eq({ "json", "xml" }, u.list_unique { "json", "xml", "json" }) +end + +utils["should .list_unique on list with no duplicates"] = function() + local u = require "gopher._utils" + t.eq({ "json", "xml" }, u.list_unique { "json", "xml" }) +end + return T
M vhs/tags.gif

Not showing binary file.

M vhs/tags.tape

@@ -26,6 +26,6 @@ Type ":w" Enter

Sleep 1s Type@400ms "jVjj" -Type ":GoTagAdd xml" Sleep 500ms Enter +Type ":GoTagAdd json=omitempty" Sleep 500ms Enter Sleep 5s