Skip to content

Commit

Permalink
Merge pull request #607 from fatih/nvim
Browse files Browse the repository at this point in the history
Neovim integration
  • Loading branch information
fatih committed Dec 8, 2015
2 parents 2bdc17a + f772a42 commit 83e4afc
Show file tree
Hide file tree
Showing 9 changed files with 427 additions and 50 deletions.
97 changes: 56 additions & 41 deletions autoload/go/cmd.vim
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,30 @@ endfunction
" 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, ...)
let default_makeprg = &makeprg
" expand all wildcards(i.e: '%' to the current file name)
let goargs = map(copy(a:000), "expand(v:val)")

let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
" escape all shell arguments before we pass it to make
let goargs = go#util#Shelllist(goargs, 1)

let l:tmpname = tempname()
" 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 v:shell_error
let &makeprg = "go build . errors"
else
" :make expands '%' and '#' wildcards, so they must also be escaped
let goargs = go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
let gofiles = go#util#Shelljoin(go#tool#Files(), 1)
let &makeprg = "go build -o " . l:tmpname . ' ' . goargs . ' ' . gofiles
" if we have nvim, call it asynchronously and return early ;)
if has('nvim')
call go#jobcontrol#Spawn("build", args)
return
endif

echon "vim-go: " | echohl Identifier | echon "building ..."| echohl None
let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()
let default_makeprg = &makeprg
let &makeprg = "go " . join(args, ' ')

if g:go_dispatch_enabled && exists(':Make') == 2
call go#util#EchoProgress("building dispatched ...")
silent! exe 'Make'
else
silent! exe 'lmake!'
Expand All @@ -42,25 +48,35 @@ function! go#cmd#Build(bang, ...)
let errors = go#list#Get()
call go#list#Window(len(errors))

if !empty(errors)
if !empty(errors)
if !a:bang
call go#list#JumpToFirst()
endif
else
redraws! | echon "vim-go: " | echohl Function | echon "[build] SUCCESS"| echohl None
call go#util#EchoSuccess("[build] SUCCESS")
endif


call delete(l:tmpname)
let &makeprg = default_makeprg
let $GOPATH = old_gopath
endfunction


" Run runs the current file (and their dependencies if any) in a new terminal.
function! go#cmd#RunTerm(mode)
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
call go#term#newmode(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, ...)
if has('nvim')
call go#cmd#RunTerm('')
return
endif

let old_gopath = $GOPATH
let $GOPATH = go#path#Detect()

Expand Down Expand Up @@ -90,27 +106,8 @@ function! go#cmd#Run(bang, ...)
exe 'lmake!'
endif

" 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 items = go#list#Get()
let errors = []
let is_readable = {}

for item in items
let filename = bufname(item.bufnr)
if !has_key(is_readable, filename)
let is_readable[filename] = filereadable(filename)
endif
if is_readable[filename]
call add(errors, 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
let errors = go#tool#FilterValids(items)

call go#list#Populate(errors)
call go#list#Window(len(errors))
Expand Down Expand Up @@ -149,33 +146,51 @@ endfunction
" 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, ...)
let command = "go test "
let args = ["test"]

" don't run the test, only compile it. Useful to capture and fix errors or
" to create a test binary.
if a:compile
let command .= "-c "
call add(args, "-c")
endif

if a:0
let command .= go#util#Shelljoin(map(copy(a:000), "expand(v:val)"))
" 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 test
call extend(args, go#util#Shelllist(goargs, 1))
else
" only add this if no custom flags are passed
let timeout = get(g:, 'go_test_timeout', '10s')
let command .= "-timeout=" . timeout . " "
call add(args, printf("-timeout=%s", timeout))
endif

if has('nvim')
if get(g:, 'go_term_enabled', 0)
call go#term#new(["go"] + args)
else
call go#jobcontrol#Spawn("test", args)
endif
return
endif

call go#cmd#autowrite()
if a:compile
echon "vim-go: " | echohl Identifier | echon "compiling tests ..." | echohl None
else
echon "vim-go: " | echohl Identifier | echon "testing ..." | echohl None
endif

call go#cmd#autowrite()
redraw

let command = "go " . join(args, ' ')

let out = go#tool#ExecuteInDir(command)
if v:shell_error
let errors = go#tool#ParseErrors(split(out, '\n'))
let errors = go#tool#FilterValids(errors)

call go#list#Populate(errors)
call go#list#Window(len(errors))
if !empty(errors) && !a:bang
Expand Down
12 changes: 9 additions & 3 deletions autoload/go/fmt.vim
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ if !exists("g:go_fmt_experimental")
let g:go_fmt_experimental = 0
endif

let s:got_fmt_error = 0

" 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
Expand Down Expand Up @@ -117,9 +119,12 @@ function! go#fmt#Format(withGoimport)
let &fileformat = old_fileformat
let &syntax = &syntax

" clean up previous location list
call go#list#Clean()
call go#list#Window()
" clean up previous location list, but only if it's due fmt
if s:got_fmt_error
let s:got_fmt_error = 0
call go#list#Clean()
call go#list#Window()
endif
elseif g:go_fmt_fail_silently == 0
let splitted = split(out, '\n')
"otherwise get the errors and put them to location list
Expand All @@ -141,6 +146,7 @@ function! go#fmt#Format(withGoimport)
echohl Error | echomsg "Gofmt returned error" | echohl None
endif

let s:got_fmt_error = 1
call go#list#Window(len(errors))

" We didn't use the temp file, so clean up
Expand Down
168 changes: 168 additions & 0 deletions autoload/go/jobcontrol.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
" s:jobs is a global reference to all jobs started with Spawn() or with the
" internal function s:spawn
let s:jobs = {}

" 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(desc, args)
" autowrite is not enabled for jobs
call go#cmd#autowrite()

let job = s:spawn(a:desc, a:args)
return job.id
endfunction

" Statusline returns the current status of the job
function! go#jobcontrol#Statusline() abort
if empty(s:jobs)
return ''
endif

let import_path = go#package#ImportPath(expand('%:p:h'))

for job in values(s:jobs)
if job.importpath != import_path
continue
endif

if job.state == "SUCCESS"
return ''
endif

return printf("%s ... [%s]", job.desc, job.state)
endfor

return ''
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(desc, args)
let job = {
\ 'desc': a:desc,
\ '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'),
\ }

" 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()

execute cd . fnameescape(expand("%:p:h"))

" append the subcommand, such as 'build'
let argv = ['go'] + a:args

" run, forrest, run!
let id = jobstart(argv, job)
let job.id = id
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, data)
let std_combined = self.stderr + self.stdout
if empty(std_combined)
call go#list#Clean()
call go#list#Window()

let self.state = "SUCCESS"
return
endif

let errors = go#tool#ParseErrors(std_combined)
let errors = go#tool#FilterValids(errors)

if !len(errors)
" no errors could be past, just return
call go#list#Clean()
call go#list#Window()

let self.state = "SUCCESS"
return
endif

let self.state = "FAILED"

" if we are still in the same windows show the list
if self.winnr == winnr()
call go#list#Populate(errors)
call go#list#Window(len(errors))
call go#list#JumpToFirst()
endif
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)
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)
call extend(self.stderr, a:data)
endfunction

" abort_all aborts all current jobs created with s:spawn()
function! s:abort_all()
if empty(s:jobs)
return
endif

for id in keys(s:jobs)
if id > 0
silent! call jobstop(id)
endif
endfor

let s:jobs = {}
endfunction

" abort aborts the job with the given name, where name is the first argument
" passed to s:spawn()
function! s:abort(path)
if empty(s:jobs)
return
endif

for job in values(s:jobs)
if job.importpath == path && job.id > 0
silent! call jobstop(job.id)
unlet s:jobs['job.id']
endif
endfor
endfunction

" vim:ts=2:sw=2:et
4 changes: 4 additions & 0 deletions autoload/go/list.vim
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ function! go#list#Populate(items)
call setloclist(0, a:items, 'r')
endfunction

function! go#list#PopulateWin(winnr, items)
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(errformat, items)
Expand Down
Loading

0 comments on commit 83e4afc

Please sign in to comment.