1
0
mirror of https://github.com/amix/vimrc synced 2025-07-18 17:44:59 +08:00
This commit is contained in:
huangqundl
2017-03-17 23:12:53 +08:00
parent 47b213d974
commit cba39b7326
855 changed files with 59981 additions and 35298 deletions

View File

@ -0,0 +1,159 @@
let s:go_decls_var = {
\ 'init': 'ctrlp#decls#init()',
\ 'exit': 'ctrlp#decls#exit()',
\ 'enter': 'ctrlp#decls#enter()',
\ 'accept': 'ctrlp#decls#accept',
\ 'lname': 'declarations',
\ 'sname': 'decls',
\ 'type': 'tabs',
\}
if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
let g:ctrlp_ext_vars = add(g:ctrlp_ext_vars, s:go_decls_var)
else
let g:ctrlp_ext_vars = [s:go_decls_var]
endif
function! ctrlp#decls#init() abort
cal s:enable_syntax()
return s:decls
endfunction
function! ctrlp#decls#exit() abort
unlet! s:decls s:current_dir s:target
endfunction
" The action to perform on the selected string
" Arguments:
" a:mode the mode that has been chosen by pressing <cr> <c-v> <c-t> or <c-x>
" the values are 'e', 'v', 't' and 'h', respectively
" a:str the selected string
function! ctrlp#decls#accept(mode, str) abort
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
" we jump to the file directory so we can get the fullpath via fnamemodify
" below
execute cd . s:current_dir
let vals = matchlist(a:str, '|\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)|')
" i.e: main.go
let filename = vals[1]
let line = vals[2]
let col = vals[3]
" i.e: /Users/fatih/vim-go/main.go
let filepath = fnamemodify(filename, ":p")
" acceptile is a very versatile method,
call ctrlp#acceptfile(a:mode, filepath)
call cursor(line, col)
silent! norm! zvzz
finally
"jump back to old dir
execute cd . fnameescape(dir)
endtry
endfunction
function! ctrlp#decls#enter() abort
let s:current_dir = fnameescape(expand('%:p:h'))
let s:decls = []
let bin_path = go#path#CheckBinPath('motion')
if empty(bin_path)
return
endif
let command = printf("%s -format vim -mode decls", bin_path)
let command .= " -include ". get(g:, "go_decls_includes", "func,type")
call go#cmd#autowrite()
if s:mode == 0
" current file mode
let fname = expand("%:p")
if exists('s:target')
let fname = s:target
endif
let command .= printf(" -file %s", fname)
else
" all functions mode
let dir = expand("%:p:h")
if exists('s:target')
let dir = s:target
endif
let command .= printf(" -dir %s", dir)
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
call go#util#EchoError(out)
return
endif
if exists("l:tmpname")
call delete(l:tmpname)
endif
let result = eval(out)
if type(result) != 4 || !has_key(result, 'decls')
return
endif
let decls = result.decls
" find the maximum function name
let max_len = 0
for decl in decls
if len(decl.ident)> max_len
let max_len = len(decl.ident)
endif
endfor
for decl in decls
" paddings
let space = " "
for i in range(max_len - len(decl.ident))
let space .= " "
endfor
call add(s:decls, printf("%s\t%s |%s:%s:%s|\t%s",
\ decl.ident . space,
\ decl.keyword,
\ fnamemodify(decl.filename, ":t"),
\ decl.line,
\ decl.col,
\ decl.full,
\))
endfor
endfunc
function! s:enable_syntax() abort
if !(has('syntax') && exists('g:syntax_on'))
return
endif
syntax match CtrlPIdent '\zs\h\+\ze\s'
syntax match CtrlPKeyword '\zs[^\t|]\+\ze|[^|]\+:\d\+:\d\+|'
syntax match CtrlPFilename '|\zs[^|]\+:\d\+:\d\+\ze|'
syntax match CtrlPSignature '\zs\t.*\ze$' contains=CtrlPKeyWord,CtrlPFilename
highlight link CtrlPIdent Function
highlight link CtrlPKeyword Keyword
highlight link CtrlPFilename SpecialComment
highlight link CtrlPSignature Comment
endfunction
let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars)
function! ctrlp#decls#cmd(mode, ...) abort
let s:mode = a:mode
if a:0 && !empty(a:1)
let s:target = a:1
endif
return s:id
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,32 @@
" By default use edit (current buffer view) to switch
if !exists("g:go_alternate_mode")
let g:go_alternate_mode = "edit"
endif
" Test alternates between the implementation of code and the test code.
function! go#alternate#Switch(bang, cmd) abort
let file = expand('%')
if empty(file)
call go#util#EchoError("no buffer name")
return
elseif file =~# '^\f\+_test\.go$'
let l:root = split(file, '_test.go$')[0]
let l:alt_file = l:root . ".go"
elseif file =~# '^\f\+\.go$'
let l:root = split(file, ".go$")[0]
let l:alt_file = l:root . '_test.go'
else
call go#util#EchoError("not a go file")
return
endif
if !filereadable(alt_file) && !bufexists(alt_file) && !a:bang
call go#util#EchoError("couldn't find ".alt_file)
return
elseif empty(a:cmd)
execute ":" . g:go_alternate_mode . " " . alt_file
else
execute ":" . a:cmd . " " . alt_file
endif
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,69 @@
" asmfmt.vim: Vim command to format Go asm files with asmfmt
" (github.com/klauspost/asmfmt).
"
" This filetype plugin adds new commands for asm buffers:
"
" :Fmt
"
" Filter the current asm buffer through asmfmt.
" It tries to preserve cursor position and avoids
" replacing the buffer with stderr output.
"
" Options:
"
" g:go_asmfmt_autosave [default=0]
"
" Flag to automatically call :Fmt when file is saved.
let s:got_fmt_error = 0
" This is a trimmed-down version of the logic in fmt.vim.
function! go#asmfmt#Format() abort
" Save state.
let l:curw = winsaveview()
" Write the current buffer to a tempfile.
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
" Run asmfmt.
let path = go#path#CheckBinPath("asmfmt")
if empty(path)
return
endif
let out = go#util#System(path . ' -w ' . l:tmpname)
" If there's no error, replace the current file with the output.
if go#util#ShellError() == 0
" Remove undo point caused by BufWritePre.
try | silent undojoin | catch | endtry
" Replace the current file with the temp file; then reload the buffer.
let old_fileformat = &fileformat
" save old file permissions
let original_fperm = getfperm(expand('%'))
call rename(l:tmpname, expand('%'))
" restore old file permissions
call setfperm(expand('%'), original_fperm)
silent edit!
let &fileformat = old_fileformat
let &syntax = &syntax
endif
" Restore the cursor/window positions.
call winrestview(l:curw)
endfunction
function! go#asmfmt#ToggleAsmFmtAutoSave() abort
if get(g:, "go_asmfmt_autosave", 0)
let g:go_asmfmt_autosave = 1
call go#util#EchoProgress("auto asmfmt enabled")
return
end
let g:go_asmfmt_autosave = 0
call go#util#EchoProgress("auto asmfmt disabled")
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,144 +1,492 @@
if !exists("g:go_jump_to_error")
let g:go_jump_to_error = 1
endif
function! go#cmd#Run(bang, ...)
let default_makeprg = &makeprg
if !len(a:000)
let &makeprg = "go run " . join(go#tool#Files(), ' ')
else
let &makeprg = "go run " . expand(a:1)
endif
exe 'make!'
if !a:bang
cwindow
let errors = getqflist()
if !empty(errors)
if g:go_jump_to_error
cc 1 "jump to first error if there is any
endif
endif
endif
let &makeprg = default_makeprg
function! go#cmd#autowrite() abort
if &autowrite == 1
silent! wall
endif
endfunction
function! go#cmd#Install(...)
let pkgs = join(a:000, ' ')
let command = 'go install '.pkgs
let out = go#tool#ExecuteInDir(command)
if v:shell_error
call go#tool#ShowErrors(out)
cwindow
return
" Build builds the source code without producting any output binary. We live in
" an editor so the best is to build it to catch errors and fix them. By
" default it tries to call simply 'go build', but it first tries to get all
" dependent files for the current folder and passes it to go build.
function! go#cmd#Build(bang, ...) abort
" expand all wildcards(i.e: '%' to the current file name)
let goargs = map(copy(a:000), "expand(v:val)")
" escape all shell arguments before we pass it to make
if !has('nvim')
let goargs = go#util#Shelllist(goargs, 1)
endif
" create our command arguments. go build discards any results when it
" compiles multiple packages. So we pass the `errors` package just as an
" placeholder with the current folder (indicated with '.')
let args = ["build"] + goargs + [".", "errors"]
if go#util#has_job()
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("building dispatched ...")
endif
if exists("$GOBIN")
echon "vim-go: " | echohl Function | echon "installed to ". $GOBIN | echohl None
call s:cmd_job({
\ 'cmd': ['go'] + args,
\ 'bang': a:bang,
\})
return
elseif has('nvim')
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("building dispatched ...")
endif
" if we have nvim, call it asynchronously and return early ;)
call go#jobcontrol#Spawn(a:bang, "build", args)
return
endif
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
let default_makeprg = &makeprg
let &makeprg = "go " . join(args, ' ')
let l:listtype = go#list#Type("quickfix")
" execute make inside the source folder so we can parse the errors
" correctly
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd . fnameescape(expand("%:p:h"))
if l:listtype == "locationlist"
silent! exe 'lmake!'
else
echon "vim-go: " | echohl Function | echon "installed to ". $GOPATH . "/bin" | echohl None
silent! exe 'make!'
endif
endfunction
function! go#cmd#Build(bang)
let default_makeprg = &makeprg
let gofiles = join(go#tool#Files(), ' ')
if v:shell_error
let &makeprg = "go build . errors"
else
let &makeprg = "go build -o /dev/null " . gofiles
endif
echon "vim-go: " | echohl Identifier | echon "building ..."| echohl None
silent! exe 'make!'
redraw!
finally
execute cd . fnameescape(dir)
endtry
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
else
call go#util#EchoSuccess("[build] SUCCESS")
endif
let &makeprg = default_makeprg
let $GOPATH = old_gopath
endfunction
" BuildTags sets or shows the current build tags used for tools
function! go#cmd#BuildTags(bang, ...) abort
if a:0
if a:0 == 1 && a:1 == '""'
unlet g:go_build_tags
call go#util#EchoSuccess("build tags are cleared")
else
let g:go_build_tags = a:1
call go#util#EchoSuccess("build tags are changed to: ". a:1)
endif
return
endif
if !exists('g:go_build_tags')
call go#util#EchoSuccess("build tags are not set")
else
call go#util#EchoSuccess("current build tags: ". g:go_build_tags)
endif
endfunction
" Run runs the current file (and their dependencies if any) in a new terminal.
function! go#cmd#RunTerm(bang, mode, files) abort
if empty(a:files)
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
else
let cmd = "go run ". go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1)
endif
call go#term#newmode(a:bang, cmd, a:mode)
endfunction
" Run runs the current file (and their dependencies if any) and outputs it.
" This is intented to test small programs and play with them. It's not
" suitable for long running apps, because vim is blocking by default and
" calling long running apps will block the whole UI.
function! go#cmd#Run(bang, ...) abort
if has('nvim')
call go#cmd#RunTerm(a:bang, '', a:000)
return
endif
if go#util#has_job()
" NOTE(arslan): 'term': 'open' case is not implement for +jobs. This means
" executions waiting for stdin will not work. That's why we don't do
" anything. Once this is implemented we're going to make :GoRun async
endif
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
if go#util#IsWin()
exec '!go run ' . go#util#Shelljoin(go#tool#Files())
if v:shell_error
redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None
else
redraws! | echon "vim-go: [run] " | echohl Function | echon "SUCCESS"| echohl None
endif
let $GOPATH = old_gopath
return
endif
" :make expands '%' and '#' wildcards, so they must also be escaped
let default_makeprg = &makeprg
if a:0 == 0
let &makeprg = 'go run ' . go#util#Shelljoin(go#tool#Files(), 1)
else
let &makeprg = "go run " . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
endif
let l:listtype = go#list#Type("quickfix")
if l:listtype == "locationlist"
exe 'lmake!'
else
exe 'make!'
endif
let items = go#list#Get(l:listtype)
let errors = go#tool#FilterValids(items)
call go#list#Populate(l:listtype, errors, &makeprg)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
endif
let $GOPATH = old_gopath
let &makeprg = default_makeprg
endfunction
" Install installs the package by simple calling 'go install'. If any argument
" is given(which are passed directly to 'go install') it tries to install
" those packages. Errors are populated in the location window.
function! go#cmd#Install(bang, ...) abort
" use vim's job functionality to call it asynchronously
if go#util#has_job()
" expand all wildcards(i.e: '%' to the current file name)
let goargs = map(copy(a:000), "expand(v:val)")
" escape all shell arguments before we pass it to make
let goargs = go#util#Shelllist(goargs, 1)
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("installing dispatched ...")
endif
call s:cmd_job({
\ 'cmd': ['go', 'install'] + goargs,
\ 'bang': a:bang,
\})
return
endif
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
let default_makeprg = &makeprg
" :make expands '%' and '#' wildcards, so they must also be escaped
let goargs = go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
let &makeprg = "go install " . goargs
let l:listtype = go#list#Type("quickfix")
" execute make inside the source folder so we can parse the errors
" correctly
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd . fnameescape(expand("%:p:h"))
if l:listtype == "locationlist"
silent! exe 'lmake!'
else
silent! exe 'make!'
endif
redraw!
finally
execute cd . fnameescape(dir)
endtry
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
else
call go#util#EchoSuccess("installed to ". $GOPATH)
endif
let $GOPATH = old_gopath
let &makeprg = default_makeprg
endfunction
" Test runs `go test` in the current directory. If compile is true, it'll
" compile the tests instead of running them (useful to catch errors in the
" test files). Any other argument is appendend to the final `go test` command
function! go#cmd#Test(bang, compile, ...) abort
let args = ["test"]
" don't run the test, only compile it. Useful to capture and fix errors.
if a:compile
let compile_file = "vim-go-test-compile"
call extend(args, ["-c", "-o", compile_file])
endif
if a:0
let goargs = a:000
" do not expand for coverage mode as we're passing the arg ourself
if a:1 != '-coverprofile'
" expand all wildcards(i.e: '%' to the current file name)
let goargs = map(copy(a:000), "expand(v:val)")
endif
if !(has('nvim') || go#util#has_job())
let goargs = go#util#Shelllist(goargs, 1)
endif
call extend(args, goargs, 1)
else
" only add this if no custom flags are passed
let timeout = get(g:, 'go_test_timeout', '10s')
call add(args, printf("-timeout=%s", timeout))
endif
if get(g:, 'go_echo_command_info', 1)
if a:compile
echon "vim-go: " | echohl Identifier | echon "compiling tests ..." | echohl None
else
echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None
endif
endif
if go#util#has_job()
" use vim's job functionality to call it asynchronously
let job_args = {
\ 'cmd': ['go'] + args,
\ 'bang': a:bang,
\ }
if a:compile
let job_args['custom_cb'] = function('s:test_compile', [compile_file])
endif
call s:cmd_job(job_args)
return
elseif has('nvim')
" use nvims's job functionality
if get(g:, 'go_term_enabled', 0)
let id = go#term#new(a:bang, ["go"] + args)
else
let id = go#jobcontrol#Spawn(a:bang, "test", args)
endif
if a:compile
call go#jobcontrol#AddHandler(function('s:test_compile_handler'))
let s:test_compile_handlers[id] = compile_file
endif
return id
endif
call go#cmd#autowrite()
redraw
let command = "go " . join(args, ' ')
let out = go#tool#ExecuteInDir(command)
let l:listtype = "quickfix"
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
execute cd fnameescape(expand("%:p:h"))
if a:compile
call delete(compile_file)
endif
if go#util#ShellError() != 0
let errors = go#tool#ParseErrors(split(out, '\n'))
let errors = go#tool#FilterValids(errors)
call go#list#Populate(l:listtype, errors, command)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
elseif empty(errors)
" failed to parse errors, output the original content
call go#util#EchoError(out)
endif
echon "vim-go: " | echohl ErrorMsg | echon "[test] FAIL" | echohl None
else
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
if a:compile
echon "vim-go: " | echohl Function | echon "[test] SUCCESS" | echohl None
else
echon "vim-go: " | echohl Function | echon "[test] PASS" | echohl None
endif
endif
execute cd . fnameescape(dir)
endfunction
" Testfunc runs a single test that surrounds the current cursor position.
" Arguments are passed to the `go test` command.
function! go#cmd#TestFunc(bang, ...) abort
" search flags legend (used only)
" 'b' search backward instead of forward
" 'c' accept a match at the cursor position
" 'n' do Not move the cursor
" 'W' don't wrap around the end of the file
"
" for the full list
" :help search
let test = search('func \(Test\|Example\)', "bcnW")
if test == 0
echo "vim-go: [test] no test found immediate to cursor"
return
end
let line = getline(test)
let name = split(split(line, " ")[1], "(")[0]
let args = [a:bang, 0, "-run", name . "$"]
if a:0
call extend(args, a:000)
endif
call call('go#cmd#Test', args)
endfunction
" Generate runs 'go generate' in similar fashion to go#cmd#Build()
function! go#cmd#Generate(bang, ...) abort
let default_makeprg = &makeprg
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" :make expands '%' and '#' wildcards, so they must also be escaped
let goargs = go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
if go#util#ShellError() != 0
let &makeprg = "go generate " . goargs
else
let gofiles = go#util#Shelljoin(go#tool#Files(), 1)
let &makeprg = "go generate " . goargs . ' ' . gofiles
endif
let l:listtype = go#list#Type("quickfix")
echon "vim-go: " | echohl Identifier | echon "generating ..."| echohl None
if l:listtype == "locationlist"
silent! exe 'lmake!'
else
silent! exe 'make!'
endif
redraw!
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if !empty(errors)
if !a:bang
cwindow
let errors = getqflist()
if !empty(errors)
if g:go_jump_to_error
cc 1 "jump to first error if there is any
endif
else
redraws! | echon "vim-go: " | echohl Function | echon "[build] SUCCESS"| echohl None
endif
call go#list#JumpToFirst(l:listtype)
endif
else
redraws! | echon "vim-go: " | echohl Function | echon "[generate] SUCCESS"| echohl None
endif
let &makeprg = default_makeprg
let &makeprg = default_makeprg
let $GOPATH = old_gopath
endfunction
function! go#cmd#Test(...)
let command = "go test ."
if len(a:000)
let command = "go test " . expand(a:1)
" ---------------------
" | Vim job callbacks |
" ---------------------
function s:cmd_job(args) abort
let status_dir = expand('%:p:h')
let started_at = reltime()
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': a:args.cmd[1],
\ 'state': "started",
\})
" autowrite is not enabled for jobs
call go#cmd#autowrite()
function! s:error_info_cb(job, exit_status, data) closure abort
let status = {
\ 'desc': 'last status',
\ 'type': a:args.cmd[1],
\ 'state': "success",
\ }
if a:exit_status
let status.state = "failed"
endif
echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None
let out = go#tool#ExecuteInDir(command)
if v:shell_error
call go#tool#ShowErrors(out)
else
call setqflist([])
endif
cwindow
let elapsed_time = reltimestr(reltime(started_at))
" strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time)
let errors = getqflist()
if !empty(errors)
if g:go_jump_to_error
cc 1 "jump to first error if there is any
endif
else
redraw | echon "vim-go: " | echohl Function | echon "[test] PASS" | echohl None
endif
call go#statusline#Update(status_dir, status)
endfunction
let a:args.error_info_cb = funcref('s:error_info_cb')
let callbacks = go#job#Spawn(a:args)
let start_options = {
\ 'callback': callbacks.callback,
\ 'close_cb': callbacks.close_cb,
\ }
" modify GOPATH if needed
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" pre start
let dir = getcwd()
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let jobdir = fnameescape(expand("%:p:h"))
execute cd . jobdir
call job_start(a:args.cmd, start_options)
" post start
execute cd . fnameescape(dir)
let $GOPATH = old_gopath
endfunction
function! go#cmd#Coverage(...)
let l:tmpname=tempname()
let command = "go test -coverprofile=".l:tmpname
let out = go#tool#ExecuteInDir(command)
if v:shell_error
call go#tool#ShowErrors(out)
else
" clear previous quick fix window
call setqflist([])
let openHTML = 'go tool cover -html='.l:tmpname
call go#tool#ExecuteInDir(openHTML)
endif
cwindow
let errors = getqflist()
if !empty(errors)
if g:go_jump_to_error
cc 1 "jump to first error if there is any
endif
endif
call delete(l:tmpname)
" test_compile is called when a GoTestCompile call is finished
function! s:test_compile(test_file, job, exit_status, data) abort
call delete(a:test_file)
endfunction
function! go#cmd#Vet()
echon "vim-go: " | echohl Identifier | echon "calling vet..." | echohl None
let out = go#tool#ExecuteInDir('go vet')
if v:shell_error
call go#tool#ShowErrors(out)
else
call setqflist([])
endif
cwindow
" -----------------------
" | Neovim job handlers |
" -----------------------
let s:test_compile_handlers = {}
let errors = getqflist()
if !empty(errors)
if g:go_jump_to_error
cc 1 "jump to first error if there is any
endif
else
redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None
endif
function! s:test_compile_handler(job, exit_status, data) abort
if !has_key(s:test_compile_handlers, a:job.id)
return
endif
let l:compile_file = s:test_compile_handlers[a:job.id]
call delete(l:compile_file)
unlet s:test_compile_handlers[a:job.id]
endfunction
" vim:ts=4:sw=4:et
"
" vim: sw=2 ts=2 et

View File

@ -1,142 +1,172 @@
if !exists("g:go_gocode_bin")
let g:go_gocode_bin = "gocode"
endif
let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix'
fu! s:gocodeCurrentBuffer()
let buf = getline(1, '$')
if &encoding != 'utf-8'
let buf = map(buf, 'iconv(v:val, &encoding, "utf-8")')
endif
if &l:fileformat == 'dos'
" XXX: line2byte() depend on 'fileformat' option.
" so if fileformat is 'dos', 'buf' must include '\r'.
let buf = map(buf, 'v:val."\r"')
endif
let file = tempname()
call writefile(buf, file)
return file
endf
let s:vim_system = get(g:, 'gocomplete#system_function', 'system')
fu! s:system(str, ...)
return call(s:vim_system, [a:str] + a:000)
endf
fu! s:gocodeShellescape(arg)
try
let ssl_save = &shellslash
set noshellslash
return shellescape(a:arg)
finally
let &shellslash = ssl_save
endtry
endf
fu! s:gocodeCommand(cmd, preargs, args)
for i in range(0, len(a:args) - 1)
let a:args[i] = s:gocodeShellescape(a:args[i])
endfor
for i in range(0, len(a:preargs) - 1)
let a:preargs[i] = s:gocodeShellescape(a:preargs[i])
endfor
let bin_path = go#tool#BinPath(g:go_gocode_bin)
if empty(bin_path)
return
endif
let result = s:system(printf('%s %s %s %s', bin_path, join(a:preargs), a:cmd, join(a:args)))
if v:shell_error != 0
return "[\"0\", []]"
else
if &encoding != 'utf-8'
let result = iconv(result, 'utf-8', &encoding)
endif
return result
endif
endf
fu! s:gocodeCurrentBufferOpt(filename)
return '-in=' . a:filename
endf
fu! s:gocodeCursor()
if &encoding != 'utf-8'
let sep = &l:fileformat == 'dos' ? "\r\n" : "\n"
let c = col('.')
let buf = line('.') == 1 ? "" : (join(getline(1, line('.')-1), sep) . sep)
let buf .= c == 1 ? "" : getline('.')[:c-2]
return printf('%d', len(iconv(buf, &encoding, "utf-8")))
endif
return printf('%d', line2byte(line('.')) + (col('.')-2))
endf
fu! s:gocodeAutocomplete()
let filename = s:gocodeCurrentBuffer()
let result = s:gocodeCommand('autocomplete',
\ [s:gocodeCurrentBufferOpt(filename), '-f=vim'],
\ [expand('%:p'), s:gocodeCursor()])
call delete(filename)
return result
endf
function! go#complete#GetInfo()
let filename = s:gocodeCurrentBuffer()
let result = s:gocodeCommand('autocomplete',
\ [s:gocodeCurrentBufferOpt(filename), '-f=godit'],
\ [expand('%:p'), s:gocodeCursor()])
call delete(filename)
" first line is: Charcount,,NumberOfCandidates, i.e: 8,,1
" following lines are candiates, i.e: func foo(name string),,foo(
let out = split(result, '\n')
" no candidates are found
if len(out) == 1
return
endif
" only one candiate is found
if len(out) == 2
return split(out[1], ',,')[0]
endif
" to many candidates are available, pick one that maches the word under the
" cursor
let infos = []
for info in out[1:]
call add(infos, split(info, ',,')[0])
endfor
let wordMatch = '\<' . expand("<cword>") . '\>'
" escape single quotes in wordMatch before passing it to filter
let wordMatch = substitute(wordMatch, "'", "''", "g")
let filtered = filter(infos, "v:val =~ '".wordMatch."'")
if len(filtered) == 1
return filtered[0]
endif
function! s:gocodeCurrentBuffer() abort
let file = tempname()
call writefile(go#util#GetLines(), file)
return file
endfunction
function! go#complete#Info()
let result = go#complete#GetInfo()
if len(result) > 0
echo "vim-go: " | echohl Function | echon result | echohl None
endif
endfunction!
function! s:gocodeCommand(cmd, preargs, args) abort
for i in range(0, len(a:args) - 1)
let a:args[i] = go#util#Shellescape(a:args[i])
endfor
for i in range(0, len(a:preargs) - 1)
let a:preargs[i] = go#util#Shellescape(a:preargs[i])
endfor
fu! go#complete#Complete(findstart, base)
"findstart = 1 when we need to get the text length
if a:findstart == 1
execute "silent let g:gocomplete_completions = " . s:gocodeAutocomplete()
return col('.') - g:gocomplete_completions[0] - 1
"findstart = 0 when we need to return the list of completions
else
return g:gocomplete_completions[1]
let bin_path = go#path#CheckBinPath("gocode")
if empty(bin_path)
return
endif
" we might hit cache problems, as gocode doesn't handle well different
" GOPATHS: https://github.com/nsf/gocode/issues/239
let old_gopath = $GOPATH
let old_goroot = $GOROOT
let $GOPATH = go#path#Detect()
let $GOROOT = go#util#env("goroot")
let socket_type = get(g:, 'go_gocode_socket_type', s:sock_type)
let cmd = printf('%s -sock %s %s %s %s',
\ go#util#Shellescape(bin_path),
\ socket_type,
\ join(a:preargs),
\ go#util#Shellescape(a:cmd),
\ join(a:args)
\ )
let result = go#util#System(cmd)
let $GOPATH = old_gopath
let $GOROOT = old_goroot
if go#util#ShellError() != 0
return "[\"0\", []]"
else
if &encoding != 'utf-8'
let result = iconv(result, 'utf-8', &encoding)
endif
return result
endif
endfunction
function! s:gocodeCurrentBufferOpt(filename) abort
return '-in=' . a:filename
endfunction
let s:optionsEnabled = 0
function! s:gocodeEnableOptions() abort
if s:optionsEnabled
return
endif
let bin_path = go#path#CheckBinPath("gocode")
if empty(bin_path)
return
endif
let s:optionsEnabled = 1
call go#util#System(printf('%s set propose-builtins %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_propose_builtins', 1))))
call go#util#System(printf('%s set autobuild %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_autobuild', 1))))
call go#util#System(printf('%s set unimported-packages %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_unimported_packages', 0))))
endfunction
function! s:toBool(val) abort
if a:val | return 'true ' | else | return 'false' | endif
endfunction
function! s:gocodeAutocomplete() abort
call s:gocodeEnableOptions()
let filename = s:gocodeCurrentBuffer()
let result = s:gocodeCommand('autocomplete',
\ [s:gocodeCurrentBufferOpt(filename), '-f=vim'],
\ [expand('%:p'), go#util#OffsetCursor()])
call delete(filename)
return result
endfunction
function! go#complete#GetInfo() abort
let offset = go#util#OffsetCursor()+1
let filename = s:gocodeCurrentBuffer()
let result = s:gocodeCommand('autocomplete',
\ [s:gocodeCurrentBufferOpt(filename), '-f=godit'],
\ [expand('%:p'), offset])
call delete(filename)
" first line is: Charcount,,NumberOfCandidates, i.e: 8,,1
" following lines are candiates, i.e: func foo(name string),,foo(
let out = split(result, '\n')
" no candidates are found
if len(out) == 1
return ""
endif
" only one candiate is found
if len(out) == 2
return split(out[1], ',,')[0]
endif
" to many candidates are available, pick one that maches the word under the
" cursor
let infos = []
for info in out[1:]
call add(infos, split(info, ',,')[0])
endfor
let wordMatch = '\<' . expand("<cword>") . '\>'
" escape single quotes in wordMatch before passing it to filter
let wordMatch = substitute(wordMatch, "'", "''", "g")
let filtered = filter(infos, "v:val =~ '".wordMatch."'")
if len(filtered) == 1
return filtered[0]
endif
return ""
endfunction
function! go#complete#Info(auto) abort
" auto is true if we were called by g:go_auto_type_info's autocmd
let result = go#complete#GetInfo()
if !empty(result)
" if auto, and the result is a PANIC by gocode, hide it
if a:auto && result ==# 'PANIC PANIC PANIC' | return | endif
echo "vim-go: " | echohl Function | echon result | echohl None
endif
endfunction
function! s:trim_bracket(val) abort
let a:val.word = substitute(a:val.word, '[(){}\[\]]\+$', '', '')
return a:val
endfunction
function! go#complete#Complete(findstart, base) abort
"findstart = 1 when we need to get the text length
if a:findstart == 1
execute "silent let g:gocomplete_completions = " . s:gocodeAutocomplete()
return col('.') - g:gocomplete_completions[0] - 1
"findstart = 0 when we need to return the list of completions
else
let s = getline(".")[col('.') - 1]
if s =~ '[(){}\{\}]'
return map(copy(g:gocomplete_completions[1]), 's:trim_bracket(v:val)')
endif
return g:gocomplete_completions[1]
endif
endf
" vim:ts=4:sw=4:et
function! go#complete#ToggleAutoTypeInfo() abort
if get(g:, "go_auto_type_info", 0)
let g:go_auto_type_info = 0
call go#util#EchoProgress("auto type info disabled")
return
end
let g:go_auto_type_info = 1
call go#util#EchoProgress("auto type info enabled")
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,371 @@
let s:toggle = 0
" Buffer creates a new cover profile with 'go test -coverprofile' and changes
" the current buffers highlighting to show covered and uncovered sections of
" the code. If run again it clears the annotation.
function! go#coverage#BufferToggle(bang, ...) abort
if s:toggle
call go#coverage#Clear()
return
endif
if a:0 == 0
return call(function('go#coverage#Buffer'), [a:bang])
endif
return call(function('go#coverage#Buffer'), [a:bang] + a:000)
endfunction
" Buffer creates a new cover profile with 'go test -coverprofile' and changes
" teh current buffers highlighting to show covered and uncovered sections of
" the code. Calling it again reruns the tests and shows the last updated
" coverage.
function! go#coverage#Buffer(bang, ...) abort
" we use matchaddpos() which was introduce with 7.4.330, be sure we have
" it: http://ftp.vim.org/vim/patches/7.4/7.4.330
if !exists("*matchaddpos")
call go#util#EchoError("GoCoverage is supported with Vim version 7.4-330 or later")
return -1
endif
" check if there is any test file, if not we just return
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd . fnameescape(expand("%:p:h"))
if empty(glob("*_test.go"))
call go#util#EchoError("no test files available")
return
endif
finally
execute cd . fnameescape(dir)
endtry
let s:toggle = 1
let l:tmpname = tempname()
if go#util#has_job()
call s:coverage_job({
\ 'cmd': ['go', 'test', '-coverprofile', l:tmpname],
\ 'custom_cb': function('s:coverage_callback', [l:tmpname]),
\ 'bang': a:bang,
\ })
return
endif
let args = [a:bang, 0, "-coverprofile", l:tmpname]
if a:0
call extend(args, a:000)
endif
let disabled_term = 0
if get(g:, 'go_term_enabled')
let disabled_term = 1
let g:go_term_enabled = 0
endif
let id = call('go#cmd#Test', args)
if disabled_term
let g:go_term_enabled = 1
endif
if has('nvim')
call go#jobcontrol#AddHandler(function('s:coverage_handler'))
let s:coverage_handler_jobs[id] = l:tmpname
return
endif
if go#util#ShellError() == 0
call go#coverage#overlay(l:tmpname)
endif
call delete(l:tmpname)
endfunction
" Clear clears and resets the buffer annotation matches
function! go#coverage#Clear() abort
call clearmatches()
if exists("s:toggle") | let s:toggle = 0 | endif
" remove the autocmd we defined
if exists("#BufWinLeave#<buffer>")
autocmd! BufWinLeave <buffer>
endif
endfunction
" Browser creates a new cover profile with 'go test -coverprofile' and opens
" a new HTML coverage page from that profile in a new browser
function! go#coverage#Browser(bang, ...) abort
let l:tmpname = tempname()
if go#util#has_job()
call s:coverage_job({
\ 'cmd': ['go', 'test', '-coverprofile', l:tmpname],
\ 'custom_cb': function('s:coverage_browser_callback', [l:tmpname]),
\ 'bang': a:bang,
\ })
return
endif
let args = [a:bang, 0, "-coverprofile", l:tmpname]
if a:0
call extend(args, a:000)
endif
let id = call('go#cmd#Test', args)
if has('nvim')
call go#jobcontrol#AddHandler(function('s:coverage_browser_handler'))
let s:coverage_browser_handler_jobs[id] = l:tmpname
return
endif
if go#util#ShellError() == 0
let openHTML = 'go tool cover -html='.l:tmpname
call go#tool#ExecuteInDir(openHTML)
endif
call delete(l:tmpname)
endfunction
" Parses a single line from the cover file generated via go test -coverprofile
" and returns a single coverage profile block.
function! go#coverage#parsegocoverline(line) abort
" file:startline.col,endline.col numstmt count
let mx = '\([^:]\+\):\(\d\+\)\.\(\d\+\),\(\d\+\)\.\(\d\+\)\s\(\d\+\)\s\(\d\+\)'
let tokens = matchlist(a:line, mx)
let ret = {}
let ret.file = tokens[1]
let ret.startline = str2nr(tokens[2])
let ret.startcol = str2nr(tokens[3])
let ret.endline = str2nr(tokens[4])
let ret.endcol = str2nr(tokens[5])
let ret.numstmt = tokens[6]
let ret.cnt = tokens[7]
return ret
endfunction
" Generates matches to be added to matchaddpos for the given coverage profile
" block
function! go#coverage#genmatch(cov) abort
let color = 'goCoverageCovered'
if a:cov.cnt == 0
let color = 'goCoverageUncover'
endif
let matches = []
" if start and end are the same, also specify the byte length
" example: foo.go:92.2,92.65 1 0
if a:cov.startline == a:cov.endline
call add(matches, {
\ 'group': color,
\ 'pos': [[a:cov.startline, a:cov.startcol, a:cov.endcol - a:cov.startcol]],
\ 'priority': 2,
\ })
return matches
endif
" add start columns. Because we don't know the length of the of
" the line, we assume it is at maximum 200 bytes. I know this is hacky,
" but that's only way of fixing the issue
call add(matches, {
\ 'group': color,
\ 'pos': [[a:cov.startline, a:cov.startcol, 200]],
\ 'priority': 2,
\ })
" and then the remaining lines
let start_line = a:cov.startline
while start_line < a:cov.endline
let start_line += 1
call add(matches, {
\ 'group': color,
\ 'pos': [[start_line]],
\ 'priority': 2,
\ })
endwhile
" finally end columns
call add(matches, {
\ 'group': color,
\ 'pos': [[a:cov.endline, a:cov.endcol-1]],
\ 'priority': 2,
\ })
return matches
endfunction
" Reads the given coverprofile file and annotates the current buffer
function! go#coverage#overlay(file) abort
if !filereadable(a:file)
return
endif
let lines = readfile(a:file)
" cover mode, by default it's 'set'. Just here for debugging purposes
let mode = lines[0]
" contains matches for matchaddpos()
let matches = []
" first mark all lines as goCoverageNormalText. We use a custom group to not
" interfere with other buffers highlightings. Because the priority is
" lower than the cover and uncover matches, it'll be overriden.
let cnt = 1
while cnt <= line('$')
call add(matches, {'group': 'goCoverageNormalText', 'pos': [cnt], 'priority': 1})
let cnt += 1
endwhile
let fname = expand('%')
" when called for a _test.go file, run the coverage for the actuall file
" file
if fname =~# '^\f\+_test\.go$'
let l:root = split(fname, '_test.go$')[0]
let fname = l:root . ".go"
if !filereadable(fname)
call go#util#EchoError("couldn't find ".fname)
return
endif
" open the alternate file to show the coverage
exe ":edit ". fnamemodify(fname, ":p")
endif
" cov.file includes only the filename itself, without full path
let fname = fnamemodify(fname, ":t")
for line in lines[1:]
let cov = go#coverage#parsegocoverline(line)
" TODO(arslan): for now only include the coverage for the current
" buffer
if fname != fnamemodify(cov.file, ':t')
continue
endif
call extend(matches, go#coverage#genmatch(cov))
endfor
" clear the matches if we leave the buffer
autocmd BufWinLeave <buffer> call go#coverage#Clear()
for m in matches
call matchaddpos(m.group, m.pos)
endfor
endfunction
" ---------------------
" | Vim job callbacks |
" ---------------------
"
function s:coverage_job(args)
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let status_dir = expand('%:p:h')
function! s:error_info_cb(job, exit_status, data) closure
let status = {
\ 'desc': 'last status',
\ 'type': "coverage",
\ 'state': "finished",
\ }
if a:exit_status
let status.state = "failed"
endif
call go#statusline#Update(status_dir, status)
endfunction
let a:args.error_info_cb = funcref('s:error_info_cb')
let callbacks = go#job#Spawn(a:args)
let start_options = {
\ 'callback': callbacks.callback,
\ 'close_cb': callbacks.close_cb,
\ }
" modify GOPATH if needed
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" pre start
let dir = getcwd()
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let jobdir = fnameescape(expand("%:p:h"))
execute cd . jobdir
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': "coverage",
\ 'state': "started",
\})
call job_start(a:args.cmd, start_options)
" post start
execute cd . fnameescape(dir)
let $GOPATH = old_gopath
endfunction
" coverage_callback is called when the coverage execution is finished
function! s:coverage_callback(coverfile, job, exit_status, data)
if a:exit_status == 0
call go#coverage#overlay(a:coverfile)
endif
call delete(a:coverfile)
endfunction
function! s:coverage_browser_callback(coverfile, job, exit_status, data)
if a:exit_status == 0
let openHTML = 'go tool cover -html='.a:coverfile
call go#tool#ExecuteInDir(openHTML)
endif
call delete(a:coverfile)
endfunction
" -----------------------
" | Neovim job handlers |
" -----------------------
let s:coverage_handler_jobs = {}
let s:coverage_browser_handler_jobs = {}
function! s:coverage_handler(job, exit_status, data) abort
if !has_key(s:coverage_handler_jobs, a:job.id)
return
endif
let l:tmpname = s:coverage_handler_jobs[a:job.id]
if a:exit_status == 0
call go#coverage#overlay(l:tmpname)
endif
call delete(l:tmpname)
unlet s:coverage_handler_jobs[a:job.id]
endfunction
function! s:coverage_browser_handler(job, exit_status, data) abort
if !has_key(s:coverage_browser_handler_jobs, a:job.id)
return
endif
let l:tmpname = s:coverage_browser_handler_jobs[a:job.id]
if a:exit_status == 0
let openHTML = 'go tool cover -html='.l:tmpname
call go#tool#ExecuteInDir(openHTML)
endif
call delete(l:tmpname)
unlet s:coverage_browser_handler_jobs[a:job.id]
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,106 +1,319 @@
if !exists("g:go_godef_bin")
let g:go_godef_bin = "godef"
endif
let s:go_stack = []
let s:go_stack_level = 0
function! go#def#Jump(mode) abort
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" modified and improved version of vim-godef
function! go#def#Jump(...)
if !len(a:000)
" gives us the offset of the word, basicall the position of the word under
" he cursor
let arg = s:getOffset()
else
let arg = a:1
endif
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
let bin_path = go#tool#BinPath(g:go_godef_bin)
if empty(bin_path)
return
endif
" so guru right now is slow for some people. previously we were using
" godef which also has it's own quirks. But this issue come up so many
" times I've decided to support both. By default we still use guru as it
" covers all edge cases, but now anyone can switch to godef if they wish
let bin_name = get(g:, 'go_def_mode', 'guru')
if bin_name == 'godef'
if &modified
" Write current unsaved buffer to a temp file and use the modified content
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
let fname = l:tmpname
endif
let command = bin_path . " -f=" . expand("%:p") . " -i " . shellescape(arg)
let bin_path = go#path#CheckBinPath("godef")
if empty(bin_path)
let $GOPATH = old_gopath
return
endif
let command = printf("%s -f=%s -o=%s -t", bin_path, fname, go#util#OffsetCursor())
let out = go#util#System(command)
if exists("l:tmpname")
call delete(l:tmpname)
endif
elseif bin_name == 'guru'
let bin_path = go#path#CheckBinPath("guru")
if empty(bin_path)
let $GOPATH = old_gopath
return
endif
" get output of godef
let out=system(command, join(getbufline(bufnr('%'), 1, '$'), "\n"))
let cmd = [bin_path]
let stdin_content = ""
" jump to it
call s:godefJump(out, "")
if &modified
let content = join(go#util#GetLines(), "\n")
let stdin_content = fname . "\n" . strlen(content) . "\n" . content
call add(cmd, "-modified")
endif
if exists('g:go_guru_tags')
let tags = get(g:, 'go_guru_tags')
call extend(cmd, ["-tags", tags])
endif
let fname = fname.':#'.go#util#OffsetCursor()
call extend(cmd, ["definition", fname])
if go#util#has_job()
let l:spawn_args = {
\ 'cmd': cmd,
\ 'custom_cb': function('s:jump_to_declaration_cb', [a:mode, bin_name]),
\ }
if &modified
let l:spawn_args.input = stdin_content
endif
call go#util#EchoProgress("searching declaration ...")
call s:def_job(spawn_args)
return
endif
let command = join(cmd, " ")
if &modified
let out = go#util#System(command, stdin_content)
else
let out = go#util#System(command)
endif
else
call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru]')
return
endif
if go#util#ShellError() != 0
call go#util#EchoError(out)
return
endif
call go#def#jump_to_declaration(out, a:mode, bin_name)
let $GOPATH = old_gopath
endfunction
function! s:jump_to_declaration_cb(mode, bin_name, job, exit_status, data) abort
if a:exit_status != 0
return
endif
function! go#def#JumpMode(mode)
let arg = s:getOffset()
let bin_path = go#tool#BinPath(g:go_godef_bin)
if empty(bin_path)
return
endif
let command = bin_path . " -f=" . expand("%:p") . " -i " . shellescape(arg)
" get output of godef
let out=system(command, join(getbufline(bufnr('%'), 1, '$'), "\n"))
call s:godefJump(out, a:mode)
call go#def#jump_to_declaration(a:data[0], a:mode, a:bin_name)
endfunction
function! go#def#jump_to_declaration(out, mode, bin_name) abort
let final_out = a:out
if a:bin_name == "godef"
" append the type information to the same line so our we can parse it.
" This makes it compatible with guru output.
let final_out = join(split(a:out, '\n'), ':')
endif
function! s:getOffset()
let pos = getpos(".")[1:2]
if &encoding == 'utf-8'
let offs = line2byte(pos[0]) + pos[1] - 2
else
let c = pos[1]
let buf = line('.') == 1 ? "" : (join(getline(1, pos[0] - 1), "\n") . "\n")
let buf .= c == 1 ? "" : getline(pos[0])[:c-2]
let offs = len(iconv(buf, &encoding, "utf-8"))
endif
" strip line ending
let out = split(final_out, go#util#LineEnding())[0]
if go#util#IsWin()
let parts = split(out, '\(^[a-zA-Z]\)\@<!:')
else
let parts = split(out, ':')
endif
let argOff = "-o=" . offs
return argOff
let filename = parts[0]
let line = parts[1]
let col = parts[2]
let ident = parts[3]
" Remove anything newer than the current position, just like basic
" vim tag support
if s:go_stack_level == 0
let s:go_stack = []
else
let s:go_stack = s:go_stack[0:s:go_stack_level-1]
endif
" increment the stack counter
let s:go_stack_level += 1
" push it on to the jumpstack
let stack_entry = {'line': line("."), 'col': col("."), 'file': expand('%:p'), 'ident': ident}
call add(s:go_stack, stack_entry)
" needed for restoring back user setting this is because there are two
" modes of switchbuf which we need based on the split mode
let old_switchbuf = &switchbuf
normal! m'
if filename != fnamemodify(expand("%"), ':p:gs?\\?/?')
" jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded
" and 3. there is buffer window number we switch to
if get(g:, 'go_def_reuse_buffer', 0) && bufloaded(filename) != 0 && bufwinnr(filename) != -1
" jumpt to existing buffer if it exists
execute bufwinnr(filename) . 'wincmd w'
else
if &modified
let cmd = 'hide edit'
else
let cmd = 'edit'
endif
if a:mode == "tab"
let &switchbuf = "usetab"
if bufloaded(filename) == 0
tab split
endif
elseif a:mode == "split"
split
elseif a:mode == "vsplit"
vsplit
endif
" open the file and jump to line and column
exec cmd filename
endif
endif
call cursor(line, col)
" also align the line to middle of the view
normal! zz
let &switchbuf = old_switchbuf
endfunction
function! go#def#SelectStackEntry() abort
let target_window = go#ui#GetReturnWindow()
if empty(target_window)
let target_window = winnr()
endif
function! s:godefJump(out, mode)
let old_errorformat = &errorformat
let &errorformat = "%f:%l:%c"
let highlighted_stack_entry = matchstr(getline("."), '^..\zs\(\d\+\)')
if !empty(highlighted_stack_entry)
execute target_window . "wincmd w"
call go#def#Stack(str2nr(highlighted_stack_entry))
endif
if a:out =~ 'godef: '
let out=substitute(a:out, '\n$', '', '')
echom out
else
let parts = split(a:out, ':')
" parts[0] contains filename
let fileName = parts[0]
" put the error format into location list so we can jump automatically to
" it
lgetexpr a:out
" needed for restoring back user setting this is because there are two
" modes of switchbuf which we need based on the split mode
let old_switchbuf = &switchbuf
if a:mode == "tab"
let &switchbuf = "usetab"
if bufloaded(fileName) == 0
tab split
endif
else
if a:mode == "split"
split
elseif a:mode == "vsplit"
vsplit
endif
endif
" jump to file now
ll 1
normal zz
let &switchbuf = old_switchbuf
end
let &errorformat = old_errorformat
call go#ui#CloseWindow()
endfunction
function! go#def#StackUI() abort
if len(s:go_stack) == 0
call go#util#EchoError("godef stack empty")
return
endif
let stackOut = ['" <Up>,<Down>:navigate <Enter>:jump <Esc>,q:exit']
let i = 0
while i < len(s:go_stack)
let entry = s:go_stack[i]
let prefix = ""
if i == s:go_stack_level
let prefix = ">"
else
let prefix = " "
endif
call add(stackOut, printf("%s %d %s|%d col %d|%s",
\ prefix, i+1, entry["file"], entry["line"], entry["col"], entry["ident"]))
let i += 1
endwhile
if s:go_stack_level == i
call add(stackOut, "> ")
endif
call go#ui#OpenWindow("GoDef Stack", stackOut, "godefstack")
noremap <buffer> <silent> <CR> :<C-U>call go#def#SelectStackEntry()<CR>
noremap <buffer> <silent> <Esc> :<C-U>call go#ui#CloseWindow()<CR>
noremap <buffer> <silent> q :<C-U>call go#ui#CloseWindow()<CR>
endfunction
function! go#def#StackClear(...) abort
let s:go_stack = []
let s:go_stack_level = 0
endfunction
function! go#def#StackPop(...) abort
if len(s:go_stack) == 0
call go#util#EchoError("godef stack empty")
return
endif
if s:go_stack_level == 0
call go#util#EchoError("at bottom of the godef stack")
return
endif
if !len(a:000)
let numPop = 1
else
let numPop = a:1
endif
let newLevel = str2nr(s:go_stack_level) - str2nr(numPop)
call go#def#Stack(newLevel + 1)
endfunction
function! go#def#Stack(...) abort
if len(s:go_stack) == 0
call go#util#EchoError("godef stack empty")
return
endif
if !len(a:000)
" Display interactive stack
call go#def#StackUI()
return
else
let jumpTarget = a:1
endif
if jumpTarget !~ '^\d\+$'
if jumpTarget !~ '^\s*$'
call go#util#EchoError("location must be a number")
endif
return
endif
let jumpTarget = str2nr(jumpTarget) - 1
if jumpTarget >= 0 && jumpTarget < len(s:go_stack)
let s:go_stack_level = jumpTarget
let target = s:go_stack[s:go_stack_level]
" jump
if expand('%:p') != target["file"]
if &modified
exec 'hide edit' target["file"]
else
exec 'edit' target["file"]
endif
endif
call cursor(target["line"], target["col"])
normal! zz
else
call go#util#EchoError("invalid location. Try :GoDefStack to see the list of valid entries")
endif
endfunction
function s:def_job(args) abort
function! s:error_info_cb(job, exit_status, data) closure
" do not print anything during async definition search&jump
endfunction
let a:args.error_info_cb = funcref('s:error_info_cb')
let callbacks = go#job#Spawn(a:args)
let start_options = {
\ 'callback': callbacks.callback,
\ 'close_cb': callbacks.close_cb,
\ }
if &modified
let l:tmpname = tempname()
call writefile(split(a:args.input, "\n"), l:tmpname, "b")
let l:start_options.in_io = "file"
let l:start_options.in_name = l:tmpname
endif
call job_start(a:args.cmd, start_options)
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,32 @@
func Test_jump_to_declaration_guru()
let file_name = "test-fixtures/def/jump.go"
let lnum = 5
let col = 6
let out = printf("%s:%d:%d: defined here as func main", file_name, lnum, col)
let bin_name = "guru"
call go#def#jump_to_declaration(out, "", bin_name)
call assert_equal(file_name, bufname("%"))
call assert_equal(lnum, getcurpos()[1])
call assert_equal(col, getcurpos()[2])
endfunc
func Test_jump_to_declaration_godef()
let file_name = "test-fixtures/def/jump.go"
let lnum = 5
let col = 6
" note that the output of godef has two lines
let out = printf("%s:%d:%d\ndefined here as func main", file_name, lnum, col)
let bin_name = "godef"
call go#def#jump_to_declaration(out, "", bin_name)
call assert_equal(file_name, bufname("%"))
call assert_equal(lnum, getcurpos()[1])
call assert_equal(col, getcurpos()[2])
endfunc
" vim: sw=2 ts=2 et

View File

@ -1,159 +1,224 @@
" Copyright 2011 The Go Authors. All rights reserved.
" Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file.
"
" godoc.vim: Vim command to see godoc.
"
"
" Commands:
"
" :GoDoc
"
" Open the relevant Godoc for either the word[s] passed to the command or
" the, by default, the word under the cursor.
"
" Options:
"
" g:go_godoc_commands [default=1]
"
" Flag to indicate whether to enable the commands listed above.
let s:buf_nr = -1
if !exists("g:go_doc_command")
let g:go_doc_command = "godoc"
let g:go_doc_command = "godoc"
endif
if !exists("g:go_doc_options")
let g:go_doc_options = ""
let g:go_doc_options = ""
endif
function! go#doc#OpenBrowser(...) abort
" check if we have gogetdoc as it gives us more and accurate information.
" Only supported if we have json_decode as it's not worth to parse the plain
" non-json output of gogetdoc
let bin_path = go#path#CheckBinPath('gogetdoc')
if !empty(bin_path) && exists('*json_decode')
let json_out = s:gogetdoc(1)
if go#util#ShellError() != 0
call go#util#EchoError(json_out)
return
endif
let out = json_decode(json_out)
if type(out) != type({})
call go#util#EchoError("gogetdoc output is malformed")
endif
let import = out["import"]
let name = out["name"]
let decl = out["decl"]
let godoc_url = "https://godoc.org/" . import
if decl !~ "^package"
let godoc_url .= "#" . name
endif
echo godoc_url
call go#tool#OpenBrowser(godoc_url)
return
endif
let pkgs = s:godocWord(a:000)
if empty(pkgs)
return
endif
let pkg = pkgs[0]
let exported_name = pkgs[1]
" example url: https://godoc.org/github.com/fatih/set#Set
let godoc_url = "https://godoc.org/" . pkg . "#" . exported_name
call go#tool#OpenBrowser(godoc_url)
endfunction
function! go#doc#Open(newmode, mode, ...) abort
if len(a:000)
" check if we have 'godoc' and use it automatically
let bin_path = go#path#CheckBinPath('godoc')
if empty(bin_path)
return
endif
let command = printf("%s %s", bin_path, join(a:000, ' '))
let out = go#util#System(command)
else
let out = s:gogetdoc(0)
endif
if go#util#ShellError() != 0
call go#util#EchoError(out)
return
endif
call s:GodocView(a:newmode, a:mode, out)
endfunction
function! s:GodocView(newposition, position, content) abort
" reuse existing buffer window if it exists otherwise create a new one
if !bufexists(s:buf_nr)
execute a:newposition
sil file `="[Godoc]"`
let s:buf_nr = bufnr('%')
elseif bufwinnr(s:buf_nr) == -1
execute a:position
execute s:buf_nr . 'buffer'
elseif bufwinnr(s:buf_nr) != bufwinnr('%')
execute bufwinnr(s:buf_nr) . 'wincmd w'
endif
if a:position == "split"
" cap buffer height to 20, but resize it for smaller contents
let max_height = 20
let content_height = len(split(a:content, "\n"))
if content_height > max_height
exe 'resize ' . max_height
else
exe 'resize ' . content_height
endif
else
" set a sane maximum width for vertical splits. In this case the minimum
" that fits the godoc for package http without extra linebreaks and line
" numbers on
exe 'vertical resize 84'
endif
setlocal filetype=godoc
setlocal bufhidden=delete
setlocal buftype=nofile
setlocal noswapfile
setlocal nobuflisted
setlocal nocursorline
setlocal nocursorcolumn
setlocal iskeyword+=:
setlocal iskeyword-=-
setlocal modifiable
%delete _
call append(0, split(a:content, "\n"))
sil $delete _
setlocal nomodifiable
sil normal! gg
" close easily with <esc> or enter
noremap <buffer> <silent> <CR> :<C-U>close<CR>
noremap <buffer> <silent> <Esc> :<C-U>close<CR>
endfunction
function! s:gogetdoc(json) abort
" check if we have 'gogetdoc' and use it automatically
let bin_path = go#path#CheckBinPath('gogetdoc')
if empty(bin_path)
return -1
endif
let cmd = [bin_path]
let offset = go#util#OffsetCursor()
let fname = expand("%:p:gs!\\!/!")
let pos = shellescape(fname.':#'.offset)
let cmd += ["-pos", pos]
if a:json
let cmd += ["-json"]
endif
let command = join(cmd, " ")
if &modified
" gogetdoc supports the same archive format as guru for dealing with
" modified buffers.
" use the -modified flag
" write each archive entry on stdin as:
" filename followed by newline
" file size followed by newline
" file contents
let in = ""
let content = join(go#util#GetLines(), "\n")
let in = fname . "\n" . strlen(content) . "\n" . content
let command .= " -modified"
let out = go#util#System(command, in)
else
let out = go#util#System(command)
endif
return out
endfunction
" returns the package and exported name. exported name might be empty.
" ie: fmt and Println
" ie: github.com/fatih/set and New
function! s:godocWord(args)
if !executable('godoc')
echohl WarningMsg
echo "godoc command not found."
echo " install with: go get code.google.com/p/go.tools/cmd/godoc"
echohl None
return []
endif
function! s:godocWord(args) abort
if !executable('godoc')
let msg = "godoc command not found."
let msg .= " install with: go get golang.org/x/tools/cmd/godoc"
call go#util#EchoWarning(msg)
return []
endif
if !len(a:args)
let oldiskeyword = &iskeyword
setlocal iskeyword+=.
let word = expand('<cword>')
let &iskeyword = oldiskeyword
let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
let words = split(word, '\.\ze[^./]\+$')
else
let words = a:args
endif
if !len(a:args)
let oldiskeyword = &iskeyword
setlocal iskeyword+=.
let word = expand('<cword>')
let &iskeyword = oldiskeyword
let word = substitute(word, '[^a-zA-Z0-9\\/._~-]', '', 'g')
let words = split(word, '\.\ze[^./]\+$')
else
let words = a:args
endif
if !len(words)
return []
endif
if !len(words)
return []
endif
let pkg = words[0]
if len(words) == 1
let exported_name = ""
else
let exported_name = words[1]
endif
let pkg = words[0]
if len(words) == 1
let exported_name = ""
else
let exported_name = words[1]
endif
let packages = go#tool#Imports()
let packages = go#tool#Imports()
if has_key(packages, pkg)
let pkg = packages[pkg]
endif
if has_key(packages, pkg)
let pkg = packages[pkg]
endif
return [pkg, exported_name]
return [pkg, exported_name]
endfunction
function! go#doc#OpenBrowser(...)
let pkgs = s:godocWord(a:000)
if empty(pkgs)
return
endif
function! s:godocNotFound(content) abort
if len(a:content) == 0
return 1
endif
let pkg = pkgs[0]
let exported_name = pkgs[1]
" example url: https://godoc.org/github.com/fatih/set#Set
let godoc_url = "https://godoc.org/" . pkg . "#" . exported_name
call go#tool#OpenBrowser(godoc_url)
endfunction
function! go#doc#Open(mode, ...)
let pkgs = s:godocWord(a:000)
if empty(pkgs)
return
endif
let pkg = pkgs[0]
let exported_name = pkgs[1]
let command = g:go_doc_command . ' ' . g:go_doc_options . ' ' . pkg
silent! let content = system(command)
if v:shell_error || !len(content)
echo 'No documentation found for "' . pkg . '".'
return -1
endif
call s:GodocView(a:mode, content)
" jump to the specified name
if search('^func ' . exported_name . '(')
silent! normal zt
return -1
endif
if search('^type ' . exported_name)
silent! normal zt
return -1
endif
if search('^\%(const\|var\|type\|\s\+\) ' . pkg . '\s\+=\s')
silent! normal zt
return -1
endif
" nothing found, jump to top
silent! normal gg
endfunction
function! s:GodocView(position, content)
" reuse existing buffer window if it exists otherwise create a new one
if !bufexists(s:buf_nr)
execute a:position
file `="[Godoc]"`
let s:buf_nr = bufnr('%')
elseif bufwinnr(s:buf_nr) == -1
execute a:position
execute s:buf_nr . 'buffer'
elseif bufwinnr(s:buf_nr) != bufwinnr('%')
execute bufwinnr(s:buf_nr) . 'wincmd w'
endif
setlocal filetype=godoc
setlocal bufhidden=delete
setlocal buftype=nofile
setlocal noswapfile
setlocal nobuflisted
setlocal nocursorline
setlocal nocursorcolumn
setlocal iskeyword+=:
setlocal iskeyword-=-
setlocal modifiable
%delete _
call append(0, split(a:content, "\n"))
$delete _
setlocal nomodifiable
return a:content =~# '^.*: no such file or directory\n$'
endfunction
" vim:ts=4:sw=4:et
" vim: sw=2 ts=2 et

View File

@ -1,36 +0,0 @@
if !exists("g:go_errcheck_bin")
let g:go_errcheck_bin = "errcheck"
endif
function! go#errcheck#Run() abort
let bin_path = go#tool#BinPath(g:go_errcheck_bin)
if empty(bin_path)
return
endif
let out = system(bin_path . ' ' . shellescape(expand('%:p:h')))
if v:shell_error
let errors = []
let mx = '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)'
for line in split(out, '\n')
let tokens = matchlist(line, mx)
if !empty(tokens)
call add(errors, {"filename": tokens[1],
\"lnum": tokens[2],
\"col": tokens[3],
\"text": tokens[4]})
endif
endfor
if empty(errors)
% | " Couldn't detect error format, output errors
endif
if !empty(errors)
call setqflist(errors, 'r')
endif
echohl Error | echomsg "GoErrCheck returned error" | echohl None
else
call setqflist([])
endif
cwindow
endfunction

View File

@ -2,46 +2,30 @@
" Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file.
"
" fmt.vim: Vim command to format Go files with gofmt.
"
" This filetype plugin add a new commands for go buffers:
"
" :Fmt
"
" Filter the current Go buffer through gofmt.
" It tries to preserve cursor position and avoids
" replacing the buffer with stderr output.
"
" Options:
"
" g:go_fmt_command [default="gofmt"]
"
" Flag naming the gofmt executable to use.
"
" g:go_fmt_autosave [default=1]
"
" Flag to auto call :Fmt when saved file
"
" fmt.vim: Vim command to format Go files with gofmt (and gofmt compatible
" toorls, such as goimports).
if !exists("g:go_fmt_command")
let g:go_fmt_command = "gofmt"
let g:go_fmt_command = "gofmt"
endif
if !exists("g:go_goimports_bin")
let g:go_goimports_bin = "goimports"
let g:go_goimports_bin = "goimports"
endif
if !exists('g:go_fmt_fail_silently')
let g:go_fmt_fail_silently = 0
let g:go_fmt_fail_silently = 0
endif
if !exists('g:go_fmt_options')
let g:go_fmt_options = ''
let g:go_fmt_options = ''
endif
let s:got_fmt_error = 0
if !exists("g:go_fmt_experimental")
let g:go_fmt_experimental = 0
endif
" we have those problems :
" we have those problems :
" http://stackoverflow.com/questions/12741977/prevent-vim-from-updating-its-undo-tree
" http://stackoverflow.com/questions/18532692/golang-formatter-and-vim-how-to-destroy-history-record?rq=1
"
@ -49,93 +33,211 @@ let s:got_fmt_error = 0
" it doesn't undo changes and break undo history. If you are here reading
" this and have VimL experience, please look at the function for
" improvements, patches are welcome :)
function! go#fmt#Format(withGoimport)
" save cursor position and many other things
let l:curw=winsaveview()
" needed for testing if gofmt fails or not
let l:tmpname=tempname()
call writefile(getline(1,'$'), l:tmpname)
function! go#fmt#Format(withGoimport) abort
if g:go_fmt_experimental == 1
" Using winsaveview to save/restore cursor state has the problem of
" closing folds on save:
" https://github.com/fatih/vim-go/issues/502
" One fix is to use mkview instead. Unfortunately, this sometimes causes
" other bad side effects:
" https://github.com/fatih/vim-go/issues/728
" and still closes all folds if foldlevel>0:
" https://github.com/fatih/vim-go/issues/732
let l:curw = {}
try
mkview!
catch
let l:curw = winsaveview()
endtry
" save our undo file to be restored after we are done. This is needed to
" prevent an additional undo jump due to BufWritePre auto command and also
" restore 'redo' history because it's getting being destroyed every
" BufWritePre
let tmpundofile=tempname()
let tmpundofile = tempname()
exe 'wundo! ' . tmpundofile
else
" Save cursor position and many other things.
let l:curw = winsaveview()
endif
" get the command first so we can test it
let fmt_command = g:go_fmt_command
if a:withGoimport == 1
" check if the user has installed goimports
let bin_path = go#tool#BinPath(g:go_goimports_bin)
if empty(bin_path)
return
endif
" Write current unsaved buffer to a temp file
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
if go#util#IsWin()
let l:tmpname = tr(l:tmpname, '\', '/')
endif
let fmt_command = bin_path
endif
let bin_name = g:go_fmt_command
if a:withGoimport == 1
let bin_name = g:go_goimports_bin
endif
" populate the final command with user based fmt options
let command = fmt_command . ' ' . g:go_fmt_options
let out = go#fmt#run(bin_name, l:tmpname, expand('%'))
if go#util#ShellError() == 0
call go#fmt#update_file(l:tmpname, expand('%'))
elseif g:go_fmt_fail_silently == 0
let errors = s:parse_errors(expand('%'), out)
call s:show_errors(errors)
endif
" execute our command...
let out = system(command . " " . l:tmpname)
"if there is no error on the temp file, gofmt again our original file
if v:shell_error == 0
" remove undo point caused via BufWritePre
try | silent undojoin | catch | endtry
" do not include stderr to the buffer, this is due to goimports/gofmt
" tha fails with a zero exit return value (sad yeah).
let default_srr = &srr
set srr=>%s
" execufe gofmt on the current buffer and replace it
silent execute "%!" . command
" only clear quickfix if it was previously set, this prevents closing
" other quickfixes
if s:got_fmt_error
let s:got_fmt_error = 0
call setqflist([])
cwindow
endif
" put back the users srr setting
let &srr = default_srr
elseif g:go_fmt_fail_silently == 0
"otherwise get the errors and put them to quickfix window
let errors = []
for line in split(out, '\n')
let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)')
if !empty(tokens)
call add(errors, {"filename": @%,
\"lnum": tokens[2],
\"col": tokens[3],
\"text": tokens[4]})
endif
endfor
if empty(errors)
% | " Couldn't detect gofmt error format, output errors
endif
if !empty(errors)
call setqflist(errors, 'r')
echohl Error | echomsg "Gofmt returned error" | echohl None
endif
let s:got_fmt_error = 1
cwindow
endif
" We didn't use the temp file, so clean up
call delete(l:tmpname)
if g:go_fmt_experimental == 1
" restore our undo history
silent! exe 'rundo ' . tmpundofile
call delete(tmpundofile)
" restore our cursor/windows positions
call delete(l:tmpname)
" Restore our cursor/windows positions, folds, etc.
if empty(l:curw)
silent! loadview
else
call winrestview(l:curw)
endif
else
" Restore our cursor/windows positions.
call winrestview(l:curw)
endif
endfunction
" update_file updates the target file with the given formatted source
function! go#fmt#update_file(source, target)
" remove undo point caused via BufWritePre
try | silent undojoin | catch | endtry
" vim:ts=4:sw=4:et
let old_fileformat = &fileformat
if exists("*getfperm")
" save file permissions
let original_fperm = getfperm(a:target)
endif
call rename(a:source, a:target)
" restore file permissions
if exists("*setfperm") && original_fperm != ''
call setfperm(a:target , original_fperm)
endif
" reload buffer to reflect latest changes
silent! edit!
let &fileformat = old_fileformat
let &syntax = &syntax
" clean up previous location list
let l:listtype = "locationlist"
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
endfunction
" run runs the gofmt/goimport command for the given source file and returns
" the the output of the executed command. Target is the real file to be
" formated.
function! go#fmt#run(bin_name, source, target)
let cmd = s:fmt_cmd(a:bin_name, a:source, a:target)
if cmd[0] == "goimports"
" change GOPATH too, so goimports can pick up the correct library
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
endif
let command = join(cmd, " ")
" execute our command...
let out = go#util#System(command)
if cmd[0] == "goimports"
let $GOPATH = old_gopath
endif
return out
endfunction
" fmt_cmd returns a dict that contains the command to execute gofmt (or
" goimports). args is dict with
function! s:fmt_cmd(bin_name, source, target)
" check if the user has installed command binary.
" For example if it's goimports, let us check if it's installed,
" if not the user get's a warning via go#path#CheckBinPath()
let bin_path = go#path#CheckBinPath(a:bin_name)
if empty(bin_path)
return
endif
" start constructing the command
let cmd = [bin_path]
call add(cmd, "-w")
call extend(cmd, split(g:go_fmt_options, " "))
if a:bin_name == "goimports"
" lazy check if goimports support `-srcdir`. We should eventually remove
" this in the future
if !exists('b:goimports_vendor_compatible')
let out = go#util#System(bin_path . " --help")
if out !~ "-srcdir"
call go#util#EchoWarning(printf("vim-go: goimports (%s) does not support srcdir. Update with: :GoUpdateBinaries", bin_path))
else
let b:goimports_vendor_compatible = 1
endif
endif
if exists('b:goimports_vendor_compatible') && b:goimports_vendor_compatible
let ssl_save = &shellslash
set noshellslash
call extend(cmd, ["-srcdir", shellescape(fnamemodify(a:target, ":p"))])
let &shellslash = ssl_save
endif
endif
call add(cmd, a:source)
return cmd
endfunction
" parse_errors parses the given errors and returns a list of parsed errors
function! s:parse_errors(filename, content) abort
let splitted = split(a:content, '\n')
" list of errors to be put into location list
let errors = []
for line in splitted
let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)')
if !empty(tokens)
call add(errors,{
\"filename": a:filename,
\"lnum": tokens[2],
\"col": tokens[3],
\"text": tokens[4],
\ })
endif
endfor
return errors
endfunction
" show_errors opens a location list and shows the given errors. If the given
" errors is empty, it closes the the location list
function! s:show_errors(errors) abort
let l:listtype = "locationlist"
if !empty(a:errors)
call go#list#Populate(l:listtype, a:errors, 'Format')
echohl Error | echomsg "Gofmt returned error" | echohl None
endif
" this closes the window if there are no errors or it opens
" it if there is any
call go#list#Window(l:listtype, len(a:errors))
endfunction
function! go#fmt#ToggleFmtAutoSave() abort
if get(g:, "go_fmt_autosave", 1)
let g:go_fmt_autosave = 0
call go#util#EchoProgress("auto fmt disabled")
return
end
let g:go_fmt_autosave = 1
call go#util#EchoProgress("auto fmt enabled")
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,31 @@
func Test_run_fmt()
let actual_file = tempname()
call writefile(readfile("test-fixtures/fmt/hello.go"), actual_file)
let expected = join(readfile("test-fixtures/fmt/hello_golden.go"), "\n")
" run our code
call go#fmt#run("gofmt", actual_file, "test-fixtures/fmt/hello.go")
" this should now contain the formatted code
let actual = join(readfile(actual_file), "\n")
call assert_equal(expected, actual)
endfunc
func Test_update_file()
let expected = join(readfile("test-fixtures/fmt/hello_golden.go"), "\n")
let source_file = tempname()
call writefile(readfile("test-fixtures/fmt/hello_golden.go"), source_file)
let target_file = tempname()
call writefile([""], target_file)
" update_file now
call go#fmt#update_file(source_file, target_file)
" this should now contain the formatted code
let actual = join(readfile(target_file), "\n")
call assert_equal(expected, actual)
endfunc

View File

@ -0,0 +1,622 @@
" guru.vim -- Vim integration for the Go guru.
" guru_cmd returns a dict that contains the command to execute guru. args
" is dict with following options:
" mode : guru mode, such as 'implements'
" format : output format, either 'plain' or 'json'
" needs_scope : if 1, adds the current package to the scope
" selected : if 1, means it's a range of selection, otherwise it picks up the
" offset under the cursor
" example output:
" {'cmd' : ['guru', '-json', 'implements', 'demo/demo.go:#66']}
function! s:guru_cmd(args) range abort
let mode = a:args.mode
let format = a:args.format
let needs_scope = a:args.needs_scope
let selected = a:args.selected
let result = {}
let dirname = expand('%:p:h')
let pkg = go#package#ImportPath(dirname)
" this is important, check it!
if pkg == -1 && needs_scope
return {'err': "current directory is not inside of a valid GOPATH"}
endif
"return with a warning if the binary doesn't exist
let bin_path = go#path#CheckBinPath("guru")
if empty(bin_path)
return {'err': "bin path not found"}
endif
" start constructing the command
let cmd = [bin_path]
let filename = fnamemodify(expand("%"), ':p:gs?\\?/?')
if &modified
let content = join(go#util#GetLines(), "\n")
let result.stdin_content = filename . "\n" . strlen(content) . "\n" . content
call add(cmd, "-modified")
endif
" enable outputting in json format
if format == "json"
call add(cmd, "-json")
endif
" check for any tags
if exists('g:go_build_tags')
let tags = get(g:, 'go_build_tags')
call extend(cmd, ["-tags", tags])
let result.tags = tags
endif
" some modes require scope to be defined (such as callers). For these we
" choose a sensible setting, which is using the current file's package
let scopes = []
if needs_scope
let scopes = [pkg]
endif
" check for any user defined scope setting. users can define the scope,
" in package pattern form. examples:
" golang.org/x/tools/cmd/guru # a single package
" golang.org/x/tools/... # all packages beneath dir
" ... # the entire workspace.
if exists('g:go_guru_scope')
" check that the setting is of type list
if type(get(g:, 'go_guru_scope')) != type([])
return {'err' : "go_guru_scope should of type list"}
endif
let scopes = get(g:, 'go_guru_scope')
endif
" now add the scope to our command if there is any
if !empty(scopes)
" strip trailing slashes for each path in scoped. bug:
" https://github.com/golang/go/issues/14584
let scopes = go#util#StripTrailingSlash(scopes)
" create shell-safe entries of the list
if !go#util#has_job() | let scopes = go#util#Shelllist(scopes) | endif
" guru expect a comma-separated list of patterns, construct it
let l:scope = join(scopes, ",")
let result.scope = l:scope
call extend(cmd, ["-scope", l:scope])
endif
let pos = printf("#%s", go#util#OffsetCursor())
if selected != -1
" means we have a range, get it
let pos1 = go#util#Offset(line("'<"), col("'<"))
let pos2 = go#util#Offset(line("'>"), col("'>"))
let pos = printf("#%s,#%s", pos1, pos2)
endif
let filename .= ':'.pos
call extend(cmd, [mode, filename])
let result.cmd = cmd
return result
endfunction
" sync_guru runs guru in sync mode with the given arguments
function! s:sync_guru(args) abort
let result = s:guru_cmd(a:args)
if has_key(result, 'err')
call go#util#EchoError(result.err)
return -1
endif
if !has_key(a:args, 'disable_progress')
if a:args.needs_scope
call go#util#EchoProgress("analysing with scope ". result.scope . " ...")
elseif a:args.mode !=# 'what'
" the query might take time, let us give some feedback
call go#util#EchoProgress("analysing ...")
endif
endif
" run, forrest run!!!
let command = join(result.cmd, " ")
if has_key(result, 'stdin_content')
let out = go#util#System(command, result.stdin_content)
else
let out = go#util#System(command)
endif
if has_key(a:args, 'custom_parse')
call a:args.custom_parse(go#util#ShellError(), out)
else
call s:parse_guru_output(go#util#ShellError(), out, a:args.mode)
endif
return out
endfunc
" async_guru runs guru in async mode with the given arguments
function! s:async_guru(args) abort
let result = s:guru_cmd(a:args)
if has_key(result, 'err')
call go#util#EchoError(result.err)
return
endif
let status_dir = expand('%:p:h')
let statusline_type = printf("%s", a:args.mode)
if !has_key(a:args, 'disable_progress')
if a:args.needs_scope
call go#util#EchoProgress("analysing with scope ". result.scope . " ...")
endif
endif
function! s:close_cb(chan) closure
let messages = []
while ch_status(a:chan, {'part': 'out'}) == 'buffered'
let msg = ch_read(a:chan, {'part': 'out'})
call add(messages, msg)
endwhile
while ch_status(a:chan, {'part': 'err'}) == 'buffered'
let msg = ch_read(a:chan, {'part': 'err'})
call add(messages, msg)
endwhile
let l:job = ch_getjob(a:chan)
let l:info = job_info(l:job)
let out = join(messages, "\n")
let status = {
\ 'desc': 'last status',
\ 'type': statusline_type,
\ 'state': "finished",
\ }
if l:info.exitval
let status.state = "failed"
endif
call go#statusline#Update(status_dir, status)
if has_key(a:args, 'custom_parse')
call a:args.custom_parse(l:info.exitval, out)
else
call s:parse_guru_output(l:info.exitval, out, a:args.mode)
endif
endfunction
let start_options = {
\ 'close_cb': funcref("s:close_cb"),
\ }
if has_key(result, 'stdin_content')
let l:tmpname = tempname()
call writefile(split(result.stdin_content, "\n"), l:tmpname, "b")
let l:start_options.in_io = "file"
let l:start_options.in_name = l:tmpname
endif
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': statusline_type,
\ 'state': "analysing",
\})
return job_start(result.cmd, start_options)
endfunc
" run_guru runs the given guru argument
function! s:run_guru(args) abort
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
if go#util#has_job()
let res = s:async_guru(a:args)
else
let res = s:sync_guru(a:args)
endif
let $GOPATH = old_gopath
return res
endfunction
" Show 'implements' relation for selected package
function! go#guru#Implements(selected) abort
let args = {
\ 'mode': 'implements',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(args)
endfunction
" Report the possible constants, global variables, and concrete types that may
" appear in a value of type error
function! go#guru#Whicherrs(selected) abort
let args = {
\ 'mode': 'whicherrs',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
" TODO(arslan): handle empty case for both sync/async
" if empty(out.out)
" call go#util#EchoSuccess("no error variables found. Try to change the scope with :GoGuruScope")
" return
" endif
call s:run_guru(args)
endfunction
" Describe selected syntax: definition, methods, etc
function! go#guru#Describe(selected) abort
let args = {
\ 'mode': 'describe',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(args)
endfunction
function! go#guru#DescribeInfo() abort
" json_encode() and friends are introduced with this patch (7.4.1304)
" vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
" nvim: https://github.com/neovim/neovim/pull/4131
if !exists("*json_decode")
call go#util#EchoError("requires 'json_decode'. Update your Vim/Neovim version.")
return
endif
function! s:info(exit_val, output)
if a:exit_val != 0
return
endif
if a:output[0] !=# '{'
return
endif
if empty(a:output) || type(a:output) != type("")
return
endif
let result = json_decode(a:output)
if type(result) != type({})
call go#util#EchoError(printf("malformed output from guru: %s", a:output))
return
endif
if !has_key(result, 'detail')
" if there is no detail check if there is a description and print it
if has_key(result, "desc")
call go#util#EchoInfo(result["desc"])
return
endif
call go#util#EchoError("detail key is missing. Please open a bug report on vim-go repo.")
return
endif
let detail = result['detail']
let info = ""
" guru gives different information based on the detail mode. Let try to
" extract the most useful information
if detail == "value"
if !has_key(result, 'value')
call go#util#EchoError("value key is missing. Please open a bug report on vim-go repo.")
return
endif
let val = result["value"]
if !has_key(val, 'type')
call go#util#EchoError("type key is missing (value.type). Please open a bug report on vim-go repo.")
return
endif
let info = val["type"]
elseif detail == "type"
if !has_key(result, 'type')
call go#util#EchoError("type key is missing. Please open a bug report on vim-go repo.")
return
endif
let type = result["type"]
if !has_key(type, 'type')
call go#util#EchoError("type key is missing (type.type). Please open a bug report on vim-go repo.")
return
endif
let info = type["type"]
elseif detail == "package"
if !has_key(result, 'package')
call go#util#EchoError("package key is missing. Please open a bug report on vim-go repo.")
return
endif
let package = result["package"]
if !has_key(package, 'path')
call go#util#EchoError("path key is missing (package.path). Please open a bug report on vim-go repo.")
return
endif
let info = printf("package %s", package["path"])
elseif detail == "unknown"
let info = result["desc"]
else
call go#util#EchoError(printf("unknown detail mode found '%s'. Please open a bug report on vim-go repo", detail))
return
endif
call go#util#EchoInfo(info)
endfunction
let args = {
\ 'mode': 'describe',
\ 'format': 'json',
\ 'selected': -1,
\ 'needs_scope': 1,
\ 'custom_parse': function('s:info'),
\ 'disable_progress': 1,
\ }
call s:run_guru(args)
endfunction
" Show possible targets of selected function call
function! go#guru#Callees(selected) abort
let args = {
\ 'mode': 'callees',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(args)
endfunction
" Show possible callers of selected function
function! go#guru#Callers(selected) abort
let args = {
\ 'mode': 'callers',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(args)
endfunction
" Show path from callgraph root to selected function
function! go#guru#Callstack(selected) abort
let args = {
\ 'mode': 'callstack',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(args)
endfunction
" Show free variables of selection
function! go#guru#Freevars(selected) abort
" Freevars requires a selection
if a:selected == -1
call go#util#EchoError("GoFreevars requires a selection (range) of code")
return
endif
let args = {
\ 'mode': 'freevars',
\ 'format': 'plain',
\ 'selected': 1,
\ 'needs_scope': 0,
\ }
call s:run_guru(args)
endfunction
" Show send/receive corresponding to selected channel op
function! go#guru#ChannelPeers(selected) abort
let args = {
\ 'mode': 'peers',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(args)
endfunction
" Show all refs to entity denoted by selected identifier
function! go#guru#Referrers(selected) abort
let args = {
\ 'mode': 'referrers',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 0,
\ }
call s:run_guru(args)
endfunction
function! go#guru#SameIdsTimer() abort
call timer_start(200, function('go#guru#SameIds'), {'repeat': -1})
endfunction
function! go#guru#SameIds() abort
" we use matchaddpos() which was introduce with 7.4.330, be sure we have
" it: http://ftp.vim.org/vim/patches/7.4/7.4.330
if !exists("*matchaddpos")
call go#util#EchoError("GoSameIds requires 'matchaddpos'. Update your Vim/Neovim version.")
return
endif
" json_encode() and friends are introduced with this patch (7.4.1304)
" vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
" nvim: https://github.com/neovim/neovim/pull/4131
if !exists("*json_decode")
call go#util#EchoError("GoSameIds requires 'json_decode'. Update your Vim/Neovim version.")
return
endif
let args = {
\ 'mode': 'what',
\ 'format': 'json',
\ 'selected': -1,
\ 'needs_scope': 0,
\ 'custom_parse': function('s:same_ids_highlight'),
\ }
call s:run_guru(args)
endfunction
function! s:same_ids_highlight(exit_val, output) abort
call go#guru#ClearSameIds() " run after calling guru to reduce flicker.
if a:output[0] !=# '{'
if !get(g:, 'go_auto_sameids', 0)
call go#util#EchoError(a:output)
endif
return
endif
let result = json_decode(a:output)
if type(result) != type({}) && !get(g:, 'go_auto_sameids', 0)
call go#util#EchoError("malformed output from guru")
return
endif
if !has_key(result, 'sameids')
if !get(g:, 'go_auto_sameids', 0)
call go#util#EchoError("no same_ids founds for the given identifier")
endif
return
endif
let poslen = 0
for enclosing in result['enclosing']
if enclosing['desc'] == 'identifier'
let poslen = enclosing['end'] - enclosing['start']
break
endif
endfor
" return when there's no identifier to highlight.
if poslen == 0
return
endif
let same_ids = result['sameids']
" highlight the lines
for item in same_ids
let pos = split(item, ':')
call matchaddpos('goSameId', [[str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]])
endfor
if get(g:, "go_auto_sameids", 0)
" re-apply SameIds at the current cursor position at the time the buffer
" is redisplayed: e.g. :edit, :GoRename, etc.
autocmd BufWinEnter <buffer> nested call go#guru#SameIds()
endif
endfunction
function! go#guru#ClearSameIds() abort
let m = getmatches()
for item in m
if item['group'] == 'goSameId'
call matchdelete(item['id'])
endif
endfor
" remove the autocmds we defined
if exists("#BufWinEnter#<buffer>")
autocmd! BufWinEnter <buffer>
endif
endfunction
function! go#guru#ToggleSameIds() abort
if len(getmatches()) != 0
call go#guru#ClearSameIds()
else
call go#guru#SameIds()
endif
endfunction
function! go#guru#AutoToogleSameIds() abort
if get(g:, "go_auto_sameids", 0)
call go#util#EchoProgress("sameids auto highlighting disabled")
call go#guru#ClearSameIds()
let g:go_auto_sameids = 0
return
endif
call go#util#EchoSuccess("sameids auto highlighting enabled")
let g:go_auto_sameids = 1
endfunction
""""""""""""""""""""""""""""""""""""""""
"" HELPER FUNCTIONS
""""""""""""""""""""""""""""""""""""""""
" This uses Vim's errorformat to parse the output from Guru's 'plain output
" and put it into location list. I believe using errorformat is much more
" easier to use. If we need more power we can always switch back to parse it
" via regex. Match two possible styles of errorformats:
"
" 'file:line.col-line2.col2: message'
" 'file:line:col: message'
"
" We discard line2 and col2 for the first errorformat, because it's not
" useful and location only has the ability to show one line and column
" number
function! s:parse_guru_output(exit_val, output, title) abort
if a:exit_val
call go#util#EchoError(a:output)
return
endif
let old_errorformat = &errorformat
let errformat = "%f:%l.%c-%[%^:]%#:\ %m,%f:%l:%c:\ %m"
call go#list#ParseFormat("locationlist", errformat, a:output, a:title)
let &errorformat = old_errorformat
let errors = go#list#Get("locationlist")
call go#list#Window("locationlist", len(errors))
endfun
function! go#guru#Scope(...) abort
if a:0
if a:0 == 1 && a:1 == '""'
unlet g:go_guru_scope
call go#util#EchoSuccess("guru scope is cleared")
else
let g:go_guru_scope = a:000
call go#util#EchoSuccess("guru scope changed to: ". join(a:000, ","))
endif
return
endif
if !exists('g:go_guru_scope')
call go#util#EchoError("guru scope is not set")
else
call go#util#EchoSuccess("current guru scope: ". join(g:go_guru_scope, ","))
endif
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,126 @@
function! go#impl#Impl(...) abort
let binpath = go#path#CheckBinPath('impl')
if empty(binpath)
return
endif
let recv = ""
let iface = ""
if a:0 == 0
" user didn't passed anything, just called ':GoImpl'
let receiveType = expand("<cword>")
let recv = printf("%s *%s", tolower(receiveType)[0], receiveType)
let iface = input("vim-go: generating method stubs for interface: ")
redraw!
if empty(iface)
call go#util#EchoError('usage: interface type is not provided')
return
endif
elseif a:0 == 1
" we assume the user only passed the interface type,
" i.e: ':GoImpl io.Writer'
let receiveType = expand("<cword>")
let recv = printf("%s *%s", tolower(receiveType)[0], receiveType)
let iface = a:1
elseif a:0 > 2
" user passed receiver and interface type both,
" i.e: 'GoImpl f *Foo io.Writer'
let recv = join(a:000[:-2], ' ')
let iface = a:000[-1]
else
call go#util#EchoError('usage: GoImpl {receiver} {interface}')
return
endif
let result = go#util#System(printf("%s '%s' '%s'", binpath, recv, iface))
if go#util#ShellError() != 0
call go#util#EchoError(result)
return
endif
if result ==# ''
return
end
let pos = getpos('.')
put =''
put =result
call setpos('.', pos)
endfunction
if exists('*uniq')
function! s:uniq(list)
return uniq(a:list)
endfunction
else
" Note: Believe that the list is sorted
function! s:uniq(list)
let i = len(a:list) - 1
while 0 < i
if a:list[i-1] ==# a:list[i]
call remove(a:list, i)
let i -= 2
else
let i -= 1
endif
endwhile
return a:list
endfunction
endif
function! s:root_dirs() abort
let dirs = []
let root = go#util#goroot()
if root !=# '' && isdirectory(root)
call add(dirs, root)
endif
let paths = map(split(go#util#gopath(), go#util#PathListSep()), "substitute(v:val, '\\\\', '/', 'g')")
if go#util#ShellError()
return []
endif
if !empty(filter(paths, 'isdirectory(v:val)'))
call extend(dirs, paths)
endif
return dirs
endfunction
function! s:go_packages(dirs) abort
let pkgs = []
for d in a:dirs
let pkg_root = expand(d . '/pkg/' . go#util#osarch())
call extend(pkgs, split(globpath(pkg_root, '**/*.a', 1), "\n"))
endfor
return map(pkgs, "fnamemodify(v:val, ':t:r')")
endfunction
function! s:interface_list(pkg) abort
let contents = split(go#util#System('go doc ' . a:pkg), "\n")
if go#util#ShellError()
return []
endif
call filter(contents, 'v:val =~# ''^type\s\+\h\w*\s\+interface''')
return map(contents, 'a:pkg . "." . matchstr(v:val, ''^type\s\+\zs\h\w*\ze\s\+interface'')')
endfunction
" Complete package and interface for {interface}
function! go#impl#Complete(arglead, cmdline, cursorpos) abort
let words = split(a:cmdline, '\s\+', 1)
if words[-1] ==# ''
return s:uniq(sort(s:go_packages(s:root_dirs())))
elseif words[-1] =~# '^\h\w*$'
return s:uniq(sort(filter(s:go_packages(s:root_dirs()), 'stridx(v:val, words[-1]) == 0')))
elseif words[-1] =~# '^\h\w*\.\%(\h\w*\)\=$'
let [pkg, interface] = split(words[-1], '\.', 1)
echomsg pkg
return s:uniq(sort(filter(s:interface_list(pkg), 'v:val =~? words[-1]')))
else
return []
endif
endfunction
" vim: sw=2 ts=2 et

View File

@ -2,235 +2,212 @@
" Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file.
"
" import.vim: Vim commands to import/drop Go packages.
" Check out the docs for more information at /doc/vim-go.txt
"
" This filetype plugin adds three new commands for go buffers:
"
" :GoImport {path}
"
" Import ensures that the provided package {path} is imported
" in the current Go buffer, using proper style and ordering.
" If {path} is already being imported, an error will be
" displayed and the buffer will be untouched.
"
" :GoImportAs {localname} {path}
"
" Same as Import, but uses a custom local name for the package.
"
" :GoDrop {path}
"
" Remove the import line for the provided package {path}, if
" present in the current Go buffer. If {path} is not being
" imported, an error will be displayed and the buffer will be
" untouched.
"
" If you would like to add shortcuts, you can do so by doing the following:
"
" Import fmt
" au Filetype go nnoremap <buffer> <LocalLeader>f :Import fmt<CR>
"
" Drop fmt
" au Filetype go nnoremap <buffer> <LocalLeader>F :Drop fmt<CR>
"
" Import the word under your cursor
" au Filetype go nnoremap <buffer> <LocalLeader>k
" \ :exe 'Import ' . expand('<cword>')<CR>
"
" The backslash '\' is the default maplocalleader, so it is possible that
" your vim is set to use a different character (:help maplocalleader).
"
function! go#import#SwitchImport(enabled, localname, path)
let view = winsaveview()
let path = a:path
function! go#import#SwitchImport(enabled, localname, path, bang) abort
let view = winsaveview()
let path = substitute(a:path, '^\s*\(.\{-}\)\s*$', '\1', '')
" Quotes are not necessary, so remove them if provided.
if path[0] == '"'
let path = strpart(path, 1)
endif
if path[len(path)-1] == '"'
let path = strpart(path, 0, len(path) - 1)
endif
if path == ''
call s:Error('Import path not provided')
return
" Quotes are not necessary, so remove them if provided.
if path[0] == '"'
let path = strpart(path, 1)
endif
if path[len(path)-1] == '"'
let path = strpart(path, 0, len(path) - 1)
endif
" if given a trailing slash, eg. `github.com/user/pkg/`, remove it
if path[len(path)-1] == '/'
let path = strpart(path, 0, len(path) - 1)
endif
if path == ''
call s:Error('Import path not provided')
return
endif
if a:bang == "!"
let out = go#util#System("go get -u -v ".shellescape(path))
if go#util#ShellError() != 0
call s:Error("Can't find import: " . path . ":" . out)
endif
endif
let exists = go#tool#Exists(path)
if exists == -1
call s:Error("Can't find import: " . path)
return
endif
let exists = go#tool#Exists(path)
if exists == -1
call s:Error("Can't find import: " . path)
return
endif
" Extract any site prefix (e.g. github.com/).
" If other imports with the same prefix are grouped separately,
" we will add this new import with them.
" Only up to and including the first slash is used.
let siteprefix = matchstr(path, "^[^/]*/")
" Extract any site prefix (e.g. github.com/).
" If other imports with the same prefix are grouped separately,
" we will add this new import with them.
" Only up to and including the first slash is used.
let siteprefix = matchstr(path, "^[^/]*/")
let qpath = '"' . path . '"'
if a:localname != ''
let qlocalpath = a:localname . ' ' . qpath
else
let qlocalpath = qpath
endif
let indentstr = 0
let packageline = -1 " Position of package name statement
let appendline = -1 " Position to introduce new import
let deleteline = -1 " Position of line with existing import
let linesdelta = 0 " Lines added/removed
let qpath = '"' . path . '"'
if a:localname != ''
let qlocalpath = a:localname . ' ' . qpath
else
let qlocalpath = qpath
endif
let indentstr = 0
let packageline = -1 " Position of package name statement
let appendline = -1 " Position to introduce new import
let deleteline = -1 " Position of line with existing import
let linesdelta = 0 " Lines added/removed
" Find proper place to add/remove import.
let line = 0
while line <= line('$')
let linestr = getline(line)
" Find proper place to add/remove import.
let line = 0
while line <= line('$')
let linestr = getline(line)
if linestr =~# '^package\s'
let packageline = line
let appendline = line
if linestr =~# '^package\s'
let packageline = line
let appendline = line
elseif linestr =~# '^import\s\+('
let appendstr = qlocalpath
let indentstr = 1
let appendline = line
let firstblank = -1
let lastprefix = ""
while line <= line("$")
let line = line + 1
let linestr = getline(line)
let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\S*\s*\)"\(.\+\)"\)')
if empty(m)
if siteprefix == "" && a:enabled
" must be in the first group
break
endif
" record this position, but keep looking
if firstblank < 0
let firstblank = line
endif
continue
endif
if m[1] == ')'
" if there's no match, add it to the first group
if appendline < 0 && firstblank >= 0
let appendline = firstblank
endif
break
endif
let lastprefix = matchstr(m[4], "^[^/]*/")
if a:localname != '' && m[3] != ''
let qlocalpath = printf('%-' . (len(m[3])-1) . 's %s', a:localname, qpath)
endif
let appendstr = m[2] . qlocalpath
let indentstr = 0
if m[4] == path
let appendline = -1
let deleteline = line
break
elseif m[4] < path
" don't set candidate position if we have a site prefix,
" we've passed a blank line, and this doesn't share the same
" site prefix.
if siteprefix == "" || firstblank < 0 || match(m[4], "^" . siteprefix) >= 0
let appendline = line
endif
elseif siteprefix != "" && match(m[4], "^" . siteprefix) >= 0
" first entry of site group
let appendline = line - 1
break
endif
endwhile
break
elseif linestr =~# '^import '
if appendline == packageline
let appendstr = 'import ' . qlocalpath
let appendline = line - 1
endif
let m = matchlist(linestr, '^import\(\s\+\)\(\S*\s*\)"\(.\+\)"')
if !empty(m)
if m[3] == path
let appendline = -1
let deleteline = line
break
endif
if m[3] < path
let appendline = line
endif
if a:localname != '' && m[2] != ''
let qlocalpath = printf("%s %" . len(m[2])-1 . "s", a:localname, qpath)
endif
let appendstr = 'import' . m[1] . qlocalpath
endif
elseif linestr =~# '^\(var\|const\|type\|func\)\>'
break
endif
elseif linestr =~# '^import\s\+('
let appendstr = qlocalpath
let indentstr = 1
let appendline = line
let firstblank = -1
let lastprefix = ""
while line <= line("$")
let line = line + 1
endwhile
" Append or remove the package import, as requested.
if a:enabled
if deleteline != -1
call s:Error(qpath . ' already being imported')
elseif appendline == -1
call s:Error('No package line found')
else
if appendline == packageline
call append(appendline + 0, '')
call append(appendline + 1, 'import (')
call append(appendline + 2, ')')
let appendline += 2
let linesdelta += 3
let appendstr = qlocalpath
let indentstr = 1
endif
call append(appendline, appendstr)
execute appendline + 1
if indentstr
execute 'normal >>'
endif
let linesdelta += 1
let linestr = getline(line)
let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\S*\s*\)"\(.\+\)"\)')
if empty(m)
if siteprefix == "" && a:enabled
" must be in the first group
break
endif
" record this position, but keep looking
if firstblank < 0
let firstblank = line
endif
continue
endif
if m[1] == ')'
" if there's no match, add it to the first group
if appendline < 0 && firstblank >= 0
let appendline = firstblank
endif
break
endif
let lastprefix = matchstr(m[4], "^[^/]*/")
if a:localname != '' && m[3] != ''
let qlocalpath = printf('%-' . (len(m[3])-1) . 's %s', a:localname, qpath)
endif
let appendstr = m[2] . qlocalpath
let indentstr = 0
if m[4] == path
let appendline = -1
let deleteline = line
break
elseif m[4] < path
" don't set candidate position if we have a site prefix,
" we've passed a blank line, and this doesn't share the same
" site prefix.
if siteprefix == "" || firstblank < 0 || match(m[4], "^" . siteprefix) >= 0
let appendline = line
endif
elseif siteprefix != "" && match(m[4], "^" . siteprefix) >= 0
" first entry of site group
let appendline = line - 1
break
endif
endwhile
break
elseif linestr =~# '^import '
if appendline == packageline
let appendstr = 'import ' . qlocalpath
let appendline = line - 1
endif
let m = matchlist(linestr, '^import\(\s\+\)\(\S*\s*\)"\(.\+\)"')
if !empty(m)
if m[3] == path
let appendline = -1
let deleteline = line
break
endif
if m[3] < path
let appendline = line
endif
if a:localname != '' && m[2] != ''
let qlocalpath = printf("%s %" . len(m[2])-1 . "s", a:localname, qpath)
endif
let appendstr = 'import' . m[1] . qlocalpath
endif
elseif linestr =~# '^\(var\|const\|type\|func\)\>'
break
endif
let line = line + 1
endwhile
" Append or remove the package import, as requested.
if a:enabled
if deleteline != -1
call s:Error(qpath . ' already being imported')
elseif appendline == -1
call s:Error('No package line found')
else
if deleteline == -1
call s:Error(qpath . ' not being imported')
else
execute deleteline . 'd'
let linesdelta -= 1
if getline(deleteline-1) =~# '^import\s\+(' && getline(deleteline) =~# '^)'
" Delete empty import block
let deleteline -= 1
execute deleteline . "d"
execute deleteline . "d"
let linesdelta -= 2
endif
if getline(deleteline) == '' && getline(deleteline - 1) == ''
" Delete spacing for removed line too.
execute deleteline . "d"
let linesdelta -= 1
endif
endif
if appendline == packageline
call append(appendline + 0, '')
call append(appendline + 1, 'import (')
call append(appendline + 2, ')')
let appendline += 2
let linesdelta += 3
let appendstr = qlocalpath
let indentstr = 1
endif
call append(appendline, appendstr)
execute appendline + 1
if indentstr
execute 'normal! >>'
endif
let linesdelta += 1
endif
else
if deleteline == -1
call s:Error(qpath . ' not being imported')
else
execute deleteline . 'd'
let linesdelta -= 1
" Adjust view for any changes.
let view.lnum += linesdelta
let view.topline += linesdelta
if view.topline < 0
let view.topline = 0
if getline(deleteline-1) =~# '^import\s\+(' && getline(deleteline) =~# '^)'
" Delete empty import block
let deleteline -= 1
execute deleteline . "d"
execute deleteline . "d"
let linesdelta -= 2
endif
if getline(deleteline) == '' && getline(deleteline - 1) == ''
" Delete spacing for removed line too.
execute deleteline . "d"
let linesdelta -= 1
endif
endif
endif
" Put buffer back where it was.
call winrestview(view)
" Adjust view for any changes.
let view.lnum += linesdelta
let view.topline += linesdelta
if view.topline < 0
let view.topline = 0
endif
" Put buffer back where it was.
call winrestview(view)
endfunction
function! s:Error(s)
echohl Error | echo a:s | echohl None
function! s:Error(s) abort
echohl Error | echo a:s | echohl None
endfunction
" vim:ts=4:sw=4:et
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,109 @@
" Spawn returns callbacks to be used with job_start. It's abstracted to be
" used with various go command, such as build, test, install, etc.. This avoid
" us to write the same callback over and over for some commands. It's fully
" customizable so each command can change it to it's own logic.
function go#job#Spawn(args)
let cbs = {
\ 'winnr': winnr(),
\ 'dir': getcwd(),
\ 'jobdir': fnameescape(expand("%:p:h")),
\ 'messages': [],
\ 'args': a:args.cmd,
\ 'bang': 0,
\ }
if has_key(a:args, 'bang')
let cbs.bang = a:args.bang
endif
" add final callback to be called if async job is finished
" The signature should be in form: func(job, exit_status, messages)
if has_key(a:args, 'custom_cb')
let cbs.custom_cb = a:args.custom_cb
endif
if has_key(a:args, 'error_info_cb')
let cbs.error_info_cb = a:args.error_info_cb
endif
function cbs.callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
function cbs.close_cb(chan) dict
let l:job = ch_getjob(a:chan)
let l:status = job_status(l:job)
" the job might be in fail status, we assume by default it's failed.
" However if it's dead, we can use the real exitval
let exitval = 1
if l:status == "dead"
let l:info = job_info(l:job)
let exitval = l:info.exitval
endif
if has_key(self, 'custom_cb')
call self.custom_cb(l:job, exitval, self.messages)
endif
if has_key(self, 'error_info_cb')
call self.error_info_cb(l:job, exitval, self.messages)
endif
if get(g:, 'go_echo_command_info', 1)
if exitval == 0
call go#util#EchoSuccess("SUCCESS")
else
call go#util#EchoError("FAILED")
endif
endif
let l:listtype = go#list#Type("quickfix")
if exitval == 0
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
return
endif
call self.show_errors(l:listtype)
endfunction
function cbs.show_errors(listtype) dict
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
try
execute cd self.jobdir
let errors = go#tool#ParseErrors(self.messages)
let errors = go#tool#FilterValids(errors)
finally
execute cd . fnameescape(self.dir)
endtry
if !len(errors)
" failed to parse errors, output the original content
call go#util#EchoError(join(self.messages, " "))
call go#util#EchoError(self.dir)
return
endif
if self.winnr == winnr()
call go#list#Populate(a:listtype, errors, join(self.args))
call go#list#Window(a:listtype, len(errors))
if !empty(errors) && !self.bang
call go#list#JumpToFirst(a:listtype)
endif
endif
endfunction
" override callback handler if user provided it
if has_key(a:args, 'callback')
let cbs.callback = a:args.callback
endif
" override close callback handler if user provided it
if has_key(a:args, 'close_cb')
let cbs.close_cb = a:args.close_cb
endif
return cbs
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,197 @@
" s:jobs is a global reference to all jobs started with Spawn() or with the
" internal function s:spawn
let s:jobs = {}
" s:handlers is a global event handlers for all jobs started with Spawn() or
" with the internal function s:spawn
let s:handlers = {}
" Spawn is a wrapper around s:spawn. It can be executed by other files and
" scripts if needed. Desc defines the description for printing the status
" during the job execution (useful for statusline integration).
function! go#jobcontrol#Spawn(bang, desc, args) abort
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let job = s:spawn(a:bang, a:desc, a:args)
return job.id
endfunction
" AddHandler adds a on_exit callback handler and returns the id.
function! go#jobcontrol#AddHandler(handler) abort
let i = len(s:handlers)
while has_key(s:handlers, string(i))
let i += 1
break
endwhile
let s:handlers[string(i)] = a:handler
return string(i)
endfunction
" RemoveHandler removes a callback handler by id.
function! go#jobcontrol#RemoveHandler(id) abort
unlet s:handlers[a:id]
endfunction
" spawn spawns a go subcommand with the name and arguments with jobstart. Once
" a job is started a reference will be stored inside s:jobs. spawn changes the
" GOPATH when g:go_autodetect_gopath is enabled. The job is started inside the
" current files folder.
function! s:spawn(bang, desc, args) abort
let status_type = a:args[0]
let status_dir = expand('%:p:h')
let started_at = reltime()
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': status_type,
\ 'state': "started",
\})
let job = {
\ 'desc': a:desc,
\ 'bang': a:bang,
\ 'winnr': winnr(),
\ 'importpath': go#package#ImportPath(expand('%:p:h')),
\ 'state': "RUNNING",
\ 'stderr' : [],
\ 'stdout' : [],
\ 'on_stdout': function('s:on_stdout'),
\ 'on_stderr': function('s:on_stderr'),
\ 'on_exit' : function('s:on_exit'),
\ 'status_type' : status_type,
\ 'status_dir' : status_dir,
\ 'started_at' : started_at,
\ }
" modify GOPATH if needed
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" execute go build in the files directory
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
" cleanup previous jobs for this file
for jb in values(s:jobs)
if jb.importpath == job.importpath
unlet s:jobs[jb.id]
endif
endfor
let dir = getcwd()
let jobdir = fnameescape(expand("%:p:h"))
execute cd . jobdir
" append the subcommand, such as 'build'
let argv = ['go'] + a:args
" run, forrest, run!
let id = jobstart(argv, job)
let job.id = id
let job.dir = jobdir
let s:jobs[id] = job
execute cd . fnameescape(dir)
" restore back GOPATH
let $GOPATH = old_gopath
return job
endfunction
" on_exit is the exit handler for jobstart(). It handles cleaning up the job
" references and also displaying errors in the quickfix window collected by
" on_stderr handler. If there are no errors and a quickfix window is open,
" it'll be closed.
function! s:on_exit(job_id, exit_status, event) dict abort
let status = {
\ 'desc': 'last status',
\ 'type': self.status_type,
\ 'state': "success",
\ }
if a:exit_status
let status.state = "failed"
endif
let elapsed_time = reltimestr(reltime(self.started_at))
" strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time)
call go#statusline#Update(self.status_dir, status)
let std_combined = self.stderr + self.stdout
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
execute cd self.dir
call s:callback_handlers_on_exit(s:jobs[a:job_id], a:exit_status, std_combined)
let l:listtype = go#list#Type("quickfix")
if a:exit_status == 0
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
let self.state = "SUCCESS"
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoSuccess("[" . self.status_type . "] SUCCESS")
endif
execute cd . fnameescape(dir)
return
endif
let self.state = "FAILED"
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoError("[" . self.status_type . "] FAILED")
endif
let errors = go#tool#ParseErrors(std_combined)
let errors = go#tool#FilterValids(errors)
execute cd . fnameescape(dir)
if !len(errors)
" failed to parse errors, output the original content
call go#util#EchoError(std_combined[0])
return
endif
" if we are still in the same windows show the list
if self.winnr == winnr()
call go#list#Populate(l:listtype, errors, self.desc)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !self.bang
call go#list#JumpToFirst(l:listtype)
endif
endif
endfunction
" callback_handlers_on_exit runs all handlers for job on exit event.
function! s:callback_handlers_on_exit(job, exit_status, data) abort
if empty(s:handlers)
return
endif
for s:handler in values(s:handlers)
call s:handler(a:job, a:exit_status, a:data)
endfor
endfunction
" on_stdout is the stdout handler for jobstart(). It collects the output of
" stderr and stores them to the jobs internal stdout list.
function! s:on_stdout(job_id, data, event) dict abort
call extend(self.stdout, a:data)
endfunction
" on_stderr is the stderr handler for jobstart(). It collects the output of
" stderr and stores them to the jobs internal stderr list.
function! s:on_stderr(job_id, data, event) dict abort
call extend(self.stderr, a:data)
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,30 +1,312 @@
" Copyright 2013 The Go Authors. All rights reserved.
" Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file.
"
" lint.vim: Vim command to lint Go files with golint.
"
" https://github.com/golang/lint
"
" This filetype plugin add a new commands for go buffers:
"
" :GoLint
"
" Run golint for the current Go file.
"
if !exists("g:go_golint_bin")
let g:go_golint_bin = "golint"
if !exists("g:go_metalinter_command")
let g:go_metalinter_command = ""
endif
function! go#lint#Run() abort
let bin_path = go#tool#BinPath(g:go_golint_bin)
if empty(bin_path)
return
endif
if !exists("g:go_metalinter_autosave_enabled")
let g:go_metalinter_autosave_enabled = ['vet', 'golint']
endif
silent cexpr system(bin_path . " " . shellescape(expand('%')))
cwindow
if !exists("g:go_metalinter_enabled")
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
endif
if !exists("g:go_golint_bin")
let g:go_golint_bin = "golint"
endif
if !exists("g:go_errcheck_bin")
let g:go_errcheck_bin = "errcheck"
endif
function! go#lint#Gometa(autosave, ...) abort
if a:0 == 0
let goargs = shellescape(expand('%:p:h'))
else
let goargs = go#util#Shelljoin(a:000)
endif
let bin_path = go#path#CheckBinPath("gometalinter")
if empty(bin_path)
return
endif
let cmd = [bin_path]
let cmd += ["--disable-all"]
if a:autosave || empty(g:go_metalinter_command)
" linters
let linters = a:autosave ? g:go_metalinter_autosave_enabled : g:go_metalinter_enabled
for linter in linters
let cmd += ["--enable=".linter]
endfor
" path
let cmd += [expand('%:p:h')]
else
" the user wants something else, let us use it.
let cmd += split(g:go_metalinter_command, " ")
endif
" gometalinter has a default deadline of 5 seconds.
"
" For async mode (s:lint_job), we want to override the default deadline only
" if we have a deadline configured.
"
" For sync mode (go#tool#ExecuteInDir), always explicitly pass the 5 seconds
" deadline if there is no other deadline configured. If a deadline is
" configured, then use it.
" Call gometalinter asynchronously.
if go#util#has_job() && has('lambda')
let deadline = get(g:, 'go_metalinter_deadline', 0)
if deadline != 0
let cmd += ["--deadline=" . deadline]
endif
call s:lint_job({'cmd': cmd})
return
endif
" We're calling gometalinter synchronously.
let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")]
if a:autosave
" include only messages for the active buffer
let cmd += ["--include='^" . expand('%:p') . ".*$'"]
endif
let meta_command = join(cmd, " ")
let out = go#tool#ExecuteInDir(meta_command)
let l:listtype = "quickfix"
if go#util#ShellError() == 0
redraw | echo
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
else
" GoMetaLinter can output one of the two, so we look for both:
" <file>:<line>:[<column>]: <message> (<linter>)
" <file>:<line>:: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
" Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if !a:autosave
call go#list#JumpToFirst(l:listtype)
endif
endif
endfunction
" Golint calls 'golint' on the current directory. Any warnings are populated in
" the location list
function! go#lint#Golint(...) abort
let bin_path = go#path#CheckBinPath(g:go_golint_bin)
if empty(bin_path)
return
endif
" vim:ts=4:sw=4:et
if a:0 == 0
let goargs = shellescape(expand('%'))
else
let goargs = go#util#Shelljoin(a:000)
endif
let out = go#util#System(bin_path . " " . goargs)
if empty(out)
echon "vim-go: " | echohl Function | echon "[lint] PASS" | echohl None
return
endif
let l:listtype = "quickfix"
call go#list#Parse(l:listtype, out)
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
call go#list#JumpToFirst(l:listtype)
endfunction
" Vet calls 'go vet' on the current directory. Any warnings are populated in
" the location list
function! go#lint#Vet(bang, ...) abort
call go#cmd#autowrite()
echon "vim-go: " | echohl Identifier | echon "calling vet..." | echohl None
if a:0 == 0
let out = go#tool#ExecuteInDir('go vet')
else
let out = go#tool#ExecuteInDir('go tool vet ' . go#util#Shelljoin(a:000))
endif
let l:listtype = "quickfix"
if go#util#ShellError() != 0
let errors = go#tool#ParseErrors(split(out, '\n'))
call go#list#Populate(l:listtype, errors, 'Vet')
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
endif
echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None
else
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None
endif
endfunction
" ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in
" the location list
function! go#lint#Errcheck(...) abort
if a:0 == 0
let goargs = go#package#ImportPath(expand('%:p:h'))
if goargs == -1
echohl Error | echomsg "vim-go: package is not inside GOPATH src" | echohl None
return
endif
else
let goargs = go#util#Shelljoin(a:000)
endif
let bin_path = go#path#CheckBinPath(g:go_errcheck_bin)
if empty(bin_path)
return
endif
echon "vim-go: " | echohl Identifier | echon "errcheck analysing ..." | echohl None
redraw
let command = bin_path . ' -abspath ' . goargs
let out = go#tool#ExecuteInDir(command)
let l:listtype = "quickfix"
if go#util#ShellError() != 0
let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m"
" Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck')
let errors = go#list#Get(l:listtype)
if empty(errors)
echohl Error | echomsg "GoErrCheck returned error" | echohl None
echo out
return
endif
if !empty(errors)
call go#list#Populate(l:listtype, errors, 'Errcheck')
call go#list#Window(l:listtype, len(errors))
if !empty(errors)
call go#list#JumpToFirst(l:listtype)
endif
endif
else
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None
endif
endfunction
function! go#lint#ToggleMetaLinterAutoSave() abort
if get(g:, "go_metalinter_autosave", 0)
let g:go_metalinter_autosave = 0
call go#util#EchoProgress("auto metalinter disabled")
return
end
let g:go_metalinter_autosave = 1
call go#util#EchoProgress("auto metalinter enabled")
endfunction
function s:lint_job(args)
let status_dir = expand('%:p:h')
let started_at = reltime()
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': "gometalinter",
\ 'state': "analysing",
\})
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let l:listtype = go#list#Type("quickfix")
let l:errformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m'
function! s:callback(chan, msg) closure
let old_errorformat = &errorformat
let &errorformat = l:errformat
caddexpr a:msg
let &errorformat = old_errorformat
" TODO(arslan): cursor still jumps to first error even If I don't want
" it. Seems like there is a regression somewhere, but not sure where.
copen
endfunction
function! s:close_cb(chan) closure
let l:job = ch_getjob(a:chan)
let l:status = job_status(l:job)
let exitval = 1
if l:status == "dead"
let l:info = job_info(l:job)
let exitval = l:info.exitval
endif
let status = {
\ 'desc': 'last status',
\ 'type': "gometaliner",
\ 'state': "finished",
\ }
if exitval
let status.state = "failed"
endif
let elapsed_time = reltimestr(reltime(started_at))
" strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
let status.state .= printf(" (%ss)", elapsed_time)
call go#statusline#Update(status_dir, status)
let errors = go#list#Get(l:listtype)
if empty(errors)
call go#list#Window(l:listtype, len(errors))
elseif has("patch-7.4.2200")
if l:listtype == 'quickfix'
call setqflist([], 'a', {'title': 'GoMetaLinter'})
else
call setloclist(0, [], 'a', {'title': 'GoMetaLinter'})
endif
endif
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoSuccess("linting finished")
endif
endfunction
let start_options = {
\ 'callback': funcref("s:callback"),
\ 'close_cb': funcref("s:close_cb"),
\ }
call job_start(a:args.cmd, start_options)
call go#list#Clean(l:listtype)
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("linting started ...")
endif
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,133 @@
if !exists("g:go_list_type")
let g:go_list_type = ""
endif
" Window opens the list with the given height up to 10 lines maximum.
" Otherwise g:go_loclist_height is used. If no or zero height is given it
" closes the window
function! go#list#Window(listtype, ...) abort
let l:listtype = go#list#Type(a:listtype)
" we don't use lwindow to close the location list as we need also the
" ability to resize the window. So, we are going to use lopen and lclose
" for a better user experience. If the number of errors in a current
" location list increases/decreases, cwindow will not resize when a new
" updated height is passed. lopen in the other hand resizes the screen.
if !a:0 || a:1 == 0
if l:listtype == "locationlist"
lclose
else
cclose
endif
return
endif
let height = get(g:, "go_list_height", 0)
if height == 0
" prevent creating a large location height for a large set of numbers
if a:1 > 10
let height = 10
else
let height = a:1
endif
endif
if l:listtype == "locationlist"
exe 'lopen ' . height
else
exe 'copen ' . height
endif
endfunction
" Get returns the current list of items from the location list
function! go#list#Get(listtype) abort
let l:listtype = go#list#Type(a:listtype)
if l:listtype == "locationlist"
return getloclist(0)
else
return getqflist()
endif
endfunction
" Populate populate the location list with the given items
function! go#list#Populate(listtype, items, title) abort
let l:listtype = go#list#Type(a:listtype)
if l:listtype == "locationlist"
call setloclist(0, a:items, 'r')
" The last argument ({what}) is introduced with 7.4.2200:
" https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640
if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif
else
call setqflist(a:items, 'r')
if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif
endif
endfunction
function! go#list#PopulateWin(winnr, items) abort
call setloclist(a:winnr, a:items, 'r')
endfunction
" Parse parses the given items based on the specified errorformat nad
" populates the location list.
function! go#list#ParseFormat(listtype, errformat, items, title) abort
let l:listtype = go#list#Type(a:listtype)
" backup users errorformat, will be restored once we are finished
let old_errorformat = &errorformat
" parse and populate the location list
let &errorformat = a:errformat
if l:listtype == "locationlist"
lgetexpr a:items
if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif
else
cgetexpr a:items
if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif
endif
"restore back
let &errorformat = old_errorformat
endfunction
" Parse parses the given items based on the global errorformat and
" populates the location list.
function! go#list#Parse(listtype, items) abort
let l:listtype = go#list#Type(a:listtype)
if l:listtype == "locationlist"
lgetexpr a:items
else
cgetexpr a:items
endif
endfunction
" JumpToFirst jumps to the first item in the location list
function! go#list#JumpToFirst(listtype) abort
let l:listtype = go#list#Type(a:listtype)
if l:listtype == "locationlist"
ll 1
else
cc 1
endif
endfunction
" Clean cleans the location list
function! go#list#Clean(listtype) abort
let l:listtype = go#list#Type(a:listtype)
if l:listtype == "locationlist"
lex []
else
cex []
endif
endfunction
function! go#list#Type(listtype) abort
if g:go_list_type == "locationlist"
return "locationlist"
elseif g:go_list_type == "quickfix"
return "quickfix"
else
return a:listtype
endif
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,224 +0,0 @@
" -*- text -*-
" oracle.vim -- Vim integration for the Go oracle.
"
" Part of this plugin was taken directly from the oracle repo, however it's
" massively changed for a better integration into vim-go. Thanks Alan Donovan
" for the first iteration based on quickfix! - fatih arslan
"
"
if !exists("g:go_oracle_bin")
let g:go_oracle_bin = "oracle"
endif
func! s:qflist(output)
let qflist = []
" Parse GNU-style 'file:line.col-line.col: message' format.
let mx = '^\(\a:[\\/][^:]\+\|[^:]\+\):\(\d\+\):\(\d\+\):\(.*\)$'
for line in split(a:output, "\n")
let ml = matchlist(line, mx)
" Ignore non-match lines or warnings
if ml == [] || ml[4] =~ '^ warning:'
continue
endif
let item = {
\ 'filename': ml[1],
\ 'text': ml[4],
\ 'lnum': ml[2],
\ 'col': ml[3],
\}
let bnr = bufnr(fnameescape(ml[1]))
if bnr != -1
let item['bufnr'] = bnr
endif
call add(qflist, item)
endfor
call setqflist(qflist)
cwindow
endfun
func! s:getpos(l, c)
if &encoding != 'utf-8'
let buf = a:l == 1 ? '' : (join(getline(1, a:l-1), "\n") . "\n")
let buf .= a:c == 1 ? '' : getline('.')[:a:c-2]
return len(iconv(buf, &encoding, 'utf-8'))
endif
return line2byte(a:l) + (a:c-2)
endfun
func! s:RunOracle(mode, selected) range abort
let fname = expand('%:p')
let dname = expand('%:p:h')
let pkg = go#package#ImportPath(dname)
if exists('g:go_oracle_scope_file')
" let the user defines the scope
let sname = shellescape(get(g:, 'go_oracle_scope_file'))
elseif exists('g:go_oracle_include_tests') && pkg != -1
" give import path so it includes all _test.go files too
let sname = shellescape(pkg)
else
" best usable way, only pass the package itself, without the test
" files
let sname = join(go#tool#Files(), ' ')
endif
"return with a warning if the bin doesn't exist
let bin_path = go#tool#BinPath(g:go_oracle_bin)
if empty(bin_path)
return
endif
if a:selected != -1
let pos1 = s:getpos(line("'<"), col("'<"))
let pos2 = s:getpos(line("'>"), col("'>"))
let cmd = printf('%s -format json -pos=%s:#%d,#%d %s %s',
\ bin_path,
\ shellescape(fname), pos1, pos2, a:mode, sname)
else
let pos = s:getpos(line('.'), col('.'))
let cmd = printf('%s -format json -pos=%s:#%d %s %s',
\ bin_path,
\ shellescape(fname), pos, a:mode, sname)
endif
echon "vim-go: " | echohl Identifier | echon "analysing ..." | echohl None
let out = system(cmd)
if v:shell_error
" unfortunaly oracle outputs a very long stack trace that is not
" parsable to show the real error. But the main issue is usually the
" package which doesn't build.
" echo out
" redraw | echon 'vim-go: could not run static analyser (does it build?)'
redraw | echon "vim-go: " | echohl Statement | echon out | echohl None
return {}
else
let json_decoded = webapi#json#decode(out)
return json_decoded
endif
endfun
" Show 'implements' relation for selected package
function! go#oracle#Implements(selected)
let out = s:RunOracle('implements', a:selected)
if empty(out)
return
endif
" be sure they exists before we retrieve them from the map
if !has_key(out, "implements")
return
endif
if has_key(out.implements, "from")
let interfaces = out.implements.from
elseif has_key(out.implements, "fromptr")
let interfaces = out.implements.fromptr
else
redraw | echon "vim-go: " | echon "does not satisfy any interface"| echohl None
return
endif
" get the type name from the type under the cursor
let typeName = out.implements.type.name
" prepare the title
let title = typeName . " implements:"
" start to populate our buffer content
let result = [title, ""]
for interface in interfaces
" don't add runtime interfaces
if interface.name !~ '^runtime'
let line = interface.name . "\t" . interface.pos
call add(result, line)
endif
endfor
" open a window and put the result
call go#ui#OpenWindow(result)
" define some buffer related mappings:
"
" go to definition when hit enter
nnoremap <buffer> <CR> :<C-u>call go#ui#OpenDefinition()<CR>
" close the window when hit ctrl-c
nnoremap <buffer> <c-c> :<C-u>call go#ui#CloseWindow()<CR>
endfunction
" Describe selected syntax: definition, methods, etc
function! go#oracle#Describe(selected)
let out = s:RunOracle('describe', a:selected)
if empty(out)
return
endif
echo out
return
let detail = out["describe"]["detail"]
let desc = out["describe"]["desc"]
echo '# detail: '. detail
" package, constant, variable, type, function or statement labe
if detail == "package"
echo desc
return
endif
if detail == "value"
echo desc
echo out["describe"]["value"]
return
endif
" the rest needs to be implemented
echo desc
endfunction
" Show possible targets of selected function call
function! go#oracle#Callees(selected)
let out = s:RunOracle('callees', a:selected)
echo out
endfunction
" Show possible callers of selected function
function! go#oracle#Callers(selected)
let out = s:RunOracle('callers', a:selected)
echo out
endfunction
" Show the callgraph of the current program.
function! go#oracle#Callgraph(selected)
let out = s:RunOracle('callgraph', a:selected)
echo out
endfunction
" Show path from callgraph root to selected function
function! go#oracle#Callstack(selected)
let out = s:RunOracle('callstack', a:selected)
echo out
endfunction
" Show free variables of selection
function! go#oracle#Freevars(selected)
let out = s:RunOracle('freevars', a:selected)
echo out
endfunction
" Show send/receive corresponding to selected channel op
function! go#oracle#Peers(selected)
let out = s:RunOracle('peers', a:selected)
echo out
endfunction
" Show all refs to entity denoted by selected identifier
function! go#oracle#Referrers(selected)
let out = s:RunOracle('referrers', a:selected)
echo out
endfunction
" vim:ts=4:sw=4:et

View File

@ -9,117 +9,125 @@ let s:goos = $GOOS
let s:goarch = $GOARCH
if len(s:goos) == 0
if exists('g:golang_goos')
let s:goos = g:golang_goos
elseif has('win32') || has('win64')
let s:goos = 'windows'
elseif has('macunix')
let s:goos = 'darwin'
else
let s:goos = '*'
endif
if exists('g:golang_goos')
let s:goos = g:golang_goos
elseif has('win32') || has('win64')
let s:goos = 'windows'
elseif has('macunix')
let s:goos = 'darwin'
else
let s:goos = '*'
endif
endif
if len(s:goarch) == 0
if exists('g:golang_goarch')
let s:goarch = g:golang_goarch
else
let s:goarch = '*'
endif
if exists('g:golang_goarch')
let s:goarch = g:golang_goarch
else
let s:goarch = '*'
endif
endif
function! go#package#Paths()
let dirs = []
function! go#package#Paths() abort
let dirs = []
if !exists("s:goroot")
if executable('go')
let goroot = substitute(system('go env GOROOT'), '\n', '', 'g')
if v:shell_error
echomsg '''go env GOROOT'' failed'
endif
let s:goroot = go#util#goroot()
if go#util#ShellError() != 0
echomsg '''go env GOROOT'' failed'
endif
else
let goroot = $GOROOT
let s:goroot = $GOROOT
endif
endif
if len(goroot) != 0 && isdirectory(goroot)
let dirs += [goroot]
endif
if len(s:goroot) != 0 && isdirectory(s:goroot)
let dirs += [s:goroot]
endif
let pathsep = ':'
if s:goos == 'windows'
let pathsep = ';'
endif
let workspaces = split($GOPATH, pathsep)
if workspaces != []
let dirs += workspaces
endif
let workspaces = split(go#path#Detect(), go#util#PathListSep())
if workspaces != []
let dirs += workspaces
endif
return dirs
return dirs
endfunction
function! go#package#ImportPath(arg)
let path = fnamemodify(resolve(a:arg), ':p')
let dirs = go#package#Paths()
function! go#package#ImportPath(arg) abort
let path = fnamemodify(resolve(a:arg), ':p')
let dirs = go#package#Paths()
for dir in dirs
if len(dir) && match(path, dir) == 0
let workspace = dir
endif
endfor
if !exists('workspace')
return -1
for dir in dirs
if len(dir) && matchstr(escape(path, '\/'), escape(dir, '\/')) == 0
let workspace = dir
endif
endfor
return substitute(path, workspace . '/src/', '', '')
if !exists('workspace')
return -1
endif
if go#util#IsWin()
let srcdir = substitute(workspace . '\src\', '//', '/', '')
return path[len(srcdir):]
else
let srcdir = substitute(workspace . '/src/', '//', '/', '')
return substitute(path, srcdir, '', '')
endif
endfunction
function! go#package#FromPath(arg)
let path = fnamemodify(resolve(a:arg), ':p')
let dirs = go#package#Paths()
function! go#package#FromPath(arg) abort
let path = fnamemodify(resolve(a:arg), ':p')
let dirs = go#package#Paths()
for dir in dirs
if len(dir) && match(path, dir) == 0
let workspace = dir
endif
endfor
if !exists('workspace')
return -1
for dir in dirs
if len(dir) && match(path, dir) == 0
let workspace = dir
endif
endfor
if isdirectory(path)
return substitute(path, workspace . 'src/', '', '')
else
return substitute(substitute(path, workspace . 'src/', '', ''),
\ '/' . fnamemodify(path, ':t'), '', '')
endif
if !exists('workspace')
return -1
endif
if isdirectory(path)
return substitute(path, workspace . 'src/', '', '')
else
return substitute(substitute(path, workspace . 'src/', '', ''),
\ '/' . fnamemodify(path, ':t'), '', '')
endif
endfunction
function! go#package#CompleteMembers(package, member)
silent! let content = system('godoc ' . a:package)
if v:shell_error || !len(content)
return []
endif
let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
try
let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*'
let mx2 = '^\%(const\|var\|type\|func\) \([A-Z][^ (]\+\).*'
let candidates = map(filter(copy(lines), 'v:val =~ mx1'),
\ 'substitute(v:val, mx1, "\\1", "")')
\ + map(filter(copy(lines), 'v:val =~ mx2'),
\ 'substitute(v:val, mx2, "\\1", "")')
return filter(candidates, '!stridx(v:val, a:member)')
catch
return []
endtry
function! go#package#CompleteMembers(package, member) abort
silent! let content = go#util#System('godoc ' . a:package)
if go#util#ShellError() || !len(content)
return []
endif
let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
try
let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*'
let mx2 = '^\%(const\|var\|type\|func\) \([A-Z][^ (]\+\).*'
let candidates = map(filter(copy(lines), 'v:val =~ mx1'),
\ 'substitute(v:val, mx1, "\\1", "")')
\ + map(filter(copy(lines), 'v:val =~ mx2'),
\ 'substitute(v:val, mx2, "\\1", "")')
return filter(candidates, '!stridx(v:val, a:member)')
catch
return []
endtry
endfunction
function! go#package#Complete(ArgLead, CmdLine, CursorPos)
let words = split(a:CmdLine, '\s\+', 1)
if len(words) > 2 && words[0] != "GoImportAs"
" Complete package members
return go#package#CompleteMembers(words[1], words[2])
endif
function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
let words = split(a:CmdLine, '\s\+', 1)
" do not complete package members for these commands
let neglect_commands = ["GoImportAs", "GoGuruScope"]
if len(words) > 2 && index(neglect_commands, words[0]) == -1
" Complete package members
return go#package#CompleteMembers(words[1], words[2])
endif
let dirs = go#package#Paths()
@ -142,6 +150,11 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos)
endif
let i = substitute(substitute(i[len(r)+1:], '[\\]', '/', 'g'),
\ '\.a$', '', 'g')
" without this the result can have duplicates in form of
" 'encoding/json' and '/encoding/json/'
let i = go#util#StripPathSep(i)
let ret[i] = i
endfor
endfor
@ -149,4 +162,4 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos)
return sort(keys(ret))
endfunction
" vim:sw=4:et
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,178 @@
" initial_go_path is used to store the initial GOPATH that was set when Vim
" was started. It's used with :GoPathClear to restore the GOPATH when the user
" changed it explicitly via :GoPath. Initially it's empty. It's being set when
" :GoPath is used
let s:initial_go_path = ""
" GoPath sets or returns the current GOPATH. If no arguments are passed it
" echoes the current GOPATH, if an argument is passed it replaces the current
" GOPATH with it. If two double quotes are passed (the empty string in go),
" it'll clear the GOPATH and will restore to the initial GOPATH.
function! go#path#GoPath(...) abort
" we have an argument, replace GOPATH
if len(a:000)
" clears the current manually set GOPATH and restores it to the
" initial GOPATH, which was set when Vim was started.
if len(a:000) == 1 && a:1 == '""'
if !empty(s:initial_go_path)
let $GOPATH = s:initial_go_path
let s:initial_go_path = ""
endif
echon "vim-go: " | echohl Function | echon "GOPATH restored to ". $GOPATH | echohl None
return
endif
echon "vim-go: " | echohl Function | echon "GOPATH changed to ". a:1 | echohl None
let s:initial_go_path = $GOPATH
let $GOPATH = a:1
return
endif
echo go#path#Detect()
endfunction
" Default returns the default GOPATH. If there is a single GOPATH it returns
" it. For multiple GOPATHS separated with a the OS specific separator, only
" the first one is returned
function! go#path#Default() abort
let go_paths = split($GOPATH, go#util#PathListSep())
if len(go_paths) == 1
return $GOPATH
endif
return go_paths[0]
endfunction
" HasPath checks whether the given path exists in GOPATH environment variable
" or not
function! go#path#HasPath(path) abort
let go_paths = split($GOPATH, go#util#PathListSep())
let last_char = strlen(a:path) - 1
" check cases of '/foo/bar/' and '/foo/bar'
if a:path[last_char] == go#util#PathSep()
let withSep = a:path
let noSep = strpart(a:path, 0, last_char)
else
let withSep = a:path . go#util#PathSep()
let noSep = a:path
endif
let hasA = index(go_paths, withSep) != -1
let hasB = index(go_paths, noSep) != -1
return hasA || hasB
endfunction
" Detect returns the current GOPATH. If a package manager is used, such as
" Godeps, GB, it will modify the GOPATH so those directories take precedence
" over the current GOPATH. It also detects diretories whose are outside
" GOPATH.
function! go#path#Detect() abort
let gopath = $GOPATH
" don't lookup for godeps if autodetect is disabled.
if !get(g:, "go_autodetect_gopath", 1)
return gopath
endif
let current_dir = fnameescape(expand('%:p:h'))
" TODO(arslan): this should be changed so folders or files should be
" fetched from a customizable list. The user should define any new package
" management tool by it's own.
" src folder outside $GOPATH
let src_root = finddir("src", current_dir .";")
if !empty(src_root)
let src_path = fnamemodify(src_root, ':p:h:h') . go#util#PathSep()
" gb vendor plugin
" (https://github.com/constabulary/gb/tree/master/cmd/gb-vendor)
let gb_vendor_root = src_path . "vendor" . go#util#PathSep()
if isdirectory(gb_vendor_root) && !go#path#HasPath(gb_vendor_root)
let gopath = gb_vendor_root . go#util#PathListSep() . gopath
endif
if !go#path#HasPath(src_path)
let gopath = src_path . go#util#PathListSep() . gopath
endif
endif
" Godeps
let godeps_root = finddir("Godeps", current_dir .";")
if !empty(godeps_root)
let godeps_path = join([fnamemodify(godeps_root, ':p:h:h'), "Godeps", "_workspace" ], go#util#PathSep())
if !go#path#HasPath(godeps_path)
let gopath = godeps_path . go#util#PathListSep() . gopath
endif
endif
" Fix up the case where initial $GOPATH is empty,
" and we end up with a trailing :
let gopath = substitute(gopath, ":$", "", "")
return gopath
endfunction
" BinPath returns the binary path of installed go tools.
function! go#path#BinPath() abort
let bin_path = ""
" check if our global custom path is set, if not check if $GOBIN is set so
" we can use it, otherwise use $GOPATH + '/bin'
if exists("g:go_bin_path")
let bin_path = g:go_bin_path
elseif $GOBIN != ""
let bin_path = $GOBIN
elseif $GOPATH != ""
let bin_path = expand(go#path#Default() . "/bin/")
else
" could not find anything
endif
return bin_path
endfunction
" CheckBinPath checks whether the given binary exists or not and returns the
" path of the binary. It returns an empty string doesn't exists.
function! go#path#CheckBinPath(binpath) abort
" remove whitespaces if user applied something like 'goimports '
let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '')
" save off original path
let old_path = $PATH
" check if we have an appropriate bin_path
let go_bin_path = go#path#BinPath()
if !empty(go_bin_path)
" append our GOBIN and GOPATH paths and be sure they can be found there...
" let us search in our GOBIN and GOPATH paths
let $PATH = go_bin_path . go#util#PathListSep() . $PATH
endif
" if it's in PATH just return it
if executable(binpath)
if exists('*exepath')
let binpath = exepath(binpath)
endif
let $PATH = old_path
return binpath
endif
" just get the basename
let basename = fnamemodify(binpath, ":t")
if !executable(basename)
echom "vim-go: could not find '" . basename . "'. Run :GoInstallBinaries to fix it."
" restore back!
let $PATH = old_path
return ""
endif
let $PATH = old_path
return go_bin_path . go#util#PathSep() . basename
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,94 +1,93 @@
if !exists("g:go_play_open_browser")
let g:go_play_open_browser = 1
let g:go_play_open_browser = 1
endif
function! go#play#Share(count, line1, line2)
if !executable('curl')
echohl ErrorMsg | echomsg "vim-go: require 'curl' command" | echohl None
return
endif
function! go#play#Share(count, line1, line2) abort
if !executable('curl')
echohl ErrorMsg | echomsg "vim-go: require 'curl' command" | echohl None
return
endif
let content = join(getline(a:line1, a:line2), "\n")
let share_file = tempname()
call writefile(split(content, "\n"), share_file, "b")
let content = join(getline(a:line1, a:line2), "\n")
let share_file = tempname()
call writefile(split(content, "\n"), share_file, "b")
let command = "curl -s -X POST http://play.golang.org/share --data-binary '@".share_file."'"
let snippet_id = system(command)
let command = "curl -s -X POST https://play.golang.org/share --data-binary '@".share_file."'"
let snippet_id = go#util#System(command)
" we can remove the temp file because it's now posted.
call delete(share_file)
" we can remove the temp file because it's now posted.
call delete(share_file)
if v:shell_error
echo 'A error has occured. Run this command to see what the problem is:'
echo command
return
endif
if go#util#ShellError() != 0
echo 'A error has occured. Run this command to see what the problem is:'
echo command
return
endif
let url = "http://play.golang.org/p/".snippet_id
let url = "http://play.golang.org/p/".snippet_id
" copy to clipboard
if has('unix') && !has('xterm_clipboard') && !has('clipboard')
let @" = url
else
let @+ = url
endif
" copy to clipboard
if has('unix') && !has('xterm_clipboard') && !has('clipboard')
let @" = url
else
let @+ = url
endif
if g:go_play_open_browser != 0
call go#tool#OpenBrowser(url)
endif
if g:go_play_open_browser != 0
call go#tool#OpenBrowser(url)
endif
echo "vim-go: snippet uploaded: ".url
echo "vim-go: snippet uploaded: ".url
endfunction
function! s:get_visual_content()
let save_regcont = @"
let save_regtype = getregtype('"')
silent! normal! gvy
let content = @"
call setreg('"', save_regcont, save_regtype)
return content
function! s:get_visual_content() abort
let save_regcont = @"
let save_regtype = getregtype('"')
silent! normal! gvy
let content = @"
call setreg('"', save_regcont, save_regtype)
return content
endfunction
" modified version of
" http://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript
" another function that returns the content of visual selection, it's not used
" but might be useful in the future
function! s:get_visual_selection()
let [lnum1, col1] = getpos("'<")[1:2]
let [lnum2, col2] = getpos("'>")[1:2]
function! s:get_visual_selection() abort
let [lnum1, col1] = getpos("'<")[1:2]
let [lnum2, col2] = getpos("'>")[1:2]
" check if the the visual mode is used before
if lnum1 == 0 || lnum2 == 0 || col1 == 0 || col2 == 0
return
endif
" check if the the visual mode is used before
if lnum1 == 0 || lnum2 == 0 || col1 == 0 || col2 == 0
return
endif
let lines = getline(lnum1, lnum2)
let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][col1 - 1:]
return join(lines, "\n")
let lines = getline(lnum1, lnum2)
let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][col1 - 1:]
return join(lines, "\n")
endfunction
" following two functions are from: https://github.com/mattn/gist-vim
" following two functions are from: https://github.com/mattn/gist-vim
" thanks @mattn
function! s:get_browser_command()
let go_play_browser_command = get(g:, 'go_play_browser_command', '')
if go_play_browser_command == ''
if has('win32') || has('win64')
let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%'
elseif has('mac') || has('macunix') || has('gui_macvim') || system('uname') =~? '^darwin'
let go_play_browser_command = 'open %URL%'
elseif executable('xdg-open')
let go_play_browser_command = 'xdg-open %URL%'
elseif executable('firefox')
let go_play_browser_command = 'firefox %URL% &'
else
let go_play_browser_command = ''
endif
function! s:get_browser_command() abort
let go_play_browser_command = get(g:, 'go_play_browser_command', '')
if go_play_browser_command == ''
if has('win32') || has('win64')
let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%'
elseif has('mac') || has('macunix') || has('gui_macvim') || go#util#System('uname') =~? '^darwin'
let go_play_browser_command = 'open %URL%'
elseif executable('xdg-open')
let go_play_browser_command = 'xdg-open %URL%'
elseif executable('firefox')
let go_play_browser_command = 'firefox %URL% &'
else
let go_play_browser_command = ''
endif
return go_play_browser_command
endif
return go_play_browser_command
endfunction
" vim:ts=4:sw=4:et
" vim: sw=2 ts=2 et

View File

@ -1,52 +1,152 @@
if !exists("g:go_gorename_bin")
let g:go_gorename_bin = "gorename"
let g:go_gorename_bin = "gorename"
endif
function! go#rename#Rename(...)
let to = ""
if a:0 == 0
let ask = printf("vim-go: rename '%s' to: ", expand("<cword>"))
let to = input(ask)
redraw
if !exists("g:go_gorename_prefill")
let g:go_gorename_prefill = 1
endif
function! go#rename#Rename(bang, ...) abort
let to_identifier = ""
if a:0 == 0
let from = expand("<cword>")
let ask = printf("vim-go: rename '%s' to: ", from)
if g:go_gorename_prefill
let to_identifier = input(ask, from)
else
let to = a:1
let to_identifier = input(ask)
endif
"return with a warning if the bin doesn't exist
let bin_path = go#tool#BinPath(g:go_gorename_bin)
if empty(bin_path)
return
redraw!
if empty(to_identifier)
return
endif
else
let to_identifier = a:1
endif
let fname = expand('%:p:t')
let pos = s:getpos(line('.'), col('.'))
let cmd = printf('%s -offset %s:#%d -to %s', bin_path, shellescape(fname), pos, to)
"return with a warning if the bin doesn't exist
let bin_path = go#path#CheckBinPath(g:go_gorename_bin)
if empty(bin_path)
return
endif
let out = go#tool#ExecuteInDir(cmd)
let fname = expand('%:p')
let pos = go#util#OffsetCursor()
let offset = printf('%s:#%d', fname, pos)
" strip out newline on the end that gorename puts. If we don't remove, it
" will trigger the 'Hit ENTER to continue' prompt
let clean = split(out, '\n')
" no need to escape for job call
let bin_path = go#util#has_job() ? bin_path : shellescape(bin_path)
let offset = go#util#has_job() ? offset : shellescape(offset)
let to_identifier = go#util#has_job() ? to_identifier : shellescape(to_identifier)
if v:shell_error
redraw | echon "vim-go: " | echohl Statement | echon clean[0] | echohl None
else
redraw | echon "vim-go: " | echohl Function | echon clean[0] | echohl None
endif
let cmd = [bin_path, "-offset", offset, "-to", to_identifier]
" refresh the buffer so we can see the new content
silent execute ":e"
" check for any tags
if exists('g:go_build_tags')
let tags = get(g:, 'go_build_tags')
call extend(cmd, ["-tags", tags])
endif
if go#util#has_job()
call go#util#EchoProgress(printf("renaming to '%s' ...", to_identifier))
call s:rename_job({
\ 'cmd': cmd,
\ 'bang': a:bang,
\})
return
endif
let command = join(cmd, " ")
let out = go#tool#ExecuteInDir(command)
let splitted = split(out, '\n')
call s:parse_errors(go#util#ShellError(), a:bang, splitted)
endfunction
func! s:getpos(l, c)
if &encoding != 'utf-8'
let buf = a:l == 1 ? '' : (join(getline(1, a:l-1), "\n") . "\n")
let buf .= a:c == 1 ? '' : getline('.')[:a:c-2]
return len(iconv(buf, &encoding, 'utf-8'))
endif
return line2byte(a:l) + (a:c-2)
endfun
function s:rename_job(args)
let messages = []
function! s:callback(chan, msg) closure
call add(messages, a:msg)
endfunction
" vim:ts=4:sw=4:et
"
let status_dir = expand('%:p:h')
function! s:close_cb(chan) closure
let l:job = ch_getjob(a:chan)
let l:info = job_info(l:job)
let status = {
\ 'desc': 'last status',
\ 'type': "gorename",
\ 'state': "finished",
\ }
if l:info.exitval
let status.state = "failed"
endif
call go#statusline#Update(status_dir, status)
call s:parse_errors(l:info.exitval, a:args.bang, messages)
endfunction
let start_options = {
\ 'callback': funcref("s:callback"),
\ 'close_cb': funcref("s:close_cb"),
\ }
" modify GOPATH if needed
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': "gorename",
\ 'state': "started",
\})
call job_start(a:args.cmd, start_options)
let $GOPATH = old_gopath
endfunction
function s:parse_errors(exit_val, bang, out)
" reload all files to reflect the new changes. We explicitly call
" checktime to trigger a reload of all files. See
" http://www.mail-archive.com/vim@vim.org/msg05900.html for more info
" about the autoread bug
let current_autoread = &autoread
set autoread
silent! checktime
let &autoread = current_autoread
let l:listtype = "quickfix"
if a:exit_val != 0
call go#util#EchoError("FAILED")
let errors = go#tool#ParseErrors(a:out)
call go#list#Populate(l:listtype, errors, 'Rename')
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
elseif empty(errors)
" failed to parse errors, output the original content
call go#util#EchoError(join(a:out, ""))
endif
return
endif
" strip out newline on the end that gorename puts. If we don't remove, it
" will trigger the 'Hit ENTER to continue' prompt
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
call go#util#EchoSuccess(a:out[0])
" refresh the buffer so we can see the new content
" TODO(arslan): also find all other buffers and refresh them too. For this
" we need a way to get the list of changes from gorename upon an success
" change.
silent execute ":e"
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,112 @@
" Statusline
""""""""""""""""""""""""""""""""
" s:statuses is a global reference to all statuses. It stores the statuses per
" import paths (map[string]status), where each status is unique per its
" type. Current status dict is in form:
" {
" 'desc' : 'Job description',
" 'state' : 'Job state, such as success, failure, etc..',
" 'type' : 'Job type, such as build, test, etc..'
" 'created_at' : 'Time it was created as seconds since 1st Jan 1970'
" }
let s:statuses = {}
" timer_id for cleaner
let s:timer_id = 0
" last_status stores the last generated text per status
let s:last_status = ""
" Show returns the current status of the job for 20 seconds (configurable). It
" displays it in form of 'desc: [type|state]' if there is any state available,
" if not it returns an empty string. This function should be plugged directly
" into the statusline.
function! go#statusline#Show() abort
" lazy initialiation of the cleaner
if !s:timer_id
" clean every 60 seconds all statuses
let interval = get(g:, 'go_statusline_duration', 60000)
let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1})
endif
" nothing to show
if empty(s:statuses)
return ''
endif
let status_dir = expand('%:p:h')
if !has_key(s:statuses, status_dir)
return ''
endif
let status = s:statuses[status_dir]
if !has_key(status, 'desc') || !has_key(status, 'state') || !has_key(status, 'type')
return ''
endif
let status_text = printf("[%s|%s]", status.type, status.state)
if empty(status_text)
return ''
endif
" only update highlight if status has changed.
if status_text != s:last_status
if status.state =~ "success" || status.state =~ "finished"
hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22
elseif status.state =~ "started" || status.state =~ "analysing"
hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88
elseif status.state =~ "failed"
hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52
endif
endif
let s:last_status = status_text
return status_text
endfunction
" Update updates (adds) the statusline for the given status_dir with the
" given status dict. It overrides any previously set status.
function! go#statusline#Update(status_dir, status) abort
let a:status.created_at = reltime()
let s:statuses[a:status_dir] = a:status
" force to update the statusline, otherwise the user needs to move the
" cursor
exe 'let &ro = &ro'
" before we stop the timer, check if we have any previous jobs to be cleaned
" up. Otherwise every job will reset the timer when this function is called
" and thus old jobs will never be cleaned
call go#statusline#Clear(0)
" also reset the timer, so the user has time to see it in the statusline.
" Setting the timer_id to 0 will trigger a new cleaner routine.
call timer_stop(s:timer_id)
let s:timer_id = 0
endfunction
" Clear clears all currently stored statusline data. The timer_id argument is
" just a placeholder so we can pass it to a timer_start() function if needed.
function! go#statusline#Clear(timer_id) abort
for [status_dir, status] in items(s:statuses)
let elapsed_time = reltimestr(reltime(status.created_at))
" strip whitespace
let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '')
if str2nr(elapsed_time) > 10
call remove(s:statuses, status_dir)
endif
endfor
if len(s:statuses) == 0
let s:statuses = {}
endif
" force to update the statusline, otherwise the user needs to move the
" cursor
exe 'let &ro = &ro'
endfunction
" vim: sw=2 ts=2 et

View File

@ -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

View File

@ -0,0 +1,28 @@
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

View File

@ -0,0 +1,50 @@
let s:current_file = expand("<sfile>")
function! go#template#create() abort
let l:go_template_use_pkg = get(g:, 'go_template_use_pkg', 0)
let l:root_dir = fnamemodify(s:current_file, ':h:h:h')
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
execute cd . fnameescape(expand("%:p:h"))
let l:package_name = go#tool#PackageName()
" if we can't figure out any package name(no Go files or non Go package
" files) from the directory create the template or use the cwd
" as the name
if l:package_name == -1 && l:go_template_use_pkg != 1
let l:template_file = get(g:, 'go_template_file', "hello_world.go")
let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file)
exe '0r ' . fnameescape(l:template_path)
$delete _
elseif l:package_name == -1 && l:go_template_use_pkg == 1
" cwd is now the dir of the package
let l:path = fnamemodify(getcwd(), ':t')
let l:content = printf("package %s", l:path)
call append(0, l:content)
$delete _
else
let l:content = printf("package %s", l:package_name)
call append(0, l:content)
$delete _
endif
" Remove the '... [New File]' message line from the command line
echon
execute cd . fnameescape(dir)
endfunction
function! go#template#ToggleAutoCreate() abort
if get(g:, "go_template_autocreate", 1)
let g:go_template_autocreate = 0
call go#util#EchoProgress("auto template create disabled")
return
end
let g:go_template_autocreate = 1
call go#util#EchoProgress("auto template create enabled")
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,139 @@
if has('nvim') && !exists("g:go_term_mode")
let g:go_term_mode = 'vsplit'
endif
" s:jobs is a global reference to all jobs started with new()
let s:jobs = {}
" new creates a new terminal with the given command. Mode is set based on the
" global variable g:go_term_mode, which is by default set to :vsplit
function! go#term#new(bang, cmd) abort
return go#term#newmode(a:bang, a:cmd, g:go_term_mode)
endfunction
" new creates a new terminal with the given command and window mode.
function! go#term#newmode(bang, cmd, mode) abort
let mode = a:mode
if empty(mode)
let mode = g:go_term_mode
endif
" modify GOPATH if needed
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" execute go build in the files directory
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
execute cd . fnameescape(expand("%:p:h"))
execute mode.' __go_term__'
setlocal filetype=goterm
setlocal bufhidden=delete
setlocal winfixheight
setlocal noswapfile
setlocal nobuflisted
let job = {
\ 'stderr' : [],
\ 'stdout' : [],
\ 'bang' : a:bang,
\ 'on_stdout': function('s:on_stdout'),
\ 'on_stderr': function('s:on_stderr'),
\ 'on_exit' : function('s:on_exit'),
\ }
let id = termopen(a:cmd, job)
execute cd . fnameescape(dir)
" restore back GOPATH
let $GOPATH = old_gopath
let job.id = id
let job.cmd = a:cmd
startinsert
" resize new term if needed.
let height = get(g:, 'go_term_height', winheight(0))
let width = get(g:, 'go_term_width', winwidth(0))
" we are careful how to resize. for example it's vertical we don't change
" the height. The below command resizes the buffer
if a:mode == "split"
exe 'resize ' . height
elseif a:mode == "vertical"
exe 'vertical resize ' . width
endif
" we also need to resize the pty, so there you go...
call jobresize(id, width, height)
let s:jobs[id] = job
return id
endfunction
function! s:on_stdout(job_id, data, event) dict abort
if !has_key(s:jobs, a:job_id)
return
endif
let job = s:jobs[a:job_id]
call extend(job.stdout, a:data)
endfunction
function! s:on_stderr(job_id, data, event) dict abort
if !has_key(s:jobs, a:job_id)
return
endif
let job = s:jobs[a:job_id]
call extend(job.stderr, a:data)
endfunction
function! s:on_exit(job_id, exit_status, event) dict abort
if !has_key(s:jobs, a:job_id)
return
endif
let job = s:jobs[a:job_id]
let l:listtype = "locationlist"
" usually there is always output so never branch into this clause
if empty(job.stdout)
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
unlet s:jobs[a:job_id]
return
endif
let errors = go#tool#ParseErrors(job.stdout)
let errors = go#tool#FilterValids(errors)
if !empty(errors)
" close terminal we don't need it anymore
close
call go#list#Populate(l:listtype, errors, job.cmd)
call go#list#Window(l:listtype, len(errors))
if !self.bang
call go#list#JumpToFirst(l:listtype)
endif
unlet s:jobs[a:job_id]
return
endif
" tests are passing clean the list and close the list. But we only can
" close them from a normal view, so jump back, close the list and then
" again jump back to the terminal
wincmd p
call go#list#Clean(l:listtype)
call go#list#Window(l:listtype)
wincmd p
unlet s:jobs[a:job_id]
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("vim-go")
}

View File

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("vim-go")
}

View File

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("vim-go")
}

View File

@ -0,0 +1,180 @@
if !exists("g:go_textobj_enabled")
let g:go_textobj_enabled = 1
endif
if !exists("g:go_textobj_include_function_doc")
let g:go_textobj_include_function_doc = 1
endif
" ( ) motions
" { } motions
" s for sentence
" p for parapgrah
" < >
" t for tag
function! go#textobj#Function(mode) abort
let offset = go#util#OffsetCursor()
let fname = shellescape(expand("%:p"))
if &modified
" Write current unsaved buffer to a temp file and use the modified content
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
let fname = l:tmpname
endif
let bin_path = go#path#CheckBinPath('motion')
if empty(bin_path)
return
endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset)
let command .= " -mode enclosing"
if g:go_textobj_include_function_doc
let command .= " -parse-comments"
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
call go#util#EchoError(out)
return
endif
" if exists, delete it as we don't need it anymore
if exists("l:tmpname")
call delete(l:tmpname)
endif
" convert our string dict representation into native Vim dictionary type
let result = eval(out)
if type(result) != 4 || !has_key(result, 'fn')
return
endif
let info = result.fn
if a:mode == 'a'
" anonymous functions doesn't have associated doc. Also check if the user
" want's to include doc comments for function declarations
if has_key(info, 'doc') && g:go_textobj_include_function_doc
call cursor(info.doc.line, info.doc.col)
else
call cursor(info.func.line, info.func.col)
endif
normal! v
call cursor(info.rbrace.line, info.rbrace.col)
return
endif
" rest is inner mode, a:mode == 'i'
" if the function is a one liner we need to select only that portion
if info.lbrace.line == info.rbrace.line
call cursor(info.lbrace.line, info.lbrace.col+1)
normal! v
call cursor(info.rbrace.line, info.rbrace.col-1)
return
endif
call cursor(info.lbrace.line+1, 1)
normal! V
call cursor(info.rbrace.line-1, 1)
endfunction
function! go#textobj#FunctionJump(mode, direction) abort
" get count of the motion. This should be done before all the normal
" expressions below as those reset this value(because they have zero
" count!). We abstract -1 because the index starts from 0 in motion.
let l:cnt = v:count1 - 1
" set context mark so we can jump back with '' or ``
normal! m'
" select already previously selected visual content and continue from there.
" If it's the first time starts with the visual mode. This is needed so
" after selecting something in visual mode, every consecutive motion
" continues.
if a:mode == 'v'
normal! gv
endif
let offset = go#util#OffsetCursor()
let fname = shellescape(expand("%:p"))
if &modified
" Write current unsaved buffer to a temp file and use the modified content
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
let fname = l:tmpname
endif
let bin_path = go#path#CheckBinPath('motion')
if empty(bin_path)
return
endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset)
let command .= ' -shift ' . l:cnt
if a:direction == 'next'
let command .= ' -mode next'
else " 'prev'
let command .= ' -mode prev'
endif
if g:go_textobj_include_function_doc
let command .= " -parse-comments"
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
call go#util#EchoError(out)
return
endif
" if exists, delete it as we don't need it anymore
if exists("l:tmpname")
call delete(l:tmpname)
endif
" convert our string dict representation into native Vim dictionary type
let result = eval(out)
if type(result) != 4 || !has_key(result, 'fn')
return
endif
" we reached the end and there are no functions. The usual [[ or ]] jumps to
" the top or bottom, we'll do the same.
if type(result) == 4 && has_key(result, 'err') && result.err == "no functions found"
if a:direction == 'next'
keepjumps normal! G
else " 'prev'
keepjumps normal! gg
endif
return
endif
let info = result.fn
" if we select something ,select all function
if a:mode == 'v' && a:direction == 'next'
keepjumps call cursor(info.rbrace.line, 1)
return
endif
if a:mode == 'v' && a:direction == 'prev'
if has_key(info, 'doc') && g:go_textobj_include_function_doc
keepjumps call cursor(info.doc.line, 1)
else
keepjumps call cursor(info.func.line, 1)
endif
return
endif
keepjumps call cursor(info.func.line, 1)
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,148 +1,177 @@
function! go#tool#Files()
if has ("win32")
let command = 'go list -f "{{range $f := .GoFiles}}{{$.Dir}}/{{$f}}{{printf \"\n\"}}{{end}}"'
else
" let command = "go list -f $'{{range $f := .GoFiles}}{{$.Dir}}/{{$f}}\n{{end}}'"
let command = "go list -f '{{range $f := .GoFiles}}{{$.Dir}}/{{$f}}{{printf \"\\n\"}}{{end}}'"
endif
let out = go#tool#ExecuteInDir(command)
return split(out, '\n')
function! go#tool#Files() abort
if go#util#IsWin()
let format = '{{range $f := .GoFiles}}{{$.Dir}}\{{$f}}{{printf \"\n\"}}{{end}}{{range $f := .CgoFiles}}{{$.Dir}}\{{$f}}{{printf \"\n\"}}{{end}}'
else
let format = "{{range $f := .GoFiles}}{{$.Dir}}/{{$f}}{{printf \"\\n\"}}{{end}}{{range $f := .CgoFiles}}{{$.Dir}}/{{$f}}{{printf \"\\n\"}}{{end}}"
endif
let command = 'go list -f '.shellescape(format)
let out = go#tool#ExecuteInDir(command)
return split(out, '\n')
endfunction
function! go#tool#Deps()
if has ("win32")
let command = 'go list -f "{{range $f := .Deps}}{{$f}}{{printf \"\n\"}}{{end}}"'
else
let command = "go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}'"
endif
let out = go#tool#ExecuteInDir(command)
return split(out, '\n')
function! go#tool#Deps() abort
if go#util#IsWin()
let format = '{{range $f := .Deps}}{{$f}}{{printf \"\n\"}}{{end}}'
else
let format = "{{range $f := .Deps}}{{$f}}\n{{end}}"
endif
let command = 'go list -f '.shellescape(format)
let out = go#tool#ExecuteInDir(command)
return split(out, '\n')
endfunction
function! go#tool#Imports()
let imports = {}
if has ("win32")
let command = 'go list -f "{{range $f := .Imports}}{{$f}}{{printf \"\n\"}}{{end}}"'
else
let command = "go list -f $'{{range $f := .Imports}}{{$f}}\n{{end}}'"
endif
let out = go#tool#ExecuteInDir(command)
if v:shell_error
echo out
return imports
endif
for package_path in split(out, '\n')
let package_name = fnamemodify(package_path, ":t")
let imports[package_name] = package_path
endfor
function! go#tool#Imports() abort
let imports = {}
if go#util#IsWin()
let format = '{{range $f := .Imports}}{{$f}}{{printf \"\n\"}}{{end}}'
else
let format = "{{range $f := .Imports}}{{$f}}{{printf \"\\n\"}}{{end}}"
endif
let command = 'go list -f '.shellescape(format)
let out = go#tool#ExecuteInDir(command)
if go#util#ShellError() != 0
echo out
return imports
endif
for package_path in split(out, '\n')
let cmd = "go list -f '{{.Name}}' " . shellescape(package_path)
let package_name = substitute(go#tool#ExecuteInDir(cmd), '\n$', '', '')
let imports[package_name] = package_path
endfor
return imports
endfunction
function! go#tool#ShowErrors(out)
let errors = []
for line in split(a:out, '\n')
let tokens = matchlist(line, '^\s*\(.\{-}\):\(\d\+\):\s*\(.*\)')
if !empty(tokens)
call add(errors, {"filename" : expand("%:p:h:") . "/" . tokens[1],
\"lnum": tokens[2],
\"text": tokens[3]})
elseif !empty(errors)
" Preserve indented lines.
" This comes up especially with multi-line test output.
if match(line, '^\s') >= 0
call add(errors, {"text": line})
endif
endif
endfor
function! go#tool#Info(auto) abort
let l:mode = get(g:, 'go_info_mode', 'gocode')
if l:mode == 'gocode'
call go#complete#Info(a:auto)
elseif l:mode == 'guru'
call go#guru#DescribeInfo()
else
call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [gocode, guru]')
endif
endfunction
if !empty(errors)
call setqflist(errors, 'r')
return
function! go#tool#PackageName() abort
let command = "go list -f \"{{.Name}}\""
let out = go#tool#ExecuteInDir(command)
if go#util#ShellError() != 0
return -1
endif
return split(out, '\n')[0]
endfunction
function! go#tool#ParseErrors(lines) abort
let errors = []
for line in a:lines
let fatalerrors = matchlist(line, '^\(fatal error:.*\)$')
let tokens = matchlist(line, '^\s*\(.\{-}\):\(\d\+\):\s*\(.*\)')
if !empty(fatalerrors)
call add(errors, {"text": fatalerrors[1]})
elseif !empty(tokens)
" strip endlines of form ^M
let out = substitute(tokens[3], '\r$', '', '')
call add(errors, {
\ "filename" : fnamemodify(tokens[1], ':p'),
\ "lnum" : tokens[2],
\ "text" : out,
\ })
elseif !empty(errors)
" Preserve indented lines.
" This comes up especially with multi-line test output.
if match(line, '^\s') >= 0
call add(errors, {"text": line})
endif
endif
endfor
return errors
endfunction
"FilterValids filters the given items with only items that have a valid
"filename. Any non valid filename is filtered out.
function! go#tool#FilterValids(items) abort
" Remove any nonvalid filename from the location list to avoid opening an
" empty buffer. See https://github.com/fatih/vim-go/issues/287 for
" details.
let filtered = []
let is_readable = {}
for item in a:items
if has_key(item, 'bufnr')
let filename = bufname(item.bufnr)
elseif has_key(item, 'filename')
let filename = item.filename
else
" nothing to do, add item back to the list
call add(filtered, item)
continue
endif
if empty(errors)
" Couldn't detect error format, output errors
echo a:out
if !has_key(is_readable, filename)
let is_readable[filename] = filereadable(filename)
endif
if is_readable[filename]
call add(filtered, item)
endif
endfor
for k in keys(filter(is_readable, '!v:val'))
echo "vim-go: " | echohl Identifier | echon "[run] Dropped " | echohl Constant | echon '"' . k . '"'
echohl Identifier | echon " from location list (nonvalid filename)" | echohl None
endfor
return filtered
endfunction
function! go#tool#ExecuteInDir(cmd) abort
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd.'`=expand("%:p:h")`'
let out = system(a:cmd)
finally
execute cd.'`=dir`'
endtry
return out
let old_gopath = $GOPATH
let old_goroot = $GOROOT
let $GOPATH = go#path#Detect()
let $GOROOT = go#util#env("goroot")
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd . fnameescape(expand("%:p:h"))
let out = go#util#System(a:cmd)
finally
execute cd . fnameescape(dir)
endtry
let $GOROOT = old_goroot
let $GOPATH = old_gopath
return out
endfunction
" Exists checks whether the given importpath exists or not. It returns 0 if
" the importpath exists under GOPATH.
function! go#tool#Exists(importpath)
function! go#tool#Exists(importpath) abort
let command = "go list ". a:importpath
let out = go#tool#ExecuteInDir(command)
if v:shell_error
if go#util#ShellError() != 0
return -1
endif
return 0
endfunction
" BinPath checks whether the given binary exists or not and returns the path
" of the binary. It returns an empty string doesn't exists.
function! go#tool#BinPath(binpath)
" remove whitespaces if user applied something like 'goimports '
let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '')
" if it's in PATH just return it
if executable(binpath)
return binpath
endif
" just get the basename
let basename = fnamemodify(binpath, ":t")
" check if we have an appropriate bin_path
let go_bin_path = GetBinPath()
if empty(go_bin_path)
echo "vim-go: could not find '" . basename . "'. Run :GoInstallBinaries to fix it."
return ""
endif
" append our GOBIN and GOPATH paths and be sure they can be found there...
" let us search in our GOBIN and GOPATH paths
let old_path = $PATH
let $PATH = $PATH . ":" .go_bin_path
if !executable(binpath)
echo "vim-go: could not find '" . basename . "'. Run :GoInstallBinaries to fix it."
return ""
endif
" restore back!
if go_bin_path
let $PATH = old_path
endif
return go_bin_path . '/' . basename
endfunction
" following two functions are from: https://github.com/mattn/gist-vim
" following two functions are from: https://github.com/mattn/gist-vim
" thanks @mattn
function! s:get_browser_command()
function! s:get_browser_command() abort
let go_play_browser_command = get(g:, 'go_play_browser_command', '')
if go_play_browser_command == ''
if has('win32') || has('win64')
if go#util#IsWin()
let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%'
elseif has('mac') || has('macunix') || has('gui_macvim') || system('uname') =~? '^darwin'
elseif has('mac') || has('macunix') || has('gui_macvim') || go#util#System('uname') =~? '^darwin'
let go_play_browser_command = 'open %URL%'
elseif executable('xdg-open')
let go_play_browser_command = 'xdg-open %URL%'
@ -155,7 +184,7 @@ function! s:get_browser_command()
return go_play_browser_command
endfunction
function! go#tool#OpenBrowser(url)
function! go#tool#OpenBrowser(url) abort
let cmd = s:get_browser_command()
if len(cmd) == 0
redraw
@ -166,16 +195,15 @@ function! go#tool#OpenBrowser(url)
return
endif
if cmd =~ '^!'
let cmd = substitute(cmd, '%URL%', '\=shellescape(a:url)', 'g')
let cmd = substitute(cmd, '%URL%', '\=escape(shellescape(a:url),"#")', 'g')
silent! exec cmd
elseif cmd =~ '^:[A-Z]'
let cmd = substitute(cmd, '%URL%', '\=a:url', 'g')
let cmd = substitute(cmd, '%URL%', '\=escape(a:url,"#")', 'g')
exec cmd
else
let cmd = substitute(cmd, '%URL%', '\=shellescape(a:url)', 'g')
call system(cmd)
call go#util#System(cmd)
endif
endfunction
" vim:ts=4:sw=4:et
" vim: sw=2 ts=2 et

View File

@ -1,89 +1,114 @@
let s:buf_nr = -1
"OpenWindow opens a new scratch window and put's the content into the window
function! go#ui#OpenWindow(content)
" reuse existing buffer window if it exists otherwise create a new one
if !bufexists(s:buf_nr)
execute 'botright new'
file `="[Implements]"`
let s:buf_nr = bufnr('%')
elseif bufwinnr(s:buf_nr) == -1
execute 'botright new'
execute s:buf_nr . 'buffer'
elseif bufwinnr(s:buf_nr) != bufwinnr('%')
execute bufwinnr(s:buf_nr) . 'wincmd w'
endif
function! go#ui#OpenWindow(title, content, filetype) abort
" Ensure there's only one return window in this session/tabpage
call go#util#Windo("unlet! w:vim_go_return_window")
" Mark the window we're leaving as such
let w:vim_go_return_window = 1
" reuse existing buffer window if it exists otherwise create a new one
if !bufexists(s:buf_nr)
execute 'botright new'
file `="[" . a:title . "]"`
let s:buf_nr = bufnr('%')
elseif bufwinnr(s:buf_nr) == -1
execute 'botright new'
execute s:buf_nr . 'buffer'
elseif bufwinnr(s:buf_nr) != bufwinnr('%')
execute bufwinnr(s:buf_nr) . 'wincmd w'
endif
" Keep minimum height to 10, if there is more just increase it that it
" occupies all results
let implements_height = 10
if len(a:content) < implements_height
exe 'resize ' . implements_height
else
exe 'resize ' . len(a:content)
endif
" some sane default values for a readonly buffer
setlocal filetype=vimgo
setlocal bufhidden=delete
setlocal buftype=nofile
setlocal noswapfile
setlocal nobuflisted
setlocal winfixheight
setlocal cursorline " make it easy to distinguish
" Resize window to content length
exe 'resize' . len(a:content)
" we need this to purge the buffer content
setlocal modifiable
execute "setlocal filetype=".a:filetype
"delete everything first from the buffer
%delete _
" some sane default values for a readonly buffer
setlocal bufhidden=delete
setlocal buftype=nofile
setlocal noswapfile
setlocal nobuflisted
setlocal winfixheight
setlocal cursorline " make it easy to distinguish
setlocal nonumber
setlocal norelativenumber
setlocal showbreak=""
" add the content
call append(0, a:content)
" we need this to purge the buffer content
setlocal modifiable
" delete last line that comes from the append call
$delete _
"delete everything first from the buffer
%delete _
" set it back to non modifiable
setlocal nomodifiable
" add the content
call append(0, a:content)
" delete last line that comes from the append call
$delete _
" set it back to non modifiable
setlocal nomodifiable
" Remove the '... [New File]' message line from the command line
echon
endfunction
function! go#ui#GetReturnWindow() abort
for l:wn in range(1, winnr("$"))
if !empty(getwinvar(l:wn, "vim_go_return_window"))
return l:wn
endif
endfor
endfunction
" CloseWindow closes the current window
function! go#ui#CloseWindow()
close
echo ""
function! go#ui#CloseWindow() abort
" Close any window associated with the ui buffer, if it's there
if bufexists(s:buf_nr)
let ui_window_number = bufwinnr(s:buf_nr)
if ui_window_number != -1
execute ui_window_number . 'close'
endif
endif
"return to original window, if it's there
let l:rw = go#ui#GetReturnWindow()
if !empty(l:rw)
execute l:rw . 'wincmd w'
unlet! w:vim_go_return_window
endif
endfunction
" OpenDefinition parses the current line and jumps to it by openening a new
" tab
function! go#ui#OpenDefinition()
let curline = getline('.')
function! go#ui#OpenDefinition(filter) abort
let curline = getline('.')
" don't touch our first line and any blank line
if curline =~ "implements" || curline =~ "^$"
" supress information about calling this function
echo ""
return
endif
" don't touch our first line or any blank line
if curline =~ a:filter || curline =~ "^$"
" suppress information about calling this function
echo ""
return
endif
" format: 'interface file:lnum:coln'
let mx = '^\(^\S*\)\s*\(.\{-}\):\(\d\+\):\(\d\+\)'
" format: 'interface file:lnum:coln'
let mx = '^\(^\S*\)\s*\(.\{-}\):\(\d\+\):\(\d\+\)'
" parse it now into the list
let tokens = matchlist(curline, mx)
" parse it now into the list
let tokens = matchlist(curline, mx)
" convert to: 'file:lnum:coln'
let expr = tokens[2] . ":" . tokens[3] . ":" . tokens[4]
" convert to: 'file:lnum:coln'
let expr = tokens[2] . ":" . tokens[3] . ":" . tokens[4]
" jump to it in a new tab, we use explicit lgetexpr so we can later change
" the behaviour via settings (like opening in vsplit instead of tab)
lgetexpr expr
tab split
ll 1
" jump to it in a new tab, we use explicit lgetexpr so we can later change
" the behaviour via settings (like opening in vsplit instead of tab)
lgetexpr expr
tab split
ll 1
" center the word
norm! zz
" center the word
norm! zz
endfunction
" vim: sw=2 ts=2 et

View File

@ -0,0 +1,321 @@
" PathSep returns the appropriate OS specific path separator.
function! go#util#PathSep() abort
if go#util#IsWin()
return '\'
endif
return '/'
endfunction
" PathListSep returns the appropriate OS specific path list separator.
function! go#util#PathListSep() abort
if go#util#IsWin()
return ";"
endif
return ":"
endfunction
" LineEnding returns the correct line ending, based on the current fileformat
function! go#util#LineEnding() abort
if &fileformat == 'dos'
return "\r\n"
elseif &fileformat == 'mac'
return "\r"
endif
return "\n"
endfunction
" Join joins any number of path elements into a single path, adding a
" Separator if necessary and returns the result
function! go#util#Join(...) abort
return join(a:000, go#util#PathSep())
endfunction
" IsWin returns 1 if current OS is Windows or 0 otherwise
function! go#util#IsWin() abort
let win = ['win16', 'win32', 'win64', 'win95']
for w in win
if (has(w))
return 1
endif
endfor
return 0
endfunction
function! go#util#has_job() abort
" job was introduced in 7.4.xxx however there are multiple bug fixes and one
" of the latest is 8.0.0087 which is required for a stable async API.
return has('job') && has("patch-8.0.0087")
endfunction
let s:env_cache = {}
" env returns the go environment variable for the given key. Where key can be
" GOARCH, GOOS, GOROOT, etc... It caches the result and returns the cached
" version.
function! go#util#env(key) abort
let l:key = tolower(a:key)
if has_key(s:env_cache, l:key)
return s:env_cache[l:key]
endif
if executable('go')
let l:var = call('go#util#'.l:key, [])
if go#util#ShellError() != 0
call go#util#EchoError(printf("'go env %s' failed", toupper(l:key)))
return ''
endif
else
let l:var = eval("$".toupper(a:key))
endif
let s:env_cache[l:key] = l:var
return l:var
endfunction
function! go#util#goarch() abort
return substitute(go#util#System('go env GOARCH'), '\n', '', 'g')
endfunction
function! go#util#goos() abort
return substitute(go#util#System('go env GOOS'), '\n', '', 'g')
endfunction
function! go#util#goroot() abort
return substitute(go#util#System('go env GOROOT'), '\n', '', 'g')
endfunction
function! go#util#gopath() abort
return substitute(go#util#System('go env GOPATH'), '\n', '', 'g')
endfunction
function! go#util#osarch() abort
return go#util#goos() . '_' . go#util#goarch()
endfunction
" System runs a shell command. It will reset the shell to /bin/sh for Unix-like
" systems if it is executable.
function! go#util#System(str, ...) abort
let l:shell = &shell
if !go#util#IsWin() && executable('/bin/sh')
let &shell = '/bin/sh'
endif
try
let l:output = call('system', [a:str] + a:000)
return l:output
finally
let &shell = l:shell
endtry
endfunction
function! go#util#ShellError() abort
return v:shell_error
endfunction
" StripPath strips the path's last character if it's a path separator.
" example: '/foo/bar/' -> '/foo/bar'
function! go#util#StripPathSep(path) abort
let last_char = strlen(a:path) - 1
if a:path[last_char] == go#util#PathSep()
return strpart(a:path, 0, last_char)
endif
return a:path
endfunction
" StripTrailingSlash strips the trailing slash from the given path list.
" example: ['/foo/bar/'] -> ['/foo/bar']
function! go#util#StripTrailingSlash(paths) abort
return map(copy(a:paths), 'go#util#StripPathSep(v:val)')
endfunction
" Shelljoin returns a shell-safe string representation of arglist. The
" {special} argument of shellescape() may optionally be passed.
function! go#util#Shelljoin(arglist, ...) abort
try
let ssl_save = &shellslash
set noshellslash
if a:0
return join(map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')'), ' ')
endif
return join(map(copy(a:arglist), 'shellescape(v:val)'), ' ')
finally
let &shellslash = ssl_save
endtry
endfunction
fu! go#util#Shellescape(arg)
try
let ssl_save = &shellslash
set noshellslash
return shellescape(a:arg)
finally
let &shellslash = ssl_save
endtry
endf
" Shelllist returns a shell-safe representation of the items in the given
" arglist. The {special} argument of shellescape() may optionally be passed.
function! go#util#Shelllist(arglist, ...) abort
try
let ssl_save = &shellslash
set noshellslash
if a:0
return map(copy(a:arglist), 'shellescape(v:val, ' . a:1 . ')')
endif
return map(copy(a:arglist), 'shellescape(v:val)')
finally
let &shellslash = ssl_save
endtry
endfunction
" Returns the byte offset for line and column
function! go#util#Offset(line, col) abort
if &encoding != 'utf-8'
let sep = go#util#LineEnding()
let buf = a:line == 1 ? '' : (join(getline(1, a:line-1), sep) . sep)
let buf .= a:col == 1 ? '' : getline('.')[:a:col-2]
return len(iconv(buf, &encoding, 'utf-8'))
endif
return line2byte(a:line) + (a:col-2)
endfunction
"
" Returns the byte offset for the cursor
function! go#util#OffsetCursor() abort
return go#util#Offset(line('.'), col('.'))
endfunction
" Windo is like the built-in :windo, only it returns to the window the command
" was issued from
function! go#util#Windo(command) abort
let s:currentWindow = winnr()
try
execute "windo " . a:command
finally
execute s:currentWindow. "wincmd w"
unlet s:currentWindow
endtry
endfunction
" snippetcase converts the given word to given preferred snippet setting type
" case.
function! go#util#snippetcase(word) abort
let l:snippet_case = get(g:, 'go_snippet_case_type', "snakecase")
if l:snippet_case == "snakecase"
return go#util#snakecase(a:word)
elseif l:snippet_case == "camelcase"
return go#util#camelcase(a:word)
else
return a:word " do nothing
endif
endfunction
" snakecase converts a string to snake case. i.e: FooBar -> foo_bar
" Copied from tpope/vim-abolish
function! go#util#snakecase(word) abort
let word = substitute(a:word,'::','/','g')
let word = substitute(word,'\(\u\+\)\(\u\l\)','\1_\2','g')
let word = substitute(word,'\(\l\|\d\)\(\u\)','\1_\2','g')
let word = substitute(word,'[.-]','_','g')
let word = tolower(word)
return word
endfunction
" camelcase converts a string to camel case. i.e: FooBar -> fooBar
" Copied from tpope/vim-abolish
function! go#util#camelcase(word) abort
let word = substitute(a:word, '-', '_', 'g')
if word !~# '_' && word =~# '\l'
return substitute(word,'^.','\l&','')
else
return substitute(word,'\C\(_\)\=\(.\)','\=submatch(1)==""?tolower(submatch(2)) : toupper(submatch(2))','g')
endif
endfunction
function! go#util#AddTags(line1, line2, ...) abort
" default is json
let l:keys = ["json"]
if a:0
let l:keys = a:000
endif
let l:line1 = a:line1
let l:line2 = a:line2
" If we're inside a struct and just call this function let us add the tags
" to all fields
" TODO(arslan): I don't like using patterns. Check if we can move it to
" `motion` and do it via AST based position
let ln1 = searchpair('struct {', '', '}', 'bcnW')
if ln1 == 0
echon "vim-go: " | echohl ErrorMsg | echon "cursor is outside the struct" | echohl None
return
endif
" searchpair only returns a single position
let ln2 = search('}', "cnW")
" if no range is given we apply for the whole struct
if l:line1 == l:line2
let l:line1 = ln1 + 1
let l:line2 = ln2 - 1
endif
for line in range(l:line1, l:line2)
" get the field name (word) that are not part of a commented line
let l:matched = matchstr(getline(line), '\(\/\/.*\)\@<!\w\+')
if empty(l:matched)
continue
endif
let word = go#util#snippetcase(l:matched)
let tags = map(copy(l:keys), 'printf("%s:%s", v:val,"\"'. word .'\"")')
let updated_line = printf("%s `%s`", getline(line), join(tags, " "))
" finally, update the line inplace
call setline(line, updated_line)
endfor
endfunction
" TODO(arslan): I couldn't parameterize the highlight types. Check if we can
" simplify the following functions
"
" NOTE(arslan): echon doesn't work well with redraw, thus echo doesn't print
" even though we order it. However echom seems to be work fine.
function! go#util#EchoSuccess(msg)
redraw | echohl Function | echom "vim-go: " . a:msg | echohl None
endfunction
function! go#util#EchoError(msg)
redraw | echohl ErrorMsg | echom "vim-go: " . a:msg | echohl None
endfunction
function! go#util#EchoWarning(msg)
redraw | echohl WarningMsg | echom "vim-go: " . a:msg | echohl None
endfunction
function! go#util#EchoProgress(msg)
redraw | echohl Identifier | echom "vim-go: " . a:msg | echohl None
endfunction
function! go#util#EchoInfo(msg)
redraw | echohl Debug | echom "vim-go: " . a:msg | echohl None
endfunction
function! go#util#GetLines()
let buf = getline(1, '$')
if &encoding != 'utf-8'
let buf = map(buf, 'iconv(v:val, &encoding, "utf-8")')
endif
if &l:fileformat == 'dos'
" XXX: line2byte() depend on 'fileformat' option.
" so if fileformat is 'dos', 'buf' must include '\r'.
let buf = map(buf, 'v:val."\r"')
endif
return buf
endfunction
" vim: sw=2 ts=2 et

View File

@ -1,135 +0,0 @@
" json
" Last Change: 2012-03-08
" Maintainer: Yasuhiro Matsumoto <mattn.jp@gmail.com>
" License: This file is placed in the public domain.
" Reference:
"
let s:save_cpo = &cpo
set cpo&vim
function! webapi#json#null()
return 0
endfunction
function! webapi#json#true()
return 1
endfunction
function! webapi#json#false()
return 0
endfunction
function! s:nr2byte(nr)
if a:nr < 0x80
return nr2char(a:nr)
elseif a:nr < 0x800
return nr2char(a:nr/64+192).nr2char(a:nr%64+128)
else
return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
endif
endfunction
function! s:nr2enc_char(charcode)
if &encoding == 'utf-8'
return nr2char(a:charcode)
endif
let char = s:nr2byte(a:charcode)
if strlen(char) > 1
let char = strtrans(iconv(char, 'utf-8', &encoding))
endif
return char
endfunction
function! s:fixup(val, tmp)
if type(a:val) == 0
return a:val
elseif type(a:val) == 1
if a:val == a:tmp.'null'
return function('webapi#json#null')
elseif a:val == a:tmp.'true'
return function('webapi#json#true')
elseif a:val == a:tmp.'false'
return function('webapi#json#false')
endif
return a:val
elseif type(a:val) == 2
return a:val
elseif type(a:val) == 3
return map(a:val, 's:fixup(v:val, a:tmp)')
elseif type(a:val) == 4
return map(a:val, 's:fixup(v:val, a:tmp)')
else
return string(a:val)
endif
endfunction
function! webapi#json#decode(json)
let json = iconv(a:json, "utf-8", &encoding)
if get(g:, 'webapi#json#parse_strict', 1) == 1 && substitute(substitute(substitute(
\ json,
\ '\\\%(["\\/bfnrt]\|u[0-9a-fA-F]\{4}\)', '\@', 'g'),
\ '"[^\"\\\n\r]*\"\|true\|false\|null\|-\?\d\+'
\ . '\%(\.\d*\)\?\%([eE][+\-]\{-}\d\+\)\?', ']', 'g'),
\ '\%(^\|:\|,\)\%(\s*\[\)\+', '', 'g') !~ '^[\],:{} \t\n]*$'
throw json
endif
let json = substitute(json, '\n', '', 'g')
let json = substitute(json, '\\u34;', '\\"', 'g')
if v:version >= 703 && has('patch780')
let json = substitute(json, '\\u\(\x\x\x\x\)', '\=iconv(nr2char(str2nr(submatch(1), 16), 1), "utf-8", &encoding)', 'g')
else
let json = substitute(json, '\\u\(\x\x\x\x\)', '\=s:nr2enc_char("0x".submatch(1))', 'g')
endif
if get(g:, 'webapi#json#allow_nil', 0) != 0
let tmp = '__WEBAPI_JSON__'
while 1
if stridx(json, tmp) == -1
break
endif
let tmp .= '_'
endwhile
let [null,true,false] = [
\ tmp.'null',
\ tmp.'true',
\ tmp.'false']
sandbox let ret = eval(json)
call s:fixup(ret, tmp)
else
let [null,true,false] = [0,1,0]
sandbox let ret = eval(json)
endif
return ret
endfunction
function! webapi#json#encode(val)
if type(a:val) == 0
return a:val
elseif type(a:val) == 1
let json = '"' . escape(a:val, '\"') . '"'
let json = substitute(json, "\r", '\\r', 'g')
let json = substitute(json, "\n", '\\n', 'g')
let json = substitute(json, "\t", '\\t', 'g')
let json = substitute(json, '\([[:cntrl:]]\)', '\=printf("\x%02d", char2nr(submatch(1)))', 'g')
return iconv(json, &encoding, "utf-8")
elseif type(a:val) == 2
let s = string(a:val)
if s == "function('webapi#json#null')"
return 'null'
elseif s == "function('webapi#json#true')"
return 'true'
elseif s == "function('webapi#json#false')"
return 'false'
endif
elseif type(a:val) == 3
return '[' . join(map(copy(a:val), 'webapi#json#encode(v:val)'), ',') . ']'
elseif type(a:val) == 4
return '{' . join(map(keys(a:val), 'webapi#json#encode(v:val).":".webapi#json#encode(a:val[v:val])'), ',') . '}'
else
return string(a:val)
endif
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et: