From 1d9f51fb881a9e349dedbf5996ea493c8f0e244d Mon Sep 17 00:00:00 2001 From: Fatih Arslan Date: Sun, 22 Jan 2017 19:55:38 +0300 Subject: [PATCH] New GoTags improvements * Use new `gomodifytags` tool to manipulate and modify tags * Adds new `:GoRenameTags` * `:GoAddTags` is able to add options * `:GoRemoveTags` is able to remove options Usage: Following command add multiple tags. Does not modify if the key already exist. If `,optionaname` is given, it adds the given option to that key. If called without arguments, it adds by default tags with `json` key. ``` :GoAddTags :GoAddTags json :GoAddTags json,omitempty :GoAddTags json hcl bson :GoAddTags json,omitempty hcl bson ``` Following command removes multiple tags and options. If `,optionname` is given, it removes the option belonging to that key instead of removing the key. If called without arguments, it removes all tags belonging to a struct. ``` :GoRemoveTags :GoRemoveTags json :GoRemoveTags json,omitempty :GoRemoveTags json hcl bson :GoRemoveTags json,omitempty hcl bson ``` Fixes following issues: #984 #985 #990 #1064 #1091 --- autoload/go/tags.vim | 206 ++++++++++++++++++ autoload/go/tags_test.vim | 30 +++ .../go/test-fixtures/tags/add_all_golden.go | 16 ++ .../go/test-fixtures/tags/add_all_input.go | 16 ++ .../test-fixtures/tags/remove_all_golden.go | 16 ++ .../go/test-fixtures/tags/remove_all_input.go | 16 ++ doc/vim-go.txt | 36 ++- ftplugin/go/commands.vim | 7 +- plugin/go.vim | 1 + 9 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 autoload/go/tags.vim create mode 100644 autoload/go/tags_test.vim create mode 100644 autoload/go/test-fixtures/tags/add_all_golden.go create mode 100644 autoload/go/test-fixtures/tags/add_all_input.go create mode 100644 autoload/go/test-fixtures/tags/remove_all_golden.go create mode 100644 autoload/go/test-fixtures/tags/remove_all_input.go diff --git a/autoload/go/tags.vim b/autoload/go/tags.vim new file mode 100644 index 0000000000..248fcf9a3a --- /dev/null +++ b/autoload/go/tags.vim @@ -0,0 +1,206 @@ +function! go#tags#Add(start, end, count, ...) abort + let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') + if &modified + " Write current unsaved buffer to a temp file and use the modified content + let l:tmpname = tempname() + call writefile(getline(1, '$'), l:tmpname) + let fname = l:tmpname + endif + + let offset = 0 + if a:count == -1 + let offset = go#util#OffsetCursor() + endif + + let test_mode = 0 + call call("go#tags#run", [a:start, a:end, offset, "add", fname, test_mode] + a:000) + + " if exists, delete it as we don't need it anymore + if exists("l:tmpname") + call delete(l:tmpname) + endif +endfunction + +function! go#tags#Remove(start, end, count, ...) abort + let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') + if &modified + " Write current unsaved buffer to a temp file and use the modified content + let l:tmpname = tempname() + call writefile(getline(1, '$'), l:tmpname) + let fname = l:tmpname + endif + + let offset = 0 + if a:count == -1 + let offset = go#util#OffsetCursor() + endif + + let test_mode = 0 + call call("go#tags#run", [a:start, a:end, offset, "remove", fname, test_mode] + a:000) + + " if exists, delete it as we don't need it anymore + if exists("l:tmpname") + call delete(l:tmpname) + endif +endfunction + +" run runs gomodifytag. This is an internal test so we can test it +function! go#tags#run(start, end, offset, mode, fname, test_mode, ...) abort + " do not split this into multiple lines, somehow tests fail in that case + let args = {'mode': a:mode,'start': a:start,'end': a:end,'offset': a:offset,'fname': a:fname,'cmd_args': a:000} + + let result = s:create_cmd(args) + if has_key(result, 'err') + call go#util#EchoError(result.err) + return -1 + endif + + let command = join(result.cmd, " ") + + call go#cmd#autowrite() + let out = go#util#System(command) + if go#util#ShellError() != 0 + call go#util#EchoError(out) + return + endif + + if a:test_mode + exe 'edit ' . a:fname + endif + + call s:write_out(out) + + if a:test_mode + exe 'write! ' . a:fname + endif +endfunc + + +" write_out writes back the given output to the current buffer +func s:write_out(out) abort + " not a json output + if a:out[0] !=# '{' + return + endif + + " nothing to do + if empty(a:out) || type(a:out) != type("") + return + endif + + let result = json_decode(a:out) + if type(result) != type({}) + call go#util#EchoError(printf("malformed output from gomodifytags: %s", a:out)) + return + endif + + let lines = result['lines'] + let start_line = result['start'] + let end_line = result['end'] + + let index = 0 + for line in range(start_line, end_line) + call setline(line, lines[index]) + let index += 1 + endfor +endfunc + + +" create_cmd returns a dict that contains the command to execute gomodifytags +func s:create_cmd(args) abort + if !exists("*json_decode") + return {'err': "requires 'json_decode'. Update your Vim/Neovim version."} + endif + + let bin_path = go#path#CheckBinPath('gomodifytags') + if empty(bin_path) + return {'err': "gomodifytags does not exist"} + endif + + let l:start = a:args.start + let l:end = a:args.end + let l:offset = a:args.offset + let l:mode = a:args.mode + let l:cmd_args = a:args.cmd_args + + " start constructing the command + let cmd = [bin_path] + call extend(cmd, ["-format", "json"]) + call extend(cmd, ["-file", a:args.fname]) + + if l:offset != 0 + call extend(cmd, ["-offset", l:offset]) + else + let range = printf("%d,%d", l:start, l:end) + call extend(cmd, ["-line", range]) + endif + + if l:mode == "add" + let l:tags = [] + let l:options = [] + + if !empty(l:cmd_args) + for item in l:cmd_args + let splitted = split(item, ",") + + " tag only + if len(splitted) == 1 + call add(l:tags, splitted[0]) + endif + + " options only + if len(splitted) == 2 + call add(l:tags, splitted[0]) + call add(l:options, printf("%s=%s", splitted[0], splitted[1])) + endif + endfor + endif + + " construct options + if !empty(l:options) + call extend(cmd, ["-add-options", join(l:options, ",")]) + else + " default value + if empty(l:tags) + let l:tags = ["json"] + endif + + " construct tags + call extend(cmd, ["-add-tags", join(l:tags, ",")]) + endif + elseif l:mode == "remove" + if empty(l:cmd_args) + call add(cmd, "-clear-tags") + else + let l:tags = [] + let l:options = [] + for item in l:cmd_args + let splitted = split(item, ",") + + " tag only + if len(splitted) == 1 + call add(l:tags, splitted[0]) + endif + + " options only + if len(splitted) == 2 + call add(l:options, printf("%s=%s", splitted[0], splitted[1])) + endif + endfor + + " construct tags + if !empty(l:tags) + call extend(cmd, ["-remove-tags", join(l:tags, ",")]) + endif + + " construct options + if !empty(l:options) + call extend(cmd, ["-remove-options", join(l:options, ",")]) + endif + endif + else + return {'err': printf("unknown mode: %s", l:mode)} + endif + + return {'cmd': cmd} +endfunc diff --git a/autoload/go/tags_test.vim b/autoload/go/tags_test.vim new file mode 100644 index 0000000000..202aa0b38e --- /dev/null +++ b/autoload/go/tags_test.vim @@ -0,0 +1,30 @@ +func Test_add_tags() + let input_file = tempname() + call writefile(readfile("test-fixtures/tags/add_all_input.go"), input_file) + + let expected = join(readfile("test-fixtures/tags/add_all_golden.go"), "\n") + + " run for offset 40, which is inside the struct + call go#tags#run(0, 0, 40, "add", input_file, 1) + + let actual = join(readfile(input_file), "\n") + + call assert_equal(expected, actual) +endfunc + + +func Test_remove_tags() + let input_file = tempname() + call writefile(readfile("test-fixtures/tags/remove_all_input.go"), input_file) + + let expected = join(readfile("test-fixtures/tags/remove_all_golden.go"), "\n") + + " run for offset 40, which is inside the struct + call go#tags#run(0, 0, 40, "remove", input_file, 1) + + let actual = join(readfile(input_file), "\n") + + call assert_equal(expected, actual) +endfunc + + diff --git a/autoload/go/test-fixtures/tags/add_all_golden.go b/autoload/go/test-fixtures/tags/add_all_golden.go new file mode 100644 index 0000000000..eaa3e7b3b4 --- /dev/null +++ b/autoload/go/test-fixtures/tags/add_all_golden.go @@ -0,0 +1,16 @@ +package main + +type Server struct { + Name string `json:"name"` + ID int `json:"id"` + MyHomeAddress string `json:"my_home_address"` + SubDomains []string `json:"sub_domains"` + Empty string `json:"empty"` + Example int64 `json:"example"` + Example2 string `json:"example_2"` + Bar struct { + Four string `json:"four"` + Five string `json:"five"` + } `json:"bar"` + Lala interface{} `json:"lala"` +} diff --git a/autoload/go/test-fixtures/tags/add_all_input.go b/autoload/go/test-fixtures/tags/add_all_input.go new file mode 100644 index 0000000000..bfc4d3e50b --- /dev/null +++ b/autoload/go/test-fixtures/tags/add_all_input.go @@ -0,0 +1,16 @@ +package main + +type Server struct { + Name string + ID int + MyHomeAddress string + SubDomains []string + Empty string + Example int64 + Example2 string + Bar struct { + Four string + Five string + } + Lala interface{} +} diff --git a/autoload/go/test-fixtures/tags/remove_all_golden.go b/autoload/go/test-fixtures/tags/remove_all_golden.go new file mode 100644 index 0000000000..de57085887 --- /dev/null +++ b/autoload/go/test-fixtures/tags/remove_all_golden.go @@ -0,0 +1,16 @@ +package main + +type Server struct { + Name string + ID int + MyHomeAddress string + SubDomains []string + Empty string + Example int64 + Example2 string + Bar struct { + Four string + Five string + } + Lala interface{} +} diff --git a/autoload/go/test-fixtures/tags/remove_all_input.go b/autoload/go/test-fixtures/tags/remove_all_input.go new file mode 100644 index 0000000000..eaa3e7b3b4 --- /dev/null +++ b/autoload/go/test-fixtures/tags/remove_all_input.go @@ -0,0 +1,16 @@ +package main + +type Server struct { + Name string `json:"name"` + ID int `json:"id"` + MyHomeAddress string `json:"my_home_address"` + SubDomains []string `json:"sub_domains"` + Empty string `json:"empty"` + Example int64 `json:"example"` + Example2 string `json:"example_2"` + Bar struct { + Four string `json:"four"` + Five string `json:"five"` + } `json:"bar"` + Lala interface{} `json:"lala"` +} diff --git a/doc/vim-go.txt b/doc/vim-go.txt index 2d7a03b292..bbfdd32335 100644 --- a/doc/vim-go.txt +++ b/doc/vim-go.txt @@ -669,12 +669,13 @@ CTRL-t :GoImpl T io.ReadWriteCloser < *:GoAddTags* -:[range]GoAddTags [key] [key1] ... +:[range]GoAddTags [key],[option] [key1],[option] ... Adds field tags for the fields of a struct. If called inside a struct it automatically add field tags with the `json` key and the value automatically generated based on the field name. An error message is given - if it's called outside a struct definition. + if it's called outside a struct definition or if the file is not correctly + formatted. If [range] is given, only the selected fields will be changed. @@ -682,6 +683,37 @@ CTRL-t arguments. An example of adding `xml` and `db` would be: > :GoAddTags xml db +< + If [option] is passed it'll either add a new tag with an option or will + modify exising tags. An example of adding `omitempty` to all `json` fields + would be: +> + :GoAddTags json,omitempty +< + You can define a constant value instead of the default field based value. + For example the following command will add ``valid:"1"`` to all fields. +> + :GoAddTags valid=1 +< + *:GoRemoveTags* +:[range]GoRemoveTags [key],[option] [key1],[option1] ... + + Rmove field tags for the fields of a struct. If called inside a struct it + automatically remove all field tags. An error message is given if it's + called outside a struct definition or if the file is not correctly + formatted + + If [range] is given, only the selected fields will be changed. + + If [key] is given, it will only remove those keys. Example: +> + :GoRemoveTags json +< + If [option] is passed with a [key], it will only remove the options. + Example, this will only remove `omitempty` options from fields containing + `json`: +> + :GoRemoveTags json,omitempty < *:GoAutoTypeInfoToggle* :GoAutoTypeInfoToggle diff --git a/ftplugin/go/commands.vim b/ftplugin/go/commands.vim index d8bff6655f..6f108a6755 100644 --- a/ftplugin/go/commands.vim +++ b/ftplugin/go/commands.vim @@ -14,14 +14,15 @@ command! -range=% GoChannelPeers call go#guru#ChannelPeers() command! -range=% GoReferrers call go#guru#Referrers() command! -nargs=? GoGuruTags call go#guru#Tags() - -command! -nargs=* -range GoAddTags call go#util#AddTags(, , ) - command! -range=0 GoSameIds call go#guru#SameIds() command! -range=0 GoSameIdsClear call go#guru#ClearSameIds() command! -range=0 GoSameIdsToggle call go#guru#ToggleSameIds() command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToogleSameIds() +" -- tags +command! -nargs=* -range GoAddTags call go#tags#Add(, , , ) +command! -nargs=* -range GoRemoveTags call go#tags#Remove(, , , ) + " -- tool command! -nargs=0 GoFiles echo go#tool#Files() command! -nargs=0 GoDeps echo go#tool#Deps() diff --git a/plugin/go.vim b/plugin/go.vim index 26547310a2..8a38145341 100644 --- a/plugin/go.vim +++ b/plugin/go.vim @@ -18,6 +18,7 @@ let s:packages = [ \ "github.com/jstemmer/gotags", \ "github.com/klauspost/asmfmt/cmd/asmfmt", \ "github.com/fatih/motion", + \ "github.com/fatih/gomodifytags", \ "github.com/zmb3/gogetdoc", \ "github.com/josharian/impl", \ ]