diff --git a/README.md b/README.md index f5e53da..9c5b960 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ Requirements: " add json tag :GoTagAdd json + " add json tag with omitempty option + :GoTagAdd json=omitempty + " remove yaml tag :GoTagRm yaml ``` @@ -233,6 +236,8 @@ require("gopher").setup { 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 diff --git a/doc/gopher.nvim.txt b/doc/gopher.nvim.txt index bde5990..0e1e3c7 100644 --- a/doc/gopher.nvim.txt +++ b/doc/gopher.nvim.txt @@ -89,6 +89,10 @@ to install them synchronously pass `{sync = true}` as an argument. -- 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 @@ -124,6 +128,9 @@ How to add/remove tags to struct fields: 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 diff --git a/lua/gopher/_utils/init.lua b/lua/gopher/_utils/init.lua index 345c670..58a17b2 100644 --- a/lua/gopher/_utils/init.lua +++ b/lua/gopher/_utils/init.lua @@ -50,4 +50,23 @@ function utils.indent(line, indent) 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 diff --git a/lua/gopher/config.lua b/lua/gopher/config.lua index ebf18c9..6cae680 100644 --- a/lua/gopher/config.lua +++ b/lua/gopher/config.lua @@ -61,6 +61,10 @@ local default_config = { -- 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 @@ function config.setup(user_config) 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 diff --git a/lua/gopher/struct_tags.lua b/lua/gopher/struct_tags.lua index 3264e7a..9f400ab 100644 --- a/lua/gopher/struct_tags.lua +++ b/lua/gopher/struct_tags.lua @@ -9,6 +9,9 @@ --- 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 @@ local function handle_tags(fpath, bufnr, range, user_args) ) end ----@param args string[] ----@return string ---@dochide -local function handler_user_tags(args) - if #args == 0 then - return c.gotag.default_tag +---@param option string +local function option_to_tag(option) + return option:match "^(.-)=" +end + +---@dochide +---@param args 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 table.concat(args, ",") + return out +end + +---@dochide +---@class gopher.StructTagsArgs +---@field tags string +---@field options string + +---@dochide +---@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 { + 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 @@ function struct_tags.add(opts) 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 @@ function struct_tags.remove(opts) 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 diff --git a/plugin/gopher.lua b/plugin/gopher.lua index 4ffda5b..d3019ef 100644 --- a/plugin/gopher.lua +++ b/plugin/gopher.lua @@ -48,7 +48,7 @@ end) -- :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, diff --git a/spec/fixtures/tags/overwrite_default_option_input.go b/spec/fixtures/tags/overwrite_default_option_input.go new file mode 100644 index 0000000..89289be --- /dev/null +++ b/spec/fixtures/tags/overwrite_default_option_input.go @@ -0,0 +1,8 @@ +package main + +type Test struct { + ID int + Another struct { + Second string + } +} diff --git a/spec/fixtures/tags/overwrite_default_option_output.go b/spec/fixtures/tags/overwrite_default_option_output.go new file mode 100644 index 0000000..86ab89f --- /dev/null +++ b/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"` +} diff --git a/spec/fixtures/tags/remove_with_option_input.go b/spec/fixtures/tags/remove_with_option_input.go new file mode 100644 index 0000000..e06d6a5 --- /dev/null +++ b/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"` +} diff --git a/spec/fixtures/tags/remove_with_option_output.go b/spec/fixtures/tags/remove_with_option_output.go new file mode 100644 index 0000000..93ed8ae --- /dev/null +++ b/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"` +} diff --git a/spec/fixtures/tags/with_default_option_input.go b/spec/fixtures/tags/with_default_option_input.go new file mode 100644 index 0000000..89289be --- /dev/null +++ b/spec/fixtures/tags/with_default_option_input.go @@ -0,0 +1,8 @@ +package main + +type Test struct { + ID int + Another struct { + Second string + } +} diff --git a/spec/fixtures/tags/with_default_option_output.go b/spec/fixtures/tags/with_default_option_output.go new file mode 100644 index 0000000..6db779c --- /dev/null +++ b/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"` +} diff --git a/spec/fixtures/tags/with_option_input.go b/spec/fixtures/tags/with_option_input.go new file mode 100644 index 0000000..7e27a27 --- /dev/null +++ b/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 + } +} diff --git a/spec/fixtures/tags/with_option_output.go b/spec/fixtures/tags/with_option_output.go new file mode 100644 index 0000000..3fbbabf --- /dev/null +++ b/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"` +} diff --git a/spec/integration/struct_tags_test.lua b/spec/integration/struct_tags_test.lua index ae7995b..9b02c76 100644 --- a/spec/integration/struct_tags_test.lua +++ b/spec/integration/struct_tags_test.lua @@ -96,4 +96,52 @@ struct_tags["should remove tag with range"] = function() 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 diff --git a/spec/unit/struct_tag_test.lua b/spec/unit/struct_tag_test.lua new file mode 100644 index 0000000..6ebc688 --- /dev/null +++ b/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 diff --git a/spec/unit/utils_test.lua b/spec/unit/utils_test.lua index 2d8f0bc..0b71c93 100644 --- a/spec/unit/utils_test.lua +++ b/spec/unit/utils_test.lua @@ -46,4 +46,14 @@ utils["should add .indent() 2 tabs"] = function() 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 diff --git a/vhs/tags.gif b/vhs/tags.gif index eb51043..26d8d86 100644 Binary files a/vhs/tags.gif and b/vhs/tags.gif differ diff --git a/vhs/tags.tape b/vhs/tags.tape index 28bad2e..d95f76f 100644 --- a/vhs/tags.tape +++ b/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