[mirror] Make your go dev experience better github.com/olexsmir/gopher.nvim
neovim golang

refactor: struct tags (#94)

* refactor(struct_tags): finally my hands got to this

* feat(struct_tags): trim output

* feat(struct_tags): add :GoTagClear

* docgen

* refactor(struct_tags): error on out-of-bounds

* feat(struct_tags): add support for working with multiple tags at the once

* test(struct_tags): test both possible inputs

* refactor(struct_tags): add type annotation, dont force write

* refactor(struct_tags): optimization ig

* docs: fix

* fixup! refactor(struct_tags): add type annotation, dont force write

* task docgen

---------

Co-authored-by: Oliver <1571880470@qq.com>

authored by olexsmir.xyz Oliver and committed by GitHub 55bc5787 e9f2eef5

+19
doc/gopher.nvim.txt
··· 129 Name string `yaml:name` 130 } 131 < 132 133 ============================================================================== 134 ------------------------------------------------------------------------------
··· 129 Name string `yaml:name` 130 } 131 < 132 + ------------------------------------------------------------------------------ 133 + *struct_tags.add()* 134 + `struct_tags.add`({...}) 135 + tags to a struct under the cursor 136 + Parameters ~ 137 + {...} `(string)` Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag] 138 + 139 + ------------------------------------------------------------------------------ 140 + *struct_tags.remove()* 141 + `struct_tags.remove`({...}) 142 + tags from a struct under the cursor 143 + Parameters ~ 144 + {...} `(string)` Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag] 145 + 146 + ------------------------------------------------------------------------------ 147 + *struct_tags.clear()* 148 + `struct_tags.clear`() 149 + all tags from a struct under the cursor 150 + 151 152 ============================================================================== 153 ------------------------------------------------------------------------------
+1
lua/gopher/init.lua
··· 43 gopher.tags = { 44 add = tags.add, 45 rm = tags.remove, 46 } 47 48 gopher.test = {
··· 43 gopher.tags = { 44 add = tags.add, 45 rm = tags.remove, 46 + clear = tags.clear, 47 } 48 49 gopher.test = {
+63 -57
lua/gopher/struct_tags.lua
··· 26 --- } 27 --- < 28 29 - local ts_utils = require "gopher._utils.ts" 30 local r = require "gopher._utils.runner" 31 local c = require "gopher.config" 32 local struct_tags = {} 33 34 - local function modify(...) 35 - local fpath = vim.fn.expand "%" ---@diagnostic disable-line: missing-parameter 36 - local bufnr = vim.api.nvim_get_current_buf() 37 - local struct = ts_utils.get_struct_under_cursor(bufnr) 38 - 39 - -- set user args for cmd 40 - local cmd_args = {} 41 - local arg = { ... } 42 - for _, v in ipairs(arg) do 43 - table.insert(cmd_args, v) 44 - end 45 46 - local rs = r.sync { 47 c.commands.gomodifytags, 48 - "-transform", 49 - c.gotag.transform, 50 - "-format", 51 - "json", 52 - "-struct", 53 - struct.name, 54 "-w", 55 - "-file", 56 - fpath, 57 - unpack(cmd_args), 58 } 59 60 if rs.code ~= 0 then 61 error("failed to set tags " .. rs.stderr) 62 end 63 64 - -- decode value 65 - local tagged = vim.json.decode(rs.stdout) 66 - if 67 - tagged.errors ~= nil 68 - or tagged.lines == nil 69 - or tagged["start"] == nil 70 - or tagged["start"] == 0 71 - then 72 - error("failed to set tags " .. vim.inspect(tagged)) 73 end 74 75 vim.api.nvim_buf_set_lines( 76 - 0, 77 - tagged.start - 1, 78 - tagged.start - 1 + #tagged.lines, 79 - false, 80 - tagged.lines 81 ) 82 - vim.cmd "write" 83 end 84 85 - -- add tags to struct under cursor 86 - function struct_tags.add(...) 87 - local user_tags = { ... } 88 - if #user_tags == 0 then 89 - user_tags = { c.gotag.default_tag } 90 end 91 92 - local cmd_args = { "-add-tags" } 93 - for _, v in ipairs(user_tags) do 94 - table.insert(cmd_args, v) 95 - end 96 97 - modify(unpack(cmd_args)) 98 end 99 100 - -- remove tags to struct under cursor 101 function struct_tags.remove(...) 102 - local user_tags = { ... } 103 - if #user_tags == 0 then 104 - user_tags = { c.gotag.default_tag } 105 - end 106 107 - local cmd_args = { "-remove-tags" } 108 - for _, v in ipairs(user_tags) do 109 - table.insert(cmd_args, v) 110 - end 111 112 - modify(unpack(cmd_args)) 113 end 114 115 return struct_tags
··· 26 --- } 27 --- < 28 29 + local ts = require "gopher._utils.ts" 30 local r = require "gopher._utils.runner" 31 local c = require "gopher.config" 32 + local log = require "gopher._utils.log" 33 local struct_tags = {} 34 35 + ---@param fpath string 36 + ---@param bufnr integer 37 + ---@param user_args string[] 38 + ---@private 39 + local function handle_tags(fpath, bufnr, user_args) 40 + local st = ts.get_struct_under_cursor(bufnr) 41 42 + -- stylua: ignore 43 + local cmd = { 44 c.commands.gomodifytags, 45 + "-transform", c.gotag.transform, 46 + "-format", "json", 47 + "-struct", st.name, 48 + "-file", fpath, 49 "-w", 50 } 51 52 + for _, v in ipairs(user_args) do 53 + table.insert(cmd, v) 54 + end 55 + 56 + local rs = r.sync(cmd) 57 if rs.code ~= 0 then 58 + log.error("tags: failed to set tags " .. rs.stderr) 59 error("failed to set tags " .. rs.stderr) 60 end 61 62 + local res = vim.json.decode(rs.stdout) 63 + 64 + if res["errors"] then 65 + log.error("tags: got an error " .. vim.inspect(res)) 66 + error("failed to set tags " .. vim.inspect(res["errors"])) 67 + end 68 + 69 + for i, v in ipairs(res["lines"]) do 70 + res["lines"][i] = string.gsub(v, "%s+$", "") 71 end 72 73 vim.api.nvim_buf_set_lines( 74 + bufnr, 75 + res["start"] - 1, 76 + res["start"] - 1 + #res["lines"], 77 + true, 78 + res["lines"] 79 ) 80 end 81 82 + ---@param args string[] 83 + ---@return string 84 + ---@private 85 + local function handler_user_args(args) 86 + if #args == 0 then 87 + return c.gotag.default_tag 88 end 89 + return table.concat(args, ",") 90 + end 91 92 + ---Adds tags to a struct under the cursor 93 + ---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag] 94 + function struct_tags.add(...) 95 + local args = { ... } 96 + local fpath = vim.fn.expand "%" 97 + local bufnr = vim.api.nvim_get_current_buf() 98 99 + local user_tags = handler_user_args(args) 100 + handle_tags(fpath, bufnr, { "-add-tags", user_tags }) 101 end 102 103 + ---Removes tags from a struct under the cursor 104 + ---@param ... string Tags to add to the struct fields. If not provided, it will use [config.gotag.default_tag] 105 function struct_tags.remove(...) 106 + local args = { ... } 107 + local fpath = vim.fn.expand "%" 108 + local bufnr = vim.api.nvim_get_current_buf() 109 110 + local user_tags = handler_user_args(args) 111 + handle_tags(fpath, bufnr, { "-remove-tags", user_tags }) 112 + end 113 114 + ---Removes all tags from a struct under the cursor 115 + function struct_tags.clear() 116 + local fpath = vim.fn.expand "%" 117 + local bufnr = vim.api.nvim_get_current_buf() 118 + handle_tags(fpath, bufnr, { "-clear-tags" }) 119 end 120 121 return struct_tags
+1
plugin/gopher.vim
··· 1 command! -nargs=* GoTagAdd :lua require"gopher".tags.add(<f-args>) 2 command! -nargs=* GoTagRm :lua require"gopher".tags.rm(<f-args>) 3 command! GoTestAdd :lua require"gopher".test.add() 4 command! GoTestsAll :lua require"gopher".test.all() 5 command! GoTestsExp :lua require"gopher".test.exported()
··· 1 command! -nargs=* GoTagAdd :lua require"gopher".tags.add(<f-args>) 2 command! -nargs=* GoTagRm :lua require"gopher".tags.rm(<f-args>) 3 + command! GoTagClear :lua require"gopher".tags.clear() 4 command! GoTestAdd :lua require"gopher".test.add() 5 command! GoTestsAll :lua require"gopher".test.all() 6 command! GoTestsExp :lua require"gopher".test.exported()
+11
spec/fixtures/tags/add_many_input.go
···
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 + Another struct { 8 + First int 9 + Second string 10 + } 11 + }
+11
spec/fixtures/tags/add_many_output.go
···
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `test4:"id" test5:"id" test1:"id" test2:"id"` 5 + Name string `test4:"name" test5:"name" test1:"name" test2:"name"` 6 + Num int64 `test4:"num" test5:"num" test1:"num" test2:"num"` 7 + Another struct { 8 + First int `test4:"first" test5:"first" test1:"first" test2:"first"` 9 + Second string `test4:"second" test5:"second" test1:"second" test2:"second"` 10 + } `test4:"another" test5:"another" test1:"another" test2:"another"` 11 + }
+11
spec/fixtures/tags/clear_input.go
···
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int `json:"id" yaml:"id" xml:"id" db:"id"` 5 + Name string `json:"name" yaml:"name" xml:"name" db:"name"` 6 + Num int64 `json:"num" yaml:"num" xml:"num" db:"num"` 7 + Another struct { 8 + First int `json:"first" yaml:"first" xml:"first" db:"first"` 9 + Second string `json:"second" yaml:"second" xml:"second" db:"second"` 10 + } `json:"another" yaml:"another" xml:"another" db:"another"` 11 + }
+11
spec/fixtures/tags/clear_output.go
···
··· 1 + package main 2 + 3 + type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 + Another struct { 8 + First int 9 + Second string 10 + } 11 + }
+6 -6
spec/fixtures/tags/remove_output.go
··· 1 package main 2 3 type Test struct { 4 - ID int 5 - Name string 6 - Num int64 7 Another struct { 8 - First int 9 - Second string 10 - } 11 }
··· 1 package main 2 3 type Test struct { 4 + ID int 5 + Name string 6 + Num int64 7 Another struct { 8 + First int 9 + Second string 10 + } 11 }
+42 -6
spec/integration/struct_tags_test.lua
··· 10 }, 11 } 12 T["struct_tags"] = MiniTest.new_set {} 13 - T["struct_tags"]["works add"] = function() 14 local tmp = t.tmpfile() 15 local fixtures = t.get_fixtures "tags/add" 16 t.writefile(tmp, fixtures.input) 17 18 child.cmd("silent edit " .. tmp) 19 - child.fn.setpos(".", { child.fn.bufnr "%", 3, 6, 0 }) 20 child.cmd "GoTagAdd json" 21 22 t.eq(t.readfile(tmp), fixtures.output) 23 end 24 25 - T["struct_tags"]["works remove"] = function() 26 local tmp = t.tmpfile() 27 local fixtures = t.get_fixtures "tags/remove" 28 t.writefile(tmp, fixtures.input) 29 30 child.cmd("silent edit " .. tmp) 31 - child.fn.setpos(".", { child.fn.bufnr "%", 4, 6, 0 }) 32 child.cmd "GoTagRm json" 33 34 t.eq(t.readfile(tmp), fixtures.output) 35 end 36 37 - T["struct_tags"]["works many structs"] = function() 38 local tmp = t.tmpfile() 39 local fixtures = t.get_fixtures "tags/many" 40 t.writefile(tmp, fixtures.input) 41 42 child.cmd("silent edit " .. tmp) 43 - child.fn.setpos(".", { child.fn.bufnr "%", 10, 3, 0 }) 44 child.cmd "GoTagAdd testing" 45 46 t.eq(t.readfile(tmp), fixtures.output) 47 end
··· 10 }, 11 } 12 T["struct_tags"] = MiniTest.new_set {} 13 + T["struct_tags"]["should add tag"] = function() 14 local tmp = t.tmpfile() 15 local fixtures = t.get_fixtures "tags/add" 16 t.writefile(tmp, fixtures.input) 17 18 child.cmd("silent edit " .. tmp) 19 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 6, 0 }) 20 child.cmd "GoTagAdd json" 21 + child.cmd "write" 22 23 t.eq(t.readfile(tmp), fixtures.output) 24 end 25 26 + T["struct_tags"]["should remove tag"] = function() 27 local tmp = t.tmpfile() 28 local fixtures = t.get_fixtures "tags/remove" 29 t.writefile(tmp, fixtures.input) 30 31 child.cmd("silent edit " .. tmp) 32 + child.fn.setpos(".", { child.fn.bufnr(tmp), 4, 6, 0 }) 33 child.cmd "GoTagRm json" 34 + child.cmd "write" 35 36 t.eq(t.readfile(tmp), fixtures.output) 37 end 38 39 + T["struct_tags"]["should be able to handle many structs"] = function() 40 local tmp = t.tmpfile() 41 local fixtures = t.get_fixtures "tags/many" 42 t.writefile(tmp, fixtures.input) 43 44 child.cmd("silent edit " .. tmp) 45 + child.fn.setpos(".", { child.fn.bufnr(tmp), 10, 3, 0 }) 46 child.cmd "GoTagAdd testing" 47 + child.cmd "write" 48 + 49 + t.eq(t.readfile(tmp), fixtures.output) 50 + end 51 + 52 + T["struct_tags"]["should clear struct"] = function() 53 + local tmp = t.tmpfile() 54 + local fixtures = t.get_fixtures "tags/clear" 55 + t.writefile(tmp, fixtures.input) 56 + 57 + child.cmd("silent edit " .. tmp) 58 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1, 0 }) 59 + child.cmd "GoTagClear" 60 + child.cmd "write" 61 + 62 + t.eq(t.readfile(tmp), fixtures.output) 63 + end 64 + 65 + T["struct_tags"]["should add more than one tag"] = function() 66 + local tmp = t.tmpfile() 67 + local fixtures = t.get_fixtures "tags/add_many" 68 + t.writefile(tmp, fixtures.input) 69 + 70 + --- with comma, like gomodifytags 71 + child.cmd("silent edit " .. tmp) 72 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1 }) 73 + child.cmd "GoTagAdd test4,test5" 74 + child.cmd "write" 75 + 76 + -- without comma 77 + child.cmd("silent edit " .. tmp) 78 + child.fn.setpos(".", { child.fn.bufnr(tmp), 3, 1 }) 79 + child.cmd "GoTagAdd test1 test2" 80 + child.cmd "write" 81 82 t.eq(t.readfile(tmp), fixtures.output) 83 end