Skip to content

Commit

Permalink
New GoTags improvements
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
fatih committed Feb 13, 2017
1 parent 3eb57ac commit 1d9f51f
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 5 deletions.
206 changes: 206 additions & 0 deletions autoload/go/tags.vim
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions autoload/go/tags_test.vim
Original file line number Diff line number Diff line change
@@ -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


16 changes: 16 additions & 0 deletions autoload/go/test-fixtures/tags/add_all_golden.go
Original file line number Diff line number Diff line change
@@ -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"`
}
16 changes: 16 additions & 0 deletions autoload/go/test-fixtures/tags/add_all_input.go
Original file line number Diff line number Diff line change
@@ -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{}
}
16 changes: 16 additions & 0 deletions autoload/go/test-fixtures/tags/remove_all_golden.go
Original file line number Diff line number Diff line change
@@ -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{}
}
16 changes: 16 additions & 0 deletions autoload/go/test-fixtures/tags/remove_all_input.go
Original file line number Diff line number Diff line change
@@ -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"`
}
36 changes: 34 additions & 2 deletions doc/vim-go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -669,19 +669,51 @@ 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.

The default `json` can be changed by providing one or more [key]
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
Expand Down
7 changes: 4 additions & 3 deletions ftplugin/go/commands.vim
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ command! -range=% GoChannelPeers call go#guru#ChannelPeers(<count>)
command! -range=% GoReferrers call go#guru#Referrers(<count>)
command! -nargs=? GoGuruTags call go#guru#Tags(<f-args>)


command! -nargs=* -range GoAddTags call go#util#AddTags(<line1>, <line2>, <f-args>)

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(<line1>, <line2>, <count>, <f-args>)
command! -nargs=* -range GoRemoveTags call go#tags#Remove(<line1>, <line2>, <count>, <f-args>)

" -- tool
command! -nargs=0 GoFiles echo go#tool#Files()
command! -nargs=0 GoDeps echo go#tool#Deps()
Expand Down
1 change: 1 addition & 0 deletions plugin/go.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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",
\ ]
Expand Down

0 comments on commit 1d9f51f

Please sign in to comment.