Update vim-go to v1.20

This commit is contained in:
Anthony Rose 2019-06-03 15:58:18 +01:00
parent b815d29703
commit 566926ad64
114 changed files with 8345 additions and 2826 deletions

View file

@ -6,6 +6,7 @@ coverage:
target: auto
threshold: 1
base: auto
patch: off
comment: false
ignore:
- "!autoload/go/*.vim$"

View file

@ -1,3 +1,3 @@
[run]
plugins = covimerage
data_file = .coverage.covimerage
data_file = .coverage_covimerage

View file

@ -1,2 +1,6 @@
.local/
.config/
.cache/
.dlv/
.git/
.viminfo

View file

@ -9,14 +9,13 @@
### Configuration (**MUST** fill this out):
* Vim version (first two lines from `:version`):
* vim-go version:
* `vimrc` you used to reproduce (use a *minimal* vimrc with other plugins disabled; do not link to a 2,000 line vimrc):
* Vim version (first three lines from `:version`):
* Go version (`go version`):
* Go environment (`go env`):
* vim-go version:
* `vimrc` you used to reproduce (use a *minimal* vimrc with other plugins disabled; do not link to a 2,000 line vimrc):

View file

@ -1,4 +1,6 @@
language: go
go:
- 1.12.1
notifications:
email: false
matrix:
@ -6,9 +8,16 @@ matrix:
- env: SCRIPT="test -c" VIM_VERSION=vim-7.4
- env: SCRIPT="test -c" VIM_VERSION=vim-8.0
- env: SCRIPT="test -c" VIM_VERSION=nvim
- env: SCRIPT=lint VIM_VERSION=vim-8.0
- env: ENV=vimlint SCRIPT=lint VIM_VERSION=vim-8.0
language: python
python: 3.6
install:
- ./scripts/install-vim $VIM_VERSION
- pip install --user vim-vint covimerage codecov
- |
if [ "$ENV" = "vimlint" ]; then
pip install vim-vint covimerage codecov pathlib
else
pip install --user vim-vint covimerage codecov pathlib
fi
script:
- ./scripts/$SCRIPT $VIM_VERSION

View file

@ -1,11 +1,314 @@
## unplanned
## 1.20 - (April 22, 2019)
FEATURES:
* ***gopls support!***
* use gopls for autocompletion by default in Vim8 and Neovim.
* use gopls for `:GoDef` by setting `g:go_def_mode='gopls'`.
* use gopls for `:GoInfo` by setting `g:go_info_mode='gopls'`.
* Add support for golangci-lint.
* set `g:go_metalinter_command='golangci-lint'` to use golangci-lint instead
of gometalinter.
* New `:GoDefType` command to jump to a type definition from an instance of the
type.
BACKWARDS INCOMPATABILITIES:
* `g:go_highlight_function_arguments` is renamed to `g:go_highlight_function_parameters`
[[GH-2117]](https://github.com/fatih/vim-go/pull/2117)
IMPROVEMENTS:
* Disable `g:go_gocode_propose_source` by default.
[[GH-2050]](https://github.com/fatih/vim-go/pull/2050)
* Don't spam users when Vim is run with vi compatibility.
[[GH-2055]](https://github.com/fatih/vim-go/pull/2055)
* Add bang support to lint commands to allow them to be run without jumping to
errors.
[[GH-2056]](https://github.com/fatih/vim-go/pull/2056)
* Use `go doc` for `:GoDoc` instead of `godoc`.
[[GH-2070]](https://github.com/fatih/vim-go/pull/2070)
* Detach from and shutdown dlv correctly.
[[GH-2075]](https://github.com/fatih/vim-go/pull/2075)
* Do not require `'autowrite'` or `'autowriteall'` to be set when using
autocompletion in module mode.
[[GH-2091]](https://github.com/fatih/vim-go/pull/2091)
* Fix use of `g:go_metalinter_command` _and_ apply it even when autosaving.
[[GH-2101]](https://github.com/fatih/vim-go/pull/2101)
* Report errors in quickfix when Delve fails to start (e.g. compiler errors).
[[GH-2111]](https://github.com/fatih/vim-go/pull/2111)
* Support `'undo_ftplugin'`, make most autocmds buffer-local, and only do the
bare minimum based on file names alone.
[[GH-2108]](https://github.com/fatih/vim-go/pull/2108)
* Write a message when `:GoInfo` can't display any results when `g:go_info_mode='gocode'`.
[[GH-2122]](https://github.com/fatih/vim-go/pull/2122)
* Highlight fields followed by an operator when `g:go_highlight_fields` is set.
[[GH-1907]](https://github.com/fatih/vim-go/pull/1907)
* Skip autosave actions when the buffer is not a readable file.
[[GH-2143]](https://github.com/fatih/vim-go/pull/2143)
* Run `godef` from the current buffer's directory to make sure it works with modules.
[[GH-2150]](https://github.com/fatih/vim-go/pull/2150)
* Add a function, `go#tool#DescribeBalloon`, to show information in a balloon
with `'balloonexpr'`. (Vim8 only).
[[GH-1975]](https://github.com/fatih/vim-go/pull/1975)
* Add initial support for `gopls`.
[[GH-2163]](https://github.com/fatih/vim-go/pull/2163).
* Add `:GoDefType` to jump to the type definition of the identifier under the
cursor.
[[GH-2165]](https://github.com/fatih/vim-go/pull/2165)
* Notify gopls about changes.
[[GH-2171]](https://github.com/fatih/vim-go/pull/2171)
* Respect `g:go_jump_to_error` when running `gometalinter` automatically on
save. [[GH-2176]](https://github.com/fatih/vim-go/pull/2176)
* Use gopls for code completion by default in Vim8 and Neovim.
[[GH-2172]](https://github.com/fatih/vim-go/pull/2172)
* Add support for golangci-lint.
[[GH-2182]](https://github.com/fatih/vim-go/pull/2182)
* Show hover balloon using gopls instead of gocode.
[[GH-2202]](https://github.com/fatih/vim-go/pull/2202)
* Add a new option, `g:go_debug_log_output`, to control logging with the
debugger.
[[GH-2203]](https://github.com/fatih/vim-go/pull/2203)
* Do not jump to quickfix or location list window when bang is used for async
jobs or linting.
[[GH-2205]](https://github.com/fatih/vim-go/pull/2205)
* Tab complete package names for commands from vendor directories and in
modules.
[[GH-2213]](https://github.com/fatih/vim-go/pull/2213)
* Add support for `gopls` to `g:go_info_mode`.
[[GH-2224]](https://github.com/fatih/vim-go/pull/2224)
BUG FIXES:
* Fix opening of non-existent file from `:GoDeclsDir` when the current
directory is not the directory containing the current buffer.
[[GH-2048]](https://github.com/fatih/vim-go/pull/2048)
* Fix jumping to an identifier with godef from a modified buffer.
[[GH-2054]](https://github.com/fatih/vim-go/pull/2054)
* Fix errors when `g:go_debug` contains `debugger-commands`.
[[GH-2075]](https://github.com/fatih/vim-go/pull/2075)
* Fix errors from `:GoDebugStop` in Neovim.
[[GH-2075]](https://github.com/fatih/vim-go/pull/2075)
* Fix `:GoSameIdsToggle`.
[[GH-2086]](https://github.com/fatih/vim-go/pull/2086)
* Do not set fileencoding or fileformat options or populate from template when
the buffer is not modifiable.
[[GH-2097]](https://github.com/fatih/vim-go/pull/2097)
* Do not clear buffer-local autocmds of other buffers.
[[GH-2109]](https://github.com/fatih/vim-go/pull/2109)
* Highlight return parameter types when g:go_highlight_function_arguments is
set. [[GH-2116]](https://github.com/fatih/vim-go/pull/2116)
* Fix lockup in Neovim when trying to run `:GoDebugTest` when there are no
tests. [[GH-2125]](https://github.com/fatih/vim-go/pull/2125)
* Keep track of breakpoints correctly when buffer is edited after breakpoints
are set.
[[GH-2126]](https://github.com/fatih/vim-go/pull/2126)
* Fix race conditions in `:GoDebugStop`.
[[GH-2127]](https://github.com/fatih/vim-go/pull/2127)
* Fix jumping to module or package using godef.
[[GH-2141]](https://github.com/fatih/vim-go/pull/2141)
* Fix errors caused by redefining functions within functions.
[[GH-2189]](https://github.com/fatih/vim-go/pull/2189)
* Highlight pre-release and metadata in versions in go.mod.
[[GH-2192]](https://github.com/fatih/vim-go/pull/2192)
* Handle runtime panics from `:GoRun` when using Neovim's terminal.
[[GH-2209]](https://github.com/fatih/vim-go/pull/2209)
* Fix adding tag option when a tag is added.
[[GH-2227]](https://github.com/fatih/vim-go/pull/2227)
## 1.19 - (November 4, 2018)
FEATURES:
* **go.mod file support!** This is the first feature for upcoming Go modules
support. The followings are added:
* Syntax highlighting for the `go.mod` file.
* A new `gomod` filetype is set if a `go.mod` file has been opened and starts
with the line `module `
* New **:GoModFmt** command that formats the `go.mod` file
* Auto format on save feature for `:GoModFmt`, enabled automatically. Can be
toggled of with the setting `g:go_mod_fmt_autosave` or with the command:
`GoModFmtAutoSaveToggle`
[[GH-1931]](https://github.com/fatih/vim-go/pull/1931)
IMPROVEMENTS:
* Unify async job handling for Vim8 and Neovim.
[[GH-1864]](https://github.com/fatih/vim-go/pull/1864)
* Document Vim and Neovim requirements in README.md and help file.
[[GH-1889]](https://github.com/fatih/vim-go/pull/1889)
* Highlight `context.Context` when `g:go_highlight_extra_types` is set.
[[GH-1903]](https://github.com/fatih/vim-go/pull/1903)
* Run gometalinter asynchronously in Neovim.
[[GH-1901]](https://github.com/fatih/vim-go/pull/1901)
* Run gorename asynchronously in Vim8 and Neovim.
[[GH-1894]](https://github.com/fatih/vim-go/pull/1894)
* Install keyify from its canonical import path.
[[GH-1924]](https://github.com/fatih/vim-go/pull/1924)
* Update the tested version of Neovim to v0.3.1.
[[GH-1923]](https://github.com/fatih/vim-go/pull/1923)
* Run autocompletion asynchronously in Vim8 and Neovim.
[[GH-1926]](https://github.com/fatih/vim-go/pull/1926)
* Show statusline update when running `:GoInfo` with `g:go_info_mode='gocode'`.
[[GH-1937]](https://github.com/fatih/vim-go/pull/1937)
* Do not update statusline when highlighting sameids or showing type info via
an autocmd.
[[GH-1937]](https://github.com/fatih/vim-go/pull/1937)
* Do not indent within a raw string literal.
[[GH-1858]](https://github.com/fatih/vim-go/pull/1858)
* Highlight Go's predeclared function identifiers (the functions in `builtins`)
using keyword groups and highlight them using the `Identifiers` group.
[[GH-1939]](https://github.com/fatih/vim-go/pull/1939)
* Add a new FAQ entry to instruct users how to modify the vim-go highlight
groups.
[[GH-1939]](https://github.com/fatih/vim-go/pull/1939)
* Improve use of statusline and progress messages.
[[GH-1948]](https://github.com/fatih/vim-go/pull/1948)
* Add `tt` snippet to create a table test boilerplate (see
https://github.com/golang/go/wiki/TableDrivenTests for more information on
how to use a table driven test).
[[GH-1956]](https://github.com/fatih/vim-go/pull/1956)
* Add `<Plug>(go-decls)` and `<Plug>(go-decls-dir)` mappings.
[[GH-1964]](https://github.com/fatih/vim-go/pull/1964)
* Handle go1.11 test output.
[[GH-1978]](https://github.com/fatih/vim-go/pull/1978)
* Internal: install tools by their custom names
[[GH-1984]](https://github.com/fatih/vim-go/pull/1984)
* Support the go-debugger features in Neovim.
[[GH-2007]](https://github.com/fatih/vim-go/pull/2007)
* color the statusline for termguicolors and Neovim.
[[GH-2014]](https://github.com/fatih/vim-go/pull/2014)
* add an option to disable highlighting of breakpoints and the current line
when debugging.
[[GH-2025]](https://github.com/fatih/vim-go/pull/2025)
* Update autocompletion to work with Go modules.
[[GH-1988]](https://github.com/fatih/vim-go/pull/1988)
* Add an option to search $GOPATH/bin or $GOBIN _after_ $PATH.
[[GH-2041]](https://github.com/fatih/vim-go/pull/2041)
BUG FIXES:
* Fix `:GoRun %` on Windows.
[[GH-1900]](https://github.com/fatih/vim-go/pull/1900)
* Fix `go#complete#GetInfo()` to return a description of the identifier.
[[GH-1905]](https://github.com/fatih/vim-go/pull/1905)
* Restore support for running tests in the Neovim terminal.
[[GH-1895]](https://github.com/fatih/vim-go/pull/1895)
* Fix `:GoInfo` when `g:go_info_mode` is `gocode`
[[GH-1915]](https://github.com/fatih/vim-go/pull/1915)
* Fix highlighting of pointer type in var blocks.
[[GH-1794]](https://github.com/fatih/vim-go/pull/1794)
* Fix `:GoImport` when adding to an empty import block (i.e`import ()`)
[[GH-1938]](https://github.com/fatih/vim-go/pull/1938)
* Run shell commands with shellcmdflag set to `-c`.
[[GH-2006]](https://github.com/fatih/vim-go/pull/2006)
* Use the correct log output option for delve.
[[GH-1992]](https://github.com/fatih/vim-go/pull/1992)
* Pass empty arguments correctly in async jobs on Windows.
[[GH-2011]](https://github.com/fatih/vim-go/pull/2011)
* Don't close godoc scratch window when using arrow keys.
[[GH-2021]](https://github.com/fatih/vim-go/pull/2021)
BACKWARDS INCOMPATIBILITIES:
* Bump minimum required version of Vim to 7.4.2009.
[[GH-1899]](https://github.com/fatih/vim-go/pull/1899)
* Switch gocode to github.com/mdempsky/gocode. Several gocode options have been
removed and a new one has been added.
[[GH-1853]](https://github.com/fatih/vim-go/pull/1853)
## 1.18 - (July 18, 2018)
FEATURES:
* Add **:GoIfErr** command together with the `<Plug>(go-iferr)` plug key to
create a custom mapping. This command generates an `if err != nil { return ... }`
automatically which infer the type of return values and the numbers.
For example:
```
func doSomething() (string, error) {
f, err := os.Open("file")
}
```
Becomes:
```
func doSomething() (string, error) {
f, err := os.Open("file")
if err != nil {
return "", err
}
}
```
* Two new text objects has been added:
* `ic` (inner comment) selects the content of the comment, excluding the start/end markers (i.e: `//`, `/*`)
* `ac` (a comment) selects the content of the whole commment block, including markers
To use this new feature, make sure you use use the latest version of
[motion](https://github.com/fatih/motion). You can update the tool from Vim
via `:GoUpdateBinaries`
[[GH-1779]](https://github.com/fatih/vim-go/pull/1779)
* Add `:GoPointsTo` to show all variables to which the pointer under the cursor
may point to.
[[GH-1751]](https://github.com/fatih/vim-go/pull/1751)
* Add `:GoReportGitHubIssue` to initialize a new GitHub issue with as much data
that our template requests as possible.
[[GH-1738]](https://github.com/fatih/vim-go/pull/1738)
IMPROVEMENTS:
* Add build tags (with `g:go_build_tags`) to all commands that support it.
[[GH-1705]](https://github.com/fatih/vim-go/pull/1705)
* Some command which operate on files (rather than Vim buffers) will now show a
warning if there are unsaved buffers, similar to Vim's `:make`.
[[GH-1754]](https://github.com/fatih/vim-go/pull/1754)
* Don't return an error from `:GoGuru` functions when the import path is
unknown and scope is unneeded.
[[GH-1826]](https://github.com/fatih/vim-go/pull/1826)
* Performance improvements for the `go.vim` syntax file.
[[GH-1799]](https://github.com/fatih/vim-go/pull/1799)
* Allow `GoDebugBreakpoint` and `GoDebugCurrent` highlight groups to be
overridden by user configuration.
[[GH-1850]](https://github.com/vim-go/pull/1850)
* Strip trailing carriage returns from quickfix errors that are parsed
manually. [[GH-1861]](https://github.com/fatih/vim-go/pull/1861).
* Cleanup title of terminal window.
[[GH-1861]](https://github.com/fatih/vim-go/pull/1861).
* Add `:GoImpl` is able to complete interfaces by their full import path in
addition to the current package name (i.e: `:GoImpl t *T github.com/BurntSushi/toml.Unmarshaller`
is now possible)
[[GH-1884]](https://github.com/fatih/vim-go/pull/1884)
BUG FIXES:
* Update the correct window's location list after a long running async job
completes, even when the user changes their window layout while the job is
running.
[[GH-1734]](https://github.com/fatih/vim-go/pull/1734)
* Apply debugger mappings only for Go buffers, and not all buffers.
[[GH-1696]](https://github.com/fatih/vim-go/pull/1696)
* The `gohtmltmpl` filetype will now highlight `{{ .. }}` syntax HTML attributes
and some other locations.
[[GH-1790]](https://github.com/fatih/vim-go/pull/1790)
* Use the correct logging flag argument for delve.
[[GH-1809]](https://github.com/fatih/vim-go/pull/1809)
* Fix gocode option string values that would cause gocode settings not to set
correctly
[[GH-1818]](https://github.com/fatih/vim-go/pull/1818)
* Fix Neovim handling of guru output.
[[GH-1846]](https://github.com/fatih/vim-go/pull/1846)
* Execute commands correctly when they are in $GOBIN but not $PATH.
[[GH-1866]](https://github.com/fatih/vim-go/pull/1866)
* Open files correctly with ctrlp.
[[GH-1878]](https://github.com/fatih/vim-go/pull/1878)
* Fix checking guru binary path
[[GH-1886]](https://github.com/fatih/vim-go/pull/1886)
* Add build tags to `:GoDef` if only it's present
[[GH-1882]](https://github.com/fatih/vim-go/pull/1882)
## 1.17 - (March 27, 2018)
FEATURES:
* **Debugger support!** Add integrated support for the
[`delve`](https://github.com/derekparker/delve) debugger. Use
[`delve`](https://github.com/go-delve/delve) debugger. Use
`:GoInstallBinaries` to install `dlv`, and see `:help go-debug` to get
started.
[[GH-1390]](https://github.com/fatih/vim-go/pull/1390)

View file

@ -1,4 +1,4 @@
FROM golang:1.9.2
FROM golang:1.12.1
RUN apt-get update -y && \
apt-get install -y build-essential curl git libncurses5-dev python3-pip && \

View file

@ -1,6 +1,6 @@
VIMS ?= vim-7.4 vim-8.0 nvim
all: install test lint
all: install lint test
install:
@echo "==> Installing Vims: $(VIMS)"

View file

@ -9,11 +9,11 @@
This plugin adds Go language support for Vim, with the following main features:
* Compile your package with `:GoBuild`, install it with `:GoInstall` or test it
with `:GoTest`. Run a single tests with `:GoTestFunc`).
with `:GoTest`. Run a single test with `:GoTestFunc`).
* Quickly execute your current file(s) with `:GoRun`.
* Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with `:GoDebugStart`.
* Completion support via `gocode`.
* Completion support via `gocode` and `gopls`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with `:GoDef`.
* Look up documentation with `:GoDoc` or `:GoDocBrowser`.
@ -33,10 +33,13 @@ This plugin adds Go language support for Vim, with the following main features:
## Install
vim-go requires at least Vim 7.4.2009 or Neovim 0.3.1.
The [**latest stable release**](https://github.com/fatih/vim-go/releases/latest) is the
recommended version to use. If you choose to use the master branch instead,
please do so with caution; it is a _development_ branch.
vim-go follows the standard runtime path structure. Below are some helper lines
for popular package managers:
@ -45,7 +48,9 @@ for popular package managers:
* [Pathogen](https://github.com/tpope/vim-pathogen)
* `git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go`
* [vim-plug](https://github.com/junegunn/vim-plug)
* `Plug 'fatih/vim-go'`
* `Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }`
* [Vundle](https://github.com/VundleVim/Vundle.vim)
* `Plugin 'fatih/vim-go'`
You will also need to install all the necessary binaries. vim-go makes it easy
to install all of them by providing a command, `:GoInstallBinaries`, which will

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:go_decls_var = {
\ 'init': 'ctrlp#decls#init()',
\ 'exit': 'ctrlp#decls#exit()',
@ -20,7 +24,7 @@ function! ctrlp#decls#init() abort
endfunction
function! ctrlp#decls#exit() abort
unlet! s:decls s:current_dir s:target
unlet! s:decls s:target
endfunction
" The action to perform on the selected string
@ -32,10 +36,6 @@ 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
@ -50,46 +50,41 @@ function! ctrlp#decls#accept(mode, str) abort
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")
let l:cmd = ['motion',
\ '-format', 'vim',
\ '-mode', 'decls',
\ '-include', go#config#DeclsIncludes(),
\ ]
call go#cmd#autowrite()
if s:mode == 0
" current file mode
let fname = expand("%:p")
let l:fname = expand("%:p")
if exists('s:target')
let fname = s:target
let l:fname = s:target
endif
let command .= printf(" -file %s", fname)
let cmd += ['-file', l:fname]
else
" all functions mode
let dir = expand("%:p:h")
let l:dir = expand("%:p:h")
if exists('s:target')
let dir = s:target
let l:dir = s:target
endif
let command .= printf(" -dir %s", dir)
let cmd += ['-dir', l:dir]
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
call go#util#EchoError(out)
let [l:out, l:err] = go#util#Exec(l:cmd)
if l:err
call go#util#EchoError(l:out)
return
endif
@ -118,7 +113,7 @@ function! ctrlp#decls#enter() abort
call add(s:decls, printf("%s\t%s |%s:%s:%s|\t%s",
\ decl.ident . space,
\ decl.keyword,
\ fnamemodify(decl.filename, ":t"),
\ fnamemodify(decl.filename, ":p"),
\ decl.line,
\ decl.col,
\ decl.full,
@ -152,4 +147,8 @@ function! ctrlp#decls#cmd(mode, ...) abort
return s:id
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! s:code(group, attr) abort
let code = synIDattr(synIDtrans(hlID(a:group)), a:attr, "cterm")
if code =~ '^[0-9]\+$'
@ -58,35 +62,34 @@ function! s:source(mode,...) abort
let s:current_dir = expand('%:p:h')
let ret_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")
let l:cmd = ['motion',
\ '-format', 'vim',
\ '-mode', 'decls',
\ '-include', go#config#DeclsIncludes(),
\ ]
call go#cmd#autowrite()
if a:mode == 0
" current file mode
let fname = expand("%:p")
let l:fname = expand("%:p")
if a:0 && !empty(a:1)
let fname = a:1
let l:fname = a:1
endif
let command .= printf(" -file %s", shellescape(fname))
let cmd += ['-file', l:fname]
else
" all functions mode
if a:0 && !empty(a:1)
let s:current_dir = a:1
endif
let command .= printf(" -dir %s", shellescape(s:current_dir))
let l:cmd += ['-dir', s:current_dir]
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
call go#util#EchoError(out)
let [l:out, l:err] = go#util#Exec(l:cmd)
if l:err
call go#util#EchoError(l:out)
return
endif
@ -147,4 +150,8 @@ function! fzf#decls#cmd(...) abort
\ }))
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,7 +1,6 @@
" By default use edit (current buffer view) to switch
if !exists("g:go_alternate_mode")
let g:go_alternate_mode = "edit"
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" Test alternates between the implementation of code and the test code.
function! go#alternate#Switch(bang, cmd) abort
@ -23,10 +22,14 @@ function! go#alternate#Switch(bang, cmd) abort
call go#util#EchoError("couldn't find ".alt_file)
return
elseif empty(a:cmd)
execute ":" . g:go_alternate_mode . " " . alt_file
execute ":" . go#config#AlternateMode() . " " . alt_file
else
execute ":" . a:cmd . " " . alt_file
endif
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -15,6 +15,10 @@
"
" Flag to automatically call :Fmt when file is saved.
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:got_fmt_error = 0
" This is a trimmed-down version of the logic in fmt.vim.
@ -28,42 +32,45 @@ function! go#asmfmt#Format() abort
call writefile(go#util#GetLines(), l:tmpname)
" Run asmfmt.
let path = go#path#CheckBinPath("asmfmt")
if empty(path)
let [l:out, l:err] = go#util#Exec(['asmfmt', '-w', l:tmpname])
if l:err
call go#util#EchoError(l:out)
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
" 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
" 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
" 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
if go#config#AsmfmtAutosave()
call go#config#SetAsmfmtAutosave(1)
call go#util#EchoProgress("auto asmfmt enabled")
return
end
let g:go_asmfmt_autosave = 0
call go#config#SetAsmfmtAutosave(0)
call go#util#EchoProgress("auto asmfmt disabled")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,93 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#auto#template_autocreate()
if !go#config#TemplateAutocreate() || !&modifiable
return
endif
" create new template from scratch
call go#template#create()
endfunction
function! go#auto#echo_go_info()
if !go#config#EchoGoInfo()
return
endif
if !exists('v:completed_item') || empty(v:completed_item)
return
endif
let item = v:completed_item
if !has_key(item, "info")
return
endif
if empty(item.info)
return
endif
redraws! | echo "vim-go: " | echohl Function | echon item.info | echohl None
endfunction
function! go#auto#auto_type_info()
if !go#config#AutoTypeInfo() || !filereadable(expand('%:p'))
return
endif
" GoInfo automatic update
call go#tool#Info(0)
endfunction
function! go#auto#auto_sameids()
if !go#config#AutoSameids() || !filereadable(expand('%:p'))
return
endif
" GoSameId automatic update
call go#guru#SameIds(0)
endfunction
function! go#auto#fmt_autosave()
if !go#config#FmtAutosave() || !filereadable(expand('%:p'))
return
endif
" Go code formatting on save
call go#fmt#Format(-1)
endfunction
function! go#auto#metalinter_autosave()
if !go#config#MetalinterAutosave() || !filereadable(expand('%:p'))
return
endif
" run gometalinter on save
call go#lint#Gometa(!g:go_jump_to_error, 1)
endfunction
function! go#auto#modfmt_autosave()
if !go#config#ModFmtAutosave() || !filereadable(expand('%:p'))
return
endif
" go.mod code formatting on save
call go#mod#Format()
endfunction
function! go#auto#asmfmt_autosave()
if !go#config#AsmfmtAutosave() || !filereadable(expand('%:p'))
return
endif
" Go asm formatting on save
call go#asmfmt#Format()
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,6 +1,21 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#cmd#autowrite() abort
if &autowrite == 1 || &autowriteall == 1
silent! wall
else
for l:nr in range(0, bufnr('$'))
if buflisted(l:nr) && getbufvar(l:nr, '&modified')
" Sleep one second to make sure people see the message. Otherwise it is
" often immediacy overwritten by the async messages (which also don't
" invoke the "hit ENTER" prompt).
call go#util#EchoWarning('[No write since last change]')
sleep 1
return
endif
endfor
endif
endfunction
@ -11,34 +26,21 @@ endfunction
function! go#cmd#Build(bang, ...) abort
" 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 '.'). We also pass -i
" that tries to install the dependencies, this has the side effect that it
" caches the build results, so every other build is faster.
let args =
\ ["build"] +
" placeholder with the current folder (indicated with '.').
let l:args =
\ ['build', '-tags', go#config#BuildTags()] +
\ map(copy(a:000), "expand(v:val)") +
\ [".", "errors"]
" Vim async.
" Vim and Neovim async.
if go#util#has_job()
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("building dispatched ...")
endif
call s:cmd_job({
\ 'cmd': ['go'] + args,
\ 'bang': a:bang,
\ 'for': 'GoBuild',
\ 'statustype': 'build'
\})
" Nvim async.
elseif has('nvim')
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("building dispatched ...")
endif
call go#jobcontrol#Spawn(a:bang, "build", "GoBuild", args)
" Vim 7.4 without async
else
let default_makeprg = &makeprg
@ -59,6 +61,7 @@ function! go#cmd#Build(bang, ...) abort
redraw!
finally
execute cd . fnameescape(dir)
let &makeprg = default_makeprg
endtry
let errors = go#list#Get(l:listtype)
@ -68,8 +71,6 @@ function! go#cmd#Build(bang, ...) abort
else
call go#util#EchoSuccess("[build] SUCCESS")
endif
let &makeprg = default_makeprg
endif
endfunction
@ -77,33 +78,44 @@ 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
let v = a:1
if v == '""' || v == "''"
let v = ""
endif
call go#config#SetBuildTags(v)
let tags = go#config#BuildTags()
if empty(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)
call go#util#EchoSuccess("build tags are changed to: " . tags)
endif
return
endif
if !exists('g:go_build_tags')
let tags = go#config#BuildTags()
if empty(tags)
call go#util#EchoSuccess("build tags are not set")
else
call go#util#EchoSuccess("current build tags: ". g:go_build_tags)
call go#util#EchoSuccess("current build tags: " . 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)
let cmd = "go run "
let tags = go#config#BuildTags()
if len(tags) > 0
let cmd .= "-tags " . go#util#Shellescape(tags) . " "
endif
call go#term#newmode(a:bang, cmd, a:mode)
if empty(a:files)
let cmd .= go#util#Shelljoin(go#tool#Files())
else
let cmd .= go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1)
endif
call go#term#newmode(a:bang, cmd, s:runerrorformat(), a:mode)
endfunction
" Run runs the current file (and their dependencies if any) and outputs it.
@ -122,8 +134,19 @@ function! go#cmd#Run(bang, ...) abort
" anything. Once this is implemented we're going to make :GoRun async
endif
let cmd = "go run "
let tags = go#config#BuildTags()
if len(tags) > 0
let cmd .= "-tags " . go#util#Shellescape(tags) . " "
endif
if go#util#IsWin()
exec '!go run ' . go#util#Shelljoin(go#tool#Files())
if a:0 == 0
exec '!' . cmd . go#util#Shelljoin(go#tool#Files(), 1)
else
exec '!' . cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
endif
if v:shell_error
redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None
else
@ -136,29 +159,36 @@ function! go#cmd#Run(bang, ...) abort
" :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)
let &makeprg = cmd . go#util#Shelljoin(go#tool#Files(), 1)
else
let &makeprg = "go run " . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
let &makeprg = cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
endif
let l:listtype = go#list#Type("GoRun")
if l:listtype == "locationlist"
exe 'lmake!'
else
exe 'make!'
endif
try
let items = go#list#Get(l:listtype)
let errors = go#tool#FilterValids(items)
" backup user's errorformat, will be restored once we are finished
let l:old_errorformat = &errorformat
let &errorformat = s:runerrorformat()
if l:listtype == "locationlist"
exe 'lmake!'
else
exe 'make!'
endif
finally
"restore errorformat
let &errorformat = l:old_errorformat
let &makeprg = default_makeprg
endtry
call go#list#Populate(l:listtype, errors, &makeprg)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(l:errors))
if !empty(l:errors) && !a:bang
call go#list#JumpToFirst(l:listtype)
endif
let &makeprg = default_makeprg
endfunction
" Install installs the package by simple calling 'go install'. If any argument
@ -170,14 +200,11 @@ function! go#cmd#Install(bang, ...) abort
" expand all wildcards(i.e: '%' to the current file name)
let goargs = map(copy(a:000), "expand(v:val)")
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("installing dispatched ...")
endif
call s:cmd_job({
\ 'cmd': ['go', 'install'] + goargs,
\ 'cmd': ['go', 'install', '-tags', go#config#BuildTags()] + goargs,
\ 'bang': a:bang,
\ 'for': 'GoInstall',
\ 'statustype': 'install'
\})
return
endif
@ -203,6 +230,7 @@ function! go#cmd#Install(bang, ...) abort
redraw!
finally
execute cd . fnameescape(dir)
let &makeprg = default_makeprg
endtry
let errors = go#list#Get(l:listtype)
@ -212,8 +240,6 @@ function! go#cmd#Install(bang, ...) abort
else
call go#util#EchoSuccess("installed to ". go#path#Default())
endif
let &makeprg = default_makeprg
endfunction
" Generate runs 'go generate' in similar fashion to go#cmd#Build()
@ -232,12 +258,17 @@ function! go#cmd#Generate(bang, ...) abort
let l:listtype = go#list#Type("GoGenerate")
echon "vim-go: " | echohl Identifier | echon "generating ..."| echohl None
if l:listtype == "locationlist"
silent! exe 'lmake!'
else
silent! exe 'make!'
endif
redraw!
try
if l:listtype == "locationlist"
silent! exe 'lmake!'
else
silent! exe 'make!'
endif
finally
redraw!
let &makeprg = default_makeprg
endtry
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
@ -249,64 +280,27 @@ function! go#cmd#Generate(bang, ...) abort
redraws! | echon "vim-go: " | echohl Function | echon "[generate] SUCCESS"| echohl None
endif
let &makeprg = default_makeprg
endfunction
function! s:runerrorformat()
let l:panicaddress = "%\\t%#%f:%l +0x%[0-9A-Fa-f]%\\+"
let l:errorformat = '%A' . l:panicaddress . "," . &errorformat
return l:errorformat
endfunction
" ---------------------
" | 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",
\})
function! s:cmd_job(args) abort
" autowrite is not enabled for jobs
call go#cmd#autowrite()
function! s:complete(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
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)
endfunction
let a:args.complete = funcref('s:complete')
let callbacks = go#job#Spawn(a:args)
let start_options = {
\ 'callback': callbacks.callback,
\ 'exit_cb': callbacks.exit_cb,
\ 'close_cb': callbacks.close_cb,
\ }
" 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)
call go#job#Spawn(a:args.cmd, a:args)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_GoBuildErrors()
try
let l:filename = 'cmd/bad.go'
@ -27,4 +31,8 @@ func! Test_GoBuildErrors()
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,16 +1,39 @@
let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix'
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! s:gocodeCommand(cmd, args) abort
let bin_path = go#path#CheckBinPath("gocode")
let l:gocode_bin = "gocode"
let l:gomod = go#util#gomod()
if filereadable(l:gomod)
let l:gocode_bin = "gocode-gomod"
endif
let bin_path = go#path#CheckBinPath(l:gocode_bin)
if empty(bin_path)
return []
endif
let socket_type = get(g:, 'go_gocode_socket_type', s:sock_type)
let socket_type = go#config#GocodeSocketType()
let cmd = [bin_path]
let cmd = extend(cmd, ['-sock', socket_type])
let cmd = extend(cmd, ['-f', 'vim'])
if go#config#GocodeProposeBuiltins()
let cmd = extend(cmd, ['-builtin'])
endif
if go#config#GocodeProposeSource()
let cmd = extend(cmd, ['-source'])
else
let cmd = extend(cmd, ['-fallback-to-source', '-cache'])
endif
if go#config#GocodeUnimportedPackages()
let cmd = extend(cmd, ['-unimported-packages'])
endif
let cmd = extend(cmd, [a:cmd])
let cmd = extend(cmd, a:args)
@ -45,32 +68,7 @@ function! s:sync_gocode(cmd, args, input) abort
return l:result
endfunction
" TODO(bc): reset when gocode isn't running
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()
" use the offset as is, because the cursor position is the position for
" which autocomplete candidates are needed.
return s:sync_gocode('autocomplete',
@ -84,60 +82,20 @@ function! go#complete#GetInfo() abort
return s:sync_info(0)
endfunction
function! go#complete#Info(auto) abort
if go#util#has_job()
return s:async_info(a:auto)
function! go#complete#Info(showstatus) abort
if go#util#has_job(1)
return s:async_info(1, a:showstatus)
else
return s:sync_info(a:auto)
return s:sync_info(1)
endif
endfunction
function! s:async_info(auto)
if exists("s:async_info_job")
call job_stop(s:async_info_job)
unlet s:async_info_job
endif
function! s:async_info(echo, showstatus)
let state = {'echo': a:echo}
let state = {
\ 'exited': 0,
\ 'exit_status': 0,
\ 'closed': 0,
\ 'messages': [],
\ 'auto': a:auto
\ }
function! s:callback(chan, msg) dict
let l:msg = a:msg
if &encoding != 'utf-8'
let l:msg = iconv(l:msg, 'utf-8', &encoding)
endif
call add(self.messages, l:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
if self.closed
call self.complete()
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call self.complete()
endif
endfunction
function state.complete() dict
if self.exit_status != 0
return
endif
let result = s:info_filter(self.auto, join(self.messages, "\n"))
call s:info_complete(self.auto, result)
endfunction
" explicitly bind complete to state so that within it, self will
" always refer to state. See :help Partial for more information.
let state.complete = function('s:complete', [], state)
" add 1 to the offset, so that the position at the cursor will be included
" in gocode's search
@ -149,23 +107,49 @@ function! s:async_info(auto)
\ "GOROOT": go#util#env("goroot")
\ }
let opts = {
\ 'bang': 1,
\ 'complete': state.complete,
\ 'for': '_',
\ }
if a:showstatus
let opts.statustype = 'gocode'
endif
let opts = go#job#Options(l:opts)
let cmd = s:gocodeCommand('autocomplete',
\ [expand('%:p'), offset])
" TODO(bc): Don't write the buffer to a file; pass the buffer directrly to
" TODO(bc): Don't write the buffer to a file; pass the buffer directly to
" gocode's stdin. It shouldn't be necessary to use {in_io: 'file', in_name:
" s:gocodeFile()}, but unfortunately {in_io: 'buffer', in_buf: bufnr('%')}
" should work.
let options = {
" doesn't work.
call extend(opts, {
\ 'env': env,
\ 'in_io': 'file',
\ 'in_name': s:gocodeFile(),
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state)
\ }
\ })
let s:async_info_job = job_start(cmd, options)
call go#job#Start(cmd, opts)
endfunction
function! s:complete(job, exit_status, messages) abort dict
if a:exit_status != 0
return
endif
if &encoding != 'utf-8'
let i = 0
while i < len(a:messages)
let a:messages[i] = iconv(a:messages[i], 'utf-8', &encoding)
let i += 1
endwhile
endif
let result = s:info_filter(self.echo, join(a:messages, "\n"))
call s:info_complete(self.echo, result)
endfunction
function! s:gocodeFile()
@ -174,9 +158,7 @@ function! s:gocodeFile()
return file
endfunction
function! s:sync_info(auto)
" auto is true if we were called by g:go_auto_type_info's autocmd
function! s:sync_info(echo)
" add 1 to the offset, so that the position at the cursor will be included
" in gocode's search
let offset = go#util#OffsetCursor()+1
@ -185,11 +167,11 @@ function! s:sync_info(auto)
\ [expand('%:p'), offset],
\ go#util#GetLines())
let result = s:info_filter(a:auto, result)
call s:info_complete(a:auto, result)
let result = s:info_filter(a:echo, result)
return s:info_complete(a:echo, result)
endfunction
function! s:info_filter(auto, result) abort
function! s:info_filter(echo, result) abort
if empty(a:result)
return ""
endif
@ -203,7 +185,7 @@ function! s:info_filter(auto, result) abort
if len(l:candidates) == 1
" When gocode panics in vim mode, it returns
" [0, [{'word': 'PANIC', 'abbr': 'PANIC PANIC PANIC', 'info': 'PANIC PANIC PANIC'}]]
if a:auto && l:candidates[0].info ==# "PANIC PANIC PANIC"
if a:echo && l:candidates[0].info ==# "PANIC PANIC PANIC"
return ""
endif
@ -216,17 +198,21 @@ function! s:info_filter(auto, result) abort
let wordMatch = substitute(wordMatch, "'", "''", "g")
let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'")
if len(l:filtered) != 1
return ""
if len(l:filtered) == 0
return "no matches"
elseif len(l:filtered) > 1
return "ambiguous match"
endif
return l:filtered[0].info
endfunction
function! s:info_complete(auto, result) abort
if !empty(a:result)
echo "vim-go: " | echohl Function | echon a:result | echohl None
function! s:info_complete(echo, result) abort
if a:echo
call go#util#ShowInfo(a:result)
endif
return a:result
endfunction
function! s:trim_bracket(val) abort
@ -234,12 +220,22 @@ function! s:trim_bracket(val) abort
return a:val
endfunction
let s:completions = ""
function! go#complete#Complete(findstart, base) abort
let s:completions = []
function! go#complete#GocodeComplete(findstart, base) abort
"findstart = 1 when we need to get the text length
if a:findstart == 1
execute "silent let s:completions = " . s:gocodeAutocomplete()
return col('.') - s:completions[0] - 1
let l:completions = []
execute "silent let l:completions = " . s:gocodeAutocomplete()
if len(l:completions) == 0 || len(l:completions) >= 2 && len(l:completions[1]) == 0
" no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches")
return -3
endif
let s:completions = l:completions[1]
return col('.') - l:completions[0] - 1
"findstart = 0 when we need to return the list of completions
else
let s = getline(".")[col('.') - 1]
@ -251,15 +247,49 @@ function! go#complete#Complete(findstart, base) abort
endif
endfunction
function! go#complete#Complete(findstart, base) abort
let l:state = {'done': 0, 'matches': []}
function! s:handler(state, matches) abort dict
let a:state.matches = a:matches
let a:state.done = 1
endfunction
"findstart = 1 when we need to get the start of the match
if a:findstart == 1
call go#lsp#Completion(expand('%:p'), line('.'), col('.'), funcref('s:handler', [l:state]))
while !l:state.done
sleep 10m
endwhile
let s:completions = l:state.matches
if len(l:state.matches) == 0
" no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches")
return -3
endif
return col('.')
else "findstart = 0 when we need to return the list of completions
return s:completions
endif
endfunction
function! go#complete#ToggleAutoTypeInfo() abort
if get(g:, "go_auto_type_info", 0)
let g:go_auto_type_info = 0
if go#config#AutoTypeInfo()
call go#config#SetAutoTypeInfo(0)
call go#util#EchoProgress("auto type info disabled")
return
end
let g:go_auto_type_info = 1
call go#config#SetAutoTypeInfo(1)
call go#util#EchoProgress("auto type info enabled")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,28 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_GetInfo()
let l:filename = 'complete/complete.go'
let l:tmp = gotest#load_fixture(l:filename)
call cursor(8, 3)
let g:go_info_mode = 'gocode'
let expected = 'func Example(s string)'
let actual = go#complete#GetInfo()
call assert_equal(expected, actual)
let g:go_info_mode = 'guru'
call go#config#InfoMode()
let actual = go#complete#GetInfo()
call assert_equal(expected, actual)
unlet g:go_info_mode
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,479 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#config#AutodetectGopath() abort
return get(g:, 'go_autodetect_gopath', 0)
endfunction
function! go#config#ListTypeCommands() abort
return get(g:, 'go_list_type_commands', {})
endfunction
function! go#config#VersionWarning() abort
return get(g:, 'go_version_warning', 1)
endfunction
function! go#config#BuildTags() abort
return get(g:, 'go_build_tags', '')
endfunction
function! go#config#SetBuildTags(value) abort
if a:value is ''
silent! unlet g:go_build_tags
return
endif
let g:go_build_tags = a:value
endfunction
function! go#config#TestTimeout() abort
return get(g:, 'go_test_timeout', '10s')
endfunction
function! go#config#TestShowName() abort
return get(g:, 'go_test_show_name', 0)
endfunction
function! go#config#TermHeight() abort
return get(g:, 'go_term_height', winheight(0))
endfunction
function! go#config#TermWidth() abort
return get(g:, 'go_term_width', winwidth(0))
endfunction
function! go#config#TermMode() abort
return get(g:, 'go_term_mode', 'vsplit')
endfunction
function! go#config#TermEnabled() abort
return has('nvim') && get(g:, 'go_term_enabled', 0)
endfunction
function! go#config#SetTermEnabled(value) abort
let g:go_term_enabled = a:value
endfunction
function! go#config#TemplateUsePkg() abort
return get(g:, 'go_template_use_pkg', 0)
endfunction
function! go#config#TemplateTestFile() abort
return get(g:, 'go_template_test_file', "hello_world_test.go")
endfunction
function! go#config#TemplateFile() abort
return get(g:, 'go_template_file', "hello_world.go")
endfunction
function! go#config#StatuslineDuration() abort
return get(g:, 'go_statusline_duration', 60000)
endfunction
function! go#config#SnippetEngine() abort
return get(g:, 'go_snippet_engine', 'automatic')
endfunction
function! go#config#PlayBrowserCommand() abort
if go#util#IsWin()
let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%'
elseif go#util#IsMac()
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% &'
elseif executable('chromium')
let go_play_browser_command = 'chromium %URL% &'
else
let go_play_browser_command = ''
endif
return get(g:, 'go_play_browser_command', go_play_browser_command)
endfunction
function! go#config#MetalinterDeadline() abort
" gometalinter has a default deadline of 5 seconds only when asynchronous
" jobs are not supported.
let deadline = '5s'
if go#util#has_job() && has('lambda')
let deadline = ''
endif
return get(g:, 'go_metalinter_deadline', deadline)
endfunction
function! go#config#ListType() abort
return get(g:, 'go_list_type', '')
endfunction
function! go#config#ListAutoclose() abort
return get(g:, 'go_list_autoclose', 1)
endfunction
function! go#config#InfoMode() abort
return get(g:, 'go_info_mode', 'gocode')
endfunction
function! go#config#GuruScope() abort
let scope = get(g:, 'go_guru_scope', [])
if !empty(scope)
" strip trailing slashes for each path in scope. bug:
" https://github.com/golang/go/issues/14584
let scopes = go#util#StripTrailingSlash(scope)
endif
return scope
endfunction
function! go#config#SetGuruScope(scope) abort
if empty(a:scope)
if exists('g:go_guru_scope')
unlet g:go_guru_scope
endif
else
let g:go_guru_scope = a:scope
endif
endfunction
function! go#config#GocodeUnimportedPackages() abort
return get(g:, 'go_gocode_unimported_packages', 0)
endfunction
let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix'
function! go#config#GocodeSocketType() abort
return get(g:, 'go_gocode_socket_type', s:sock_type)
endfunction
function! go#config#GocodeProposeBuiltins() abort
return get(g:, 'go_gocode_propose_builtins', 1)
endfunction
function! go#config#GocodeProposeSource() abort
return get(g:, 'go_gocode_propose_source', 0)
endfunction
function! go#config#EchoCommandInfo() abort
return get(g:, 'go_echo_command_info', 1)
endfunction
function! go#config#DocUrl() abort
let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org')
if godoc_url isnot 'https://godoc.org'
" strip last '/' character if available
let last_char = strlen(godoc_url) - 1
if godoc_url[last_char] == '/'
let godoc_url = strpart(godoc_url, 0, last_char)
endif
" custom godoc installations expect /pkg before package names
let godoc_url .= "/pkg"
endif
return godoc_url
endfunction
function! go#config#DefReuseBuffer() abort
return get(g:, 'go_def_reuse_buffer', 0)
endfunction
function! go#config#DefMode() abort
return get(g:, 'go_def_mode', 'guru')
endfunction
function! go#config#DeclsIncludes() abort
return get(g:, 'go_decls_includes', 'func,type')
endfunction
function! go#config#Debug() abort
return get(g:, 'go_debug', [])
endfunction
function! go#config#DebugWindows() abort
return get(g:, 'go_debug_windows', {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
\ }
\ )
endfunction
function! go#config#DebugAddress() abort
return get(g:, 'go_debug_address', '127.0.0.1:8181')
endfunction
function! go#config#DebugCommands() abort
" make sure g:go_debug_commands is set so that it can be added to easily.
let g:go_debug_commands = get(g:, 'go_debug_commands', [])
return g:go_debug_commands
endfunction
function! go#config#DebugLogOutput() abort
return get(g:, 'go_debug_log_output', 'debugger, rpc')
endfunction
function! go#config#LspLog() abort
" make sure g:go_lsp_log is set so that it can be added to easily.
let g:go_lsp_log = get(g:, 'go_lsp_log', [])
return g:go_lsp_log
endfunction
function! go#config#SetDebugDiag(value) abort
let g:go_debug_diag = a:value
endfunction
function! go#config#AutoSameids() abort
return get(g:, 'go_auto_sameids', 0)
endfunction
function! go#config#SetAutoSameids(value) abort
let g:go_auto_sameids = a:value
endfunction
function! go#config#AddtagsTransform() abort
return get(g:, 'go_addtags_transform', "snakecase")
endfunction
function! go#config#TemplateAutocreate() abort
return get(g:, "go_template_autocreate", 1)
endfunction
function! go#config#SetTemplateAutocreate(value) abort
let g:go_template_autocreate = a:value
endfunction
function! go#config#MetalinterCommand() abort
return get(g:, "go_metalinter_command", "gometalinter")
endfunction
function! go#config#MetalinterAutosaveEnabled() abort
let l:default_enabled = ["vet", "golint"]
if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif
return get(g:, "go_metalinter_autosave_enabled", default_enabled)
endfunction
function! go#config#MetalinterEnabled() abort
let l:default_enabled = ["vet", "golint", "errcheck"]
if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif
return get(g:, "go_metalinter_enabled", default_enabled)
endfunction
function! go#config#MetalinterDisabled() abort
return get(g:, "go_metalinter_disabled", [])
endfunction
function! go#config#GolintBin() abort
return get(g:, "go_golint_bin", "golint")
endfunction
function! go#config#ErrcheckBin() abort
return get(g:, "go_errcheck_bin", "errcheck")
endfunction
function! go#config#MetalinterAutosave() abort
return get(g:, "go_metalinter_autosave", 0)
endfunction
function! go#config#SetMetalinterAutosave(value) abort
let g:go_metalinter_autosave = a:value
endfunction
function! go#config#ListHeight() abort
return get(g:, "go_list_height", 0)
endfunction
function! go#config#FmtAutosave() abort
return get(g:, "go_fmt_autosave", 1)
endfunction
function! go#config#SetFmtAutosave(value) abort
let g:go_fmt_autosave = a:value
endfunction
function! go#config#AsmfmtAutosave() abort
return get(g:, "go_asmfmt_autosave", 0)
endfunction
function! go#config#SetAsmfmtAutosave(value) abort
let g:go_asmfmt_autosave = a:value
endfunction
function! go#config#ModFmtAutosave() abort
return get(g:, "go_mod_fmt_autosave", 1)
endfunction
function! go#config#SetModFmtAutosave(value) abort
let g:go_mod_fmt_autosave = a:value
endfunction
function! go#config#DocMaxHeight() abort
return get(g:, "go_doc_max_height", 20)
endfunction
function! go#config#AutoTypeInfo() abort
return get(g:, "go_auto_type_info", 0)
endfunction
function! go#config#SetAutoTypeInfo(value) abort
let g:go_auto_type_info = a:value
endfunction
function! go#config#AlternateMode() abort
return get(g:, "go_alternate_mode", "edit")
endfunction
function! go#config#DeclsMode() abort
return get(g:, "go_decls_mode", "")
endfunction
function! go#config#FmtCommand() abort
return get(g:, "go_fmt_command", "gofmt")
endfunction
function! go#config#FmtOptions() abort
return get(g:, "go_fmt_options", {})
endfunction
function! go#config#FmtFailSilently() abort
return get(g:, "go_fmt_fail_silently", 0)
endfunction
function! go#config#FmtExperimental() abort
return get(g:, "go_fmt_experimental", 0 )
endfunction
function! go#config#PlayOpenBrowser() abort
return get(g:, "go_play_open_browser", 1)
endfunction
function! go#config#GorenameBin() abort
return get(g:, "go_gorename_bin", "gorename")
endfunction
function! go#config#GorenamePrefill() abort
return get(g:, "go_gorename_prefill", 'expand("<cword>") =~# "^[A-Z]"' .
\ '? go#util#pascalcase(expand("<cword>"))' .
\ ': go#util#camelcase(expand("<cword>"))')
endfunction
function! go#config#TextobjIncludeFunctionDoc() abort
return get(g:, "go_textobj_include_function_doc", 1)
endfunction
function! go#config#TextobjIncludeVariable() abort
return get(g:, "go_textobj_include_variable", 1)
endfunction
function! go#config#BinPath() abort
return get(g:, "go_bin_path", "")
endfunction
function! go#config#SearchBinPathFirst() abort
return get(g:, 'go_search_bin_path_first', 1)
endfunction
function! go#config#HighlightArrayWhitespaceError() abort
return get(g:, 'go_highlight_array_whitespace_error', 0)
endfunction
function! go#config#HighlightChanWhitespaceError() abort
return get(g:, 'go_highlight_chan_whitespace_error', 0)
endfunction
function! go#config#HighlightExtraTypes() abort
return get(g:, 'go_highlight_extra_types', 0)
endfunction
function! go#config#HighlightSpaceTabError() abort
return get(g:, 'go_highlight_space_tab_error', 0)
endfunction
function! go#config#HighlightTrailingWhitespaceError() abort
return get(g:, 'go_highlight_trailing_whitespace_error', 0)
endfunction
function! go#config#HighlightOperators() abort
return get(g:, 'go_highlight_operators', 0)
endfunction
function! go#config#HighlightFunctions() abort
return get(g:, 'go_highlight_functions', 0)
endfunction
function! go#config#HighlightFunctionParameters() abort
" fallback to highlight_function_arguments for backwards compatibility
return get(g:, 'go_highlight_function_parameters', get(g:, 'go_highlight_function_arguments', 0))
endfunction
function! go#config#HighlightFunctionCalls() abort
return get(g:, 'go_highlight_function_calls', 0)
endfunction
function! go#config#HighlightFields() abort
return get(g:, 'go_highlight_fields', 0)
endfunction
function! go#config#HighlightTypes() abort
return get(g:, 'go_highlight_types', 0)
endfunction
function! go#config#HighlightBuildConstraints() abort
return get(g:, 'go_highlight_build_constraints', 0)
endfunction
function! go#config#HighlightStringSpellcheck() abort
return get(g:, 'go_highlight_string_spellcheck', 1)
endfunction
function! go#config#HighlightFormatStrings() abort
return get(g:, 'go_highlight_format_strings', 1)
endfunction
function! go#config#HighlightGenerateTags() abort
return get(g:, 'go_highlight_generate_tags', 0)
endfunction
function! go#config#HighlightVariableAssignments() abort
return get(g:, 'go_highlight_variable_assignments', 0)
endfunction
function! go#config#HighlightVariableDeclarations() abort
return get(g:, 'go_highlight_variable_declarations', 0)
endfunction
function! go#config#HighlightDebug() abort
return get(g:, 'go_highlight_debug', 1)
endfunction
function! go#config#FoldEnable(...) abort
if a:0 > 0
return index(go#config#FoldEnable(), a:1) > -1
endif
return get(g:, 'go_fold_enable', ['block', 'import', 'varconst', 'package_comment'])
endfunction
function! go#config#EchoGoInfo() abort
return get(g:, "go_echo_go_info", 1)
endfunction
" Set the default value. A value of "1" is a shortcut for this, for
" compatibility reasons.
if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1
unlet g:go_gorename_prefill
endif
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:toggle = 0
" Buffer creates a new cover profile with 'go test -coverprofile' and changes
@ -44,43 +48,28 @@ function! go#coverage#Buffer(bang, ...) abort
let s:toggle = 1
let l:tmpname = tempname()
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("testing...")
endif
if go#util#has_job()
call s:coverage_job({
\ 'cmd': ['go', 'test', '-coverprofile', l:tmpname] + a:000,
\ 'cmd': ['go', 'test', '-tags', go#config#BuildTags(), '-coverprofile', l:tmpname] + a:000,
\ 'complete': function('s:coverage_callback', [l:tmpname]),
\ 'bang': a:bang,
\ 'for': 'GoTest',
\ 'statustype': 'coverage',
\ })
return
endif
if go#config#EchoCommandInfo()
call go#util#EchoProgress("testing...")
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#test#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
@ -96,7 +85,7 @@ function! go#coverage#Clear() abort
" remove the autocmd we defined
augroup vim-go-coverage
autocmd!
autocmd! * <buffer>
augroup end
endfunction
@ -106,10 +95,11 @@ 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],
\ 'cmd': ['go', 'test', '-tags', go#config#BuildTags(), '-coverprofile', l:tmpname],
\ 'complete': function('s:coverage_browser_callback', [l:tmpname]),
\ 'bang': a:bang,
\ 'for': 'GoTest',
\ 'statustype': 'coverage',
\ })
return
endif
@ -120,16 +110,9 @@ function! go#coverage#Browser(bang, ...) abort
endif
let id = call('go#test#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)
call go#util#ExecInDir(['go', 'tool', 'cover', '-html=' . l:tmpname])
endif
call delete(l:tmpname)
@ -259,7 +242,7 @@ function! go#coverage#overlay(file) abort
" clear the matches if we leave the buffer
augroup vim-go-coverage
autocmd!
autocmd! * <buffer>
autocmd BufWinLeave <buffer> call go#coverage#Clear()
augroup end
@ -277,48 +260,17 @@ function s:coverage_job(args)
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let status_dir = expand('%:p:h')
let Complete = a:args.complete
function! s:complete(job, exit_status, data) closure
let status = {
\ 'desc': 'last status',
\ 'type': "coverage",
\ 'state': "finished",
\ }
let disabled_term = 0
if go#config#TermEnabled()
let disabled_term = 1
call go#config#SetTermEnabled(0)
endif
if a:exit_status
let status.state = "failed"
endif
call go#job#Spawn(a:args.cmd, a:args)
call go#statusline#Update(status_dir, status)
return Complete(a:job, a:exit_status, a:data)
endfunction
let a:args.complete = funcref('s:complete')
let callbacks = go#job#Spawn(a:args)
let start_options = {
\ 'callback': callbacks.callback,
\ 'exit_cb': callbacks.exit_cb,
\ 'close_cb': callbacks.close_cb,
\ }
" 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)
if disabled_term
call go#config#SetTermEnabled(1)
endif
endfunction
" coverage_callback is called when the coverage execution is finished
@ -332,47 +284,14 @@ 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)
call go#util#ExecInDir(['go', 'tool', 'cover', '-html=' . a:coverfile])
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
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,22 +1,13 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
if !exists('g:go_debug_windows')
let g:go_debug_windows = {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
\ }
endif
if !exists('g:go_debug_address')
let g:go_debug_address = '127.0.0.1:8181'
endif
if !exists('s:state')
let s:state = {
\ 'rpcid': 1,
\ 'running': 0,
\ 'breakpoint': {},
\ 'currentThread': {},
\ 'localVars': {},
\ 'functionArgs': {},
@ -25,7 +16,7 @@ if !exists('s:state')
\}
if go#util#HasDebug('debugger-state')
let g:go_debug_diag = s:state
call go#config#SetDebugDiag(s:state)
endif
endif
@ -37,14 +28,35 @@ function! s:groutineID() abort
return s:state['currentThread'].goroutineID
endfunction
function! s:exit(job, status) abort
function! s:complete(job, exit_status, data) abort
let l:gotready = get(s:state, 'ready', 0)
" copy messages to a:data _only_ when dlv exited non-zero and it was never
" detected as ready (e.g. there was a compiler error).
if a:exit_status > 0 && !l:gotready
" copy messages to data so that vim-go's usual handling of errors from
" async jobs will occur.
call extend(a:data, s:state['message'])
endif
" return early instead of clearing any variables when the current job is not
" a:job
if has_key(s:state, 'job') && s:state['job'] != a:job
return
endif
if has_key(s:state, 'job')
call remove(s:state, 'job')
endif
call s:clearState()
if a:status > 0
call go#util#EchoError(s:state['message'])
if has_key(s:state, 'ready')
call remove(s:state, 'ready')
endif
if has_key(s:state, 'ch')
call remove(s:state, 'ch')
endif
call s:clearState()
endfunction
function! s:logger(prefix, ch, msg) abort
@ -71,57 +83,46 @@ endfunction
function! s:call_jsonrpc(method, ...) abort
if go#util#HasDebug('debugger-commands')
if !exists('g:go_debug_commands')
let g:go_debug_commands = []
endif
echom 'sending to dlv ' . a:method
endif
if len(a:000) > 0 && type(a:000[0]) == v:t_func
let Cb = a:000[0]
let args = a:000[1:]
else
let Cb = v:none
let args = a:000
endif
let l:args = a:000
let s:state['rpcid'] += 1
let req_json = json_encode({
let l:req_json = json_encode({
\ 'id': s:state['rpcid'],
\ 'method': a:method,
\ 'params': args,
\ 'params': l:args,
\})
try
" Use callback
if type(Cb) == v:t_func
let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb})
call ch_sendraw(s:ch, req_json)
if go#util#HasDebug('debugger-commands')
let g:go_debug_commands = add(g:go_debug_commands, {
\ 'request': req_json,
\ 'response': Cb,
\ })
endif
return
let l:ch = s:state['ch']
if has('nvim')
call chansend(l:ch, l:req_json)
while len(s:state.data) == 0
sleep 50m
if get(s:state, 'ready', 0) == 0
return
endif
endwhile
let resp_json = s:state.data[0]
let s:state.data = s:state.data[1:]
else
call ch_sendraw(l:ch, req_json)
let l:resp_raw = ch_readraw(l:ch)
let resp_json = json_decode(l:resp_raw)
endif
let ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'timeout': 20000})
call ch_sendraw(ch, req_json)
let resp_json = ch_readraw(ch)
if go#util#HasDebug('debugger-commands')
let g:go_debug_commands = add(g:go_debug_commands, {
\ 'request': req_json,
\ 'response': resp_json,
let g:go_debug_commands = add(go#config#DebugCommands(), {
\ 'request': l:req_json,
\ 'response': l:resp_json,
\ })
endif
let obj = json_decode(resp_json)
if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error)
throw obj.error
if type(l:resp_json) == v:t_dict && has_key(l:resp_json, 'error') && !empty(l:resp_json.error)
throw l:resp_json.error
endif
return obj
return l:resp_json
catch
throw substitute(v:exception, '^Vim', '', '')
endtry
@ -130,7 +131,7 @@ endfunction
" Update the location of the current breakpoint or line we're halted on based on
" response from dlv.
function! s:update_breakpoint(res) abort
if type(a:res) ==# v:t_none
if type(a:res) ==# type(v:null)
return
endif
@ -227,26 +228,34 @@ function! s:clearState() abort
let s:state['localVars'] = {}
let s:state['functionArgs'] = {}
let s:state['message'] = []
silent! sign unplace 9999
endfunction
function! s:stop() abort
call s:clearState()
let l:res = s:call_jsonrpc('RPCServer.Detach', {'kill': v:true})
if has_key(s:state, 'job')
call job_stop(s:state['job'])
call remove(s:state, 'job')
call go#job#Wait(s:state['job'])
" while waiting, the s:complete may have already removed job from s:state.
if has_key(s:state, 'job')
call remove(s:state, 'job')
endif
endif
if has_key(s:state, 'ready')
call remove(s:state, 'ready')
endif
if has_key(s:state, 'ch')
call remove(s:state, 'ch')
endif
call s:clearState()
endfunction
function! go#debug#Stop() abort
" Remove signs.
for k in keys(s:state['breakpoint'])
let bt = s:state['breakpoint'][k]
if bt.id >= 0
silent exe 'sign unplace ' . bt.id
endif
endfor
" Remove all commands and add back the default commands.
for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")')
exe 'delcommand' k
@ -272,8 +281,15 @@ function! go#debug#Stop() abort
silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c'
silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c'
set noballooneval
set balloonexpr=
if has('balloon_eval')
let &ballooneval=s:ballooneval
let &balloonexpr=s:balloonexpr
endif
augroup vim-go-debug
autocmd!
augroup END
augroup! vim-go-debug
endfunction
function! s:goto_file() abort
@ -393,22 +409,8 @@ function! s:expand_var() abort
endif
endfunction
function! s:start_cb(ch, json) abort
let res = json_decode(a:json)
if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
throw res.error
endif
if empty(res) || !has_key(res, 'result')
return
endif
for bt in res.result.Breakpoints
if bt.id >= 0
let s:state['breakpoint'][bt.id] = bt
exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
endif
endfor
let oldbuf = bufnr('%')
function! s:start_cb() abort
let l:winid = win_getid()
silent! only!
let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
@ -416,8 +418,9 @@ function! s:start_cb(ch, json) abort
return
endif
if exists('g:go_debug_windows["stack"]') && g:go_debug_windows['stack'] != ''
exe 'silent ' . g:go_debug_windows['stack']
let debugwindows = go#config#DebugWindows()
if has_key(debugwindows, "stack") && debugwindows['stack'] != ''
exe 'silent ' . debugwindows['stack']
silent file `='__GODEBUG_STACKTRACE__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugstacktrace
@ -425,16 +428,16 @@ function! s:start_cb(ch, json) abort
nmap <buffer> q <Plug>(go-debug-stop)
endif
if exists('g:go_debug_windows["out"]') && g:go_debug_windows['out'] != ''
exe 'silent ' . g:go_debug_windows['out']
if has_key(debugwindows, "out") && debugwindows['out'] != ''
exe 'silent ' . debugwindows['out']
silent file `='__GODEBUG_OUTPUT__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugoutput
nmap <buffer> q <Plug>(go-debug-stop)
endif
if exists('g:go_debug_windows["vars"]') && g:go_debug_windows['vars'] != ''
exe 'silent ' . g:go_debug_windows['vars']
if has_key(debugwindows, "vars") && debugwindows['vars'] != ''
exe 'silent ' . debugwindows['vars']
silent file `='__GODEBUG_VARIABLES__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugvariables
@ -462,75 +465,124 @@ function! s:start_cb(ch, json) abort
nnoremap <silent> <Plug>(go-debug-stop) :<C-u>call go#debug#Stop()<CR>
nnoremap <silent> <Plug>(go-debug-print) :<C-u>call go#debug#Print(expand('<cword>'))<CR>
nmap <F5> <Plug>(go-debug-continue)
nmap <F6> <Plug>(go-debug-print)
nmap <F9> <Plug>(go-debug-breakpoint)
nmap <F10> <Plug>(go-debug-next)
nmap <F11> <Plug>(go-debug-step)
if has('balloon_eval')
let s:balloonexpr=&balloonexpr
let s:ballooneval=&ballooneval
set balloonexpr=go#debug#BalloonExpr()
set ballooneval
set balloonexpr=go#debug#BalloonExpr()
set ballooneval
endif
exe bufwinnr(oldbuf) 'wincmd w'
call win_gotoid(l:winid)
augroup vim-go-debug
autocmd! * <buffer>
autocmd FileType go nmap <buffer> <F5> <Plug>(go-debug-continue)
autocmd FileType go nmap <buffer> <F6> <Plug>(go-debug-print)
autocmd FileType go nmap <buffer> <F9> <Plug>(go-debug-breakpoint)
autocmd FileType go nmap <buffer> <F10> <Plug>(go-debug-next)
autocmd FileType go nmap <buffer> <F11> <Plug>(go-debug-step)
augroup END
doautocmd vim-go-debug FileType go
endfunction
function! s:err_cb(ch, msg) abort
call go#util#EchoError(a:msg)
if get(s:state, 'ready', 0) != 0
call call('s:logger', ['ERR: ', a:ch, a:msg])
return
endif
let s:state['message'] += [a:msg]
endfunction
function! s:out_cb(ch, msg) abort
call go#util#EchoProgress(a:msg)
if get(s:state, 'ready', 0) != 0
call call('s:logger', ['OUT: ', a:ch, a:msg])
return
endif
let s:state['message'] += [a:msg]
" TODO: why do this in this callback?
if stridx(a:msg, g:go_debug_address) != -1
call ch_setoptions(a:ch, {
\ 'out_cb': function('s:logger', ['OUT: ']),
\ 'err_cb': function('s:logger', ['ERR: ']),
\})
if stridx(a:msg, go#config#DebugAddress()) != -1
if has('nvim')
let s:state['data'] = []
let l:state = {'databuf': ''}
" explicitly bind callback to state so that within it, self will
" always refer to state. See :help Partial for more information.
let l:state.on_data = function('s:on_data', [], l:state)
let l:ch = sockconnect('tcp', go#config#DebugAddress(), {'on_data': l:state.on_data, 'state': l:state})
if l:ch == 0
call go#util#EchoError("could not connect to debugger")
call go#job#Stop(s:state['job'])
return
endif
else
let l:ch = ch_open(go#config#DebugAddress(), {'mode': 'raw', 'timeout': 20000})
if ch_status(l:ch) !=# 'open'
call go#util#EchoError("could not connect to debugger")
call go#job#Stop(s:state['job'])
return
endif
endif
" Tell dlv about the breakpoints that the user added before delve started.
let l:breaks = copy(s:state.breakpoint)
let s:state['breakpoint'] = {}
for l:bt in values(l:breaks)
call go#debug#Breakpoint(bt.line)
let s:state['ch'] = l:ch
" After this block executes, Delve will be running with all the
" breakpoints setup, so this callback doesn't have to run again; just log
" future messages.
let s:state['ready'] = 1
" replace all the breakpoints set before delve started so that the ids won't overlap.
let l:breakpoints = s:list_breakpoints()
for l:bt in s:list_breakpoints()
exe 'sign unplace '. l:bt.id
call go#debug#Breakpoint(l:bt.line, l:bt.file)
endfor
call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb'))
call s:start_cb()
endif
endfunction
function! s:on_data(ch, data, event) dict abort
let l:data = self.databuf
for l:msg in a:data
let l:data .= l:msg
endfor
try
let l:res = json_decode(l:data)
let s:state['data'] = add(s:state['data'], l:res)
let self.databuf = ''
catch
" there isn't a complete message in databuf: buffer l:data and try
" again when more data comes in.
let self.databuf = l:data
finally
endtry
endfunction
" Start the debug mode. The first argument is the package name to compile and
" debug, anything else will be passed to the running program.
function! go#debug#Start(is_test, ...) abort
if has('nvim')
call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(')
return
endif
call go#cmd#autowrite()
if !go#util#has_job()
call go#util#EchoError('This feature requires Vim 8.0.0087 or newer with +job.')
call go#util#EchoError('This feature requires either Vim 8.0.0087 or newer with +job or Neovim.')
return
endif
" It's already running.
if has_key(s:state, 'job') && job_status(s:state['job']) == 'run'
return
if has_key(s:state, 'job')
return s:state['job']
endif
let s:start_args = a:000
if go#util#HasDebug('debugger-state')
let g:go_debug_diag = s:state
call go#config#SetDebugDiag(s:state)
endif
" cd in to test directory; this is also what running "go test" does.
if a:is_test
lcd %:p:h
endif
let s:state.is_test = a:is_test
let dlv = go#path#CheckBinPath("dlv")
if empty(dlv)
return
@ -539,14 +591,27 @@ function! go#debug#Start(is_test, ...) abort
try
if len(a:000) > 0
let l:pkgname = a:1
" Expand .; otherwise this won't work from a tmp dir.
if l:pkgname[0] == '.'
let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:]
let l:pkgname = go#package#FromPath(l:pkgname)
endif
else
let l:pkgname = go#package#FromPath(getcwd())
endif
if l:pkgname is -1
call go#util#EchoError('could not determine package name')
return
endif
" cd in to test directory; this is also what running "go test" does.
if a:is_test
" TODO(bc): Either remove this if it's ok to do so or else record it and
" reset cwd after the job completes.
lcd %:p:h
endif
let s:state.is_test = a:is_test
let l:args = []
if len(a:000) > 1
let l:args = ['--'] + a:000[1:]
@ -555,29 +620,40 @@ function! go#debug#Start(is_test, ...) abort
let l:cmd = [
\ dlv,
\ (a:is_test ? 'test' : 'debug'),
\ l:pkgname,
\ '--output', tempname(),
\ '--headless',
\ '--api-version', '2',
\ '--log',
\ '--listen', g:go_debug_address,
\ '--accept-multiclient',
\ '--listen', go#config#DebugAddress(),
\]
if get(g:, 'go_build_tags', '') isnot ''
let l:cmd += ['--build-flags', '--tags=' . g:go_build_tags]
let l:debugLogOutput = go#config#DebugLogOutput()
if l:debugLogOutput != ''
let cmd += ['--log', '--log-output', l:debugLogOutput]
endif
let l:buildtags = go#config#BuildTags()
if buildtags isnot ''
let l:cmd += ['--build-flags', '--tags=' . buildtags]
endif
let l:cmd += l:args
call go#util#EchoProgress('Starting GoDebug...')
let s:state['message'] = []
let s:state['job'] = job_start(l:cmd, {
\ 'out_cb': function('s:out_cb'),
\ 'err_cb': function('s:err_cb'),
\ 'exit_cb': function('s:exit'),
\ 'stoponexit': 'kill',
\})
let l:opts = {
\ 'for': 'GoDebug',
\ 'statustype': 'debug',
\ 'complete': function('s:complete'),
\ }
let l:opts = go#job#Options(l:opts)
let l:opts.out_cb = function('s:out_cb')
let l:opts.err_cb = function('s:err_cb')
let l:opts.stoponexit = 'kill'
let s:state['job'] = go#job#Start(l:cmd, l:opts)
catch
call go#util#EchoError(v:exception)
endtry
return s:state['job']
endfunction
" Translate a reflect kind constant to a human string.
@ -670,13 +746,12 @@ endfunction
function! s:eval(arg) abort
try
let res = s:call_jsonrpc('RPCServer.State')
let goroutineID = res.result.State.currentThread.goroutineID
let res = s:call_jsonrpc('RPCServer.Eval', {
let l:res = s:call_jsonrpc('RPCServer.State')
let l:res = s:call_jsonrpc('RPCServer.Eval', {
\ 'expr': a:arg,
\ 'scope': {'GoroutineID': goroutineID}
\ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID}
\ })
return s:eval_tree(res.result.Variable, 0)
return s:eval_tree(l:res.result.Variable, 0)
catch
call go#util#EchoError(v:exception)
return ''
@ -726,12 +801,11 @@ endfunction
function! go#debug#Set(symbol, value) abort
try
let res = s:call_jsonrpc('RPCServer.State')
let goroutineID = res.result.State.currentThread.goroutineID
let l:res = s:call_jsonrpc('RPCServer.State')
call s:call_jsonrpc('RPCServer.Set', {
\ 'symbol': a:symbol,
\ 'value': a:value,
\ 'scope': {'GoroutineID': goroutineID}
\ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID}
\ })
catch
call go#util#EchoError(v:exception)
@ -742,27 +816,20 @@ endfunction
function! s:update_stacktrace() abort
try
let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
call s:show_stacktrace(res)
let l:res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
call s:show_stacktrace(l:res)
catch
call go#util#EchoError(v:exception)
endtry
endfunction
function! s:stack_cb(ch, json) abort
function! s:stack_cb(res) abort
let s:stack_name = ''
let res = json_decode(a:json)
if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
call go#util#EchoError(res.error)
call s:clearState()
call go#debug#Restart()
return
endif
if empty(res) || !has_key(res, 'result')
if empty(a:res) || !has_key(a:res, 'result')
return
endif
call s:update_breakpoint(res)
call s:update_breakpoint(a:res)
call s:update_stacktrace()
call s:update_variables()
endfunction
@ -780,7 +847,7 @@ function! go#debug#Stack(name) abort
endif
" Add a breakpoint to the main.Main if the user didn't define any.
if len(s:state['breakpoint']) is 0
if len(s:list_breakpoints()) is 0
if go#debug#Breakpoint() isnot 0
let s:state.running = 0
return
@ -793,36 +860,34 @@ function! go#debug#Stack(name) abort
call s:call_jsonrpc('RPCServer.CancelNext')
endif
let s:stack_name = l:name
call s:call_jsonrpc('RPCServer.Command', function('s:stack_cb'), {'name': l:name})
try
let res = s:call_jsonrpc('RPCServer.Command', {'name': l:name})
call s:stack_cb(res)
catch
call go#util#EchoError(v:exception)
call s:clearState()
call go#debug#Restart()
endtry
catch
call go#util#EchoError(v:exception)
endtry
endfunction
function! go#debug#Restart() abort
try
call job_stop(s:state['job'])
while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run'
sleep 50m
endwhile
call go#cmd#autowrite()
try
call s:stop()
let l:breaks = s:state['breakpoint']
let s:state = {
\ 'rpcid': 1,
\ 'running': 0,
\ 'breakpoint': {},
\ 'currentThread': {},
\ 'localVars': {},
\ 'functionArgs': {},
\ 'message': [],
\}
" Preserve breakpoints.
for bt in values(l:breaks)
" TODO: should use correct filename
exe 'sign unplace '. bt.id .' file=' . bt.file
call go#debug#Breakpoint(bt.line)
endfor
call call('go#debug#Start', s:start_args)
catch
call go#util#EchoError(v:exception)
@ -837,47 +902,45 @@ endfunction
" Toggle breakpoint. Returns 0 on success and 1 on failure.
function! go#debug#Breakpoint(...) abort
let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
let l:linenr = line('.')
" Get line number from argument.
if len(a:000) > 0
let linenr = str2nr(a:1)
if linenr is 0
let l:linenr = str2nr(a:1)
if l:linenr is 0
call go#util#EchoError('not a number: ' . a:1)
return 0
endif
else
let linenr = line('.')
if len(a:000) > 1
let l:filename = a:2
endif
endif
try
" Check if we already have a breakpoint for this line.
let found = v:none
for k in keys(s:state.breakpoint)
let bt = s:state.breakpoint[k]
if bt.file == l:filename && bt.line == linenr
let found = bt
let l:found = {}
for l:bt in s:list_breakpoints()
if l:bt.file is# l:filename && l:bt.line is# l:linenr
let l:found = l:bt
break
endif
endfor
" Remove breakpoint.
if type(found) == v:t_dict
call remove(s:state['breakpoint'], bt.id)
exe 'sign unplace '. found.id .' file=' . found.file
if type(l:found) == v:t_dict && !empty(l:found)
exe 'sign unplace '. l:found.id .' file=' . l:found.file
if s:isActive()
let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': found.id})
let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': l:found.id})
endif
" Add breakpoint.
else
if s:isActive()
let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}})
let bt = res.result.Breakpoint
exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
let s:state['breakpoint'][bt.id] = bt
let l:res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': l:linenr}})
let l:bt = res.result.Breakpoint
exe 'sign place '. l:bt.id .' line=' . l:bt.line . ' name=godebugbreakpoint file=' . l:bt.file
else
let id = len(s:state['breakpoint']) + 1
let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr}
exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename
let l:id = len(s:list_breakpoints()) + 1
exe 'sign place ' . l:id . ' line=' . l:linenr . ' name=godebugbreakpoint file=' . l:filename
endif
endif
catch
@ -888,17 +951,43 @@ function! go#debug#Breakpoint(...) abort
return 0
endfunction
sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint
sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent
function! s:list_breakpoints()
" :sign place
" --- Signs ---
" Signs for a.go:
" line=15 id=2 name=godebugbreakpoint
" line=16 id=1 name=godebugbreakpoint
" Signs for a_test.go:
" line=6 id=3 name=godebugbreakpoint
fun! s:hi()
hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black
hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White
endfun
augroup vim-go-breakpoint
autocmd!
autocmd ColorScheme * call s:hi()
augroup end
call s:hi()
let l:signs = []
let l:file = ''
for l:line in split(execute('sign place'), '\n')[1:]
if l:line =~# '^Signs for '
let l:file = l:line[10:-2]
continue
endif
if l:line !~# 'name=godebugbreakpoint'
continue
endif
let l:sign = matchlist(l:line, '\vline\=(\d+) +id\=(\d+)')
call add(l:signs, {
\ 'id': l:sign[2],
\ 'file': fnamemodify(l:file, ':p'),
\ 'line': str2nr(l:sign[1]),
\ })
endfor
return l:signs
endfunction
sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint
sign define godebugcurline text== texthl=GoDebugCurrent linehl=GoDebugCurrent
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,101 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! Test_GoDebugStart_Empty() abort
call s:debug()
endfunction
function! Test_GoDebugStart_RelativePackage() abort
call s:debug('./debug/debugmain')
endfunction
function! Test_GoDebugStart_Package() abort
call s:debug('debug/debugmain')
endfunction
function! Test_GoDebugStart_Errors() abort
if !go#util#has_job()
return
endif
try
let l:expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': '# debug/compilerror'},
\ {'lnum': 6, 'bufnr': 7, 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': ' syntax error: unexpected newline, expecting comma or )'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exit status 2'}
\]
call setqflist([], 'r')
let l:tmp = gotest#load_fixture('debug/compilerror/main.go')
call assert_false(exists(':GoDebugStop'))
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
execute l:cd . ' debug/compilerror'
call go#debug#Start(0)
let l:actual = getqflist()
let l:start = reltime()
while len(l:actual) == 0 && reltimefloat(reltime(l:start)) < 10
sleep 100m
let l:actual = getqflist()
endwhile
call gotest#assert_quickfix(l:actual, l:expected)
call assert_false(exists(':GoDebugStop'))
finally
call delete(l:tmp, 'rf')
" clear the quickfix lists
call setqflist([], 'r')
endtry
endfunction
function! s:debug(...) abort
if !go#util#has_job()
return
endif
try
let l:tmp = gotest#load_fixture('debug/debugmain/debugmain.go')
call go#debug#Breakpoint(6)
call assert_false(exists(':GoDebugStop'))
if a:0 == 0
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
execute l:cd . ' debug/debugmain'
let l:job = go#debug#Start(0)
else
let l:job = go#debug#Start(0, a:1)
endif
let l:start = reltime()
while !exists(':GoDebugStop') && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_true(exists(':GoDebugStop'))
call gotest#assert_quickfix(getqflist(), [])
call go#debug#Stop()
if !has('nvim')
call assert_equal(job_status(l:job), 'dead')
endif
call assert_false(exists(':GoDebugStop'))
finally
call go#debug#Breakpoint(6)
call delete(l:tmp, 'rf')
endtry
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,11 +1,12 @@
if !exists('g:go_decls_mode')
let g:go_decls_mode = ''
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#decls#Decls(mode, ...) abort
if g:go_decls_mode == 'ctrlp'
let decls_mode = go#config#DeclsMode()
if decls_mode == 'ctrlp'
call ctrlp#init(call("ctrlp#decls#cmd", [a:mode] + a:000))
elseif g:go_decls_mode == 'fzf'
elseif decls_mode == 'fzf'
call call("fzf#decls#cmd", [a:mode] + a:000)
else
if globpath(&rtp, 'plugin/ctrlp.vim') != ""
@ -18,4 +19,8 @@ function! go#decls#Decls(mode, ...) abort
end
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,83 +1,86 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:go_stack = []
let s:go_stack_level = 0
function! go#def#Jump(mode) abort
function! go#def#Jump(mode, type) abort
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" 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')
let bin_name = go#config#DefMode()
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 l:cmd = ['godef',
\ '-f=' . l:fname,
\ '-o=' . go#util#OffsetCursor(),
\ '-t']
let bin_path = go#path#CheckBinPath("godef")
if empty(bin_path)
return
endif
let command = printf("%s -f=%s -o=%s -t", go#util#Shellescape(bin_path),
\ go#util#Shellescape(fname), go#util#OffsetCursor())
let out = go#util#System(command)
if exists("l:tmpname")
call delete(l:tmpname)
if &modified
let l:stdin_content = join(go#util#GetLines(), "\n")
call add(l:cmd, "-i")
let [l:out, l:err] = go#util#ExecInDir(l:cmd, l:stdin_content)
else
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
endif
elseif bin_name == 'guru'
let bin_path = go#path#CheckBinPath("guru")
if empty(bin_path)
return
let cmd = [go#path#CheckBinPath(bin_name)]
let buildtags = go#config#BuildTags()
if buildtags isnot ''
let cmd += ['-tags', buildtags]
endif
let cmd = [bin_path]
let stdin_content = ""
if &modified
let content = join(go#util#GetLines(), "\n")
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_build_tags')
let tags = get(g:, 'go_build_tags')
call extend(cmd, ["-tags", tags])
endif
let fname = fname.':#'.go#util#OffsetCursor()
call extend(cmd, ["definition", fname])
call extend(cmd, ["definition", fname . ':#' . go#util#OffsetCursor()])
if go#util#has_job()
let l:state = {}
let l:spawn_args = {
\ 'cmd': cmd,
\ 'complete': function('s:jump_to_declaration_cb', [a:mode, bin_name]),
\ 'complete': function('s:jump_to_declaration_cb', [a:mode, bin_name], l:state),
\ 'for': '_',
\ 'statustype': 'searching declaration',
\ }
if &modified
let l:spawn_args.input = stdin_content
endif
call go#util#EchoProgress("searching declaration ...")
call s:def_job(spawn_args)
call s:def_job(spawn_args, l:state)
return
endif
let command = join(cmd, " ")
if &modified
let out = go#util#System(command, stdin_content)
let [l:out, l:err] = go#util#ExecInDir(l:cmd, l:stdin_content)
else
let out = go#util#System(command)
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
endif
elseif bin_name == 'gopls'
let [l:line, l:col] = getpos('.')[1:2]
" delegate to gopls, with an empty job object and an exit status of 0
" (they're irrelevant for gopls).
if a:type
call go#lsp#TypeDef(l:fname, l:line, l:col, function('s:jump_to_declaration_cb', [a:mode, 'gopls', {}, 0]))
else
call go#lsp#Definition(l:fname, l:line, l:col, function('s:jump_to_declaration_cb', [a:mode, 'gopls', {}, 0]))
endif
return
else
call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru]')
call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru, gopls]')
return
endif
if go#util#ShellError() != 0
if l:err
call go#util#EchoError(out)
return
endif
@ -85,19 +88,26 @@ function! go#def#Jump(mode) abort
call go#def#jump_to_declaration(out, a:mode, bin_name)
endfunction
function! s:jump_to_declaration_cb(mode, bin_name, job, exit_status, data) abort
function! s:jump_to_declaration_cb(mode, bin_name, job, exit_status, data) abort dict
if a:exit_status != 0
return
endif
call go#def#jump_to_declaration(a:data[0], a:mode, a:bin_name)
call go#util#EchoSuccess(fnamemodify(a:data[0], ":t"))
" capture the active window so that callbacks for jobs, exit_cb and
" close_cb, and callbacks for gopls can return to it when a:mode caused a
" split.
let self.winid = win_getid(winnr())
endfunction
" go#def#jump_to_declaration parses out (expected to be
" 'filename:line:col: message').
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.
" append the type information to the same line so it will be parsed
" correctly using guru's output format.
" This makes it compatible with guru output.
let final_out = join(split(a:out, '\n'), ':')
endif
@ -110,10 +120,24 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
let parts = split(out, ':')
endif
if len(parts) == 0
call go#util#EchoError('go jump_to_declaration '. a:bin_name .' output is not valid.')
return
endif
let line = 1
let col = 1
let ident = 0
let filename = parts[0]
let line = parts[1]
let col = parts[2]
let ident = parts[3]
if len(parts) > 1
let line = parts[1]
endif
if len(parts) > 2
let col = parts[2]
endif
if len(parts) > 3
let ident = parts[3]
endif
" Remove anything newer than the current position, just like basic
" vim tag support
@ -138,7 +162,7 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
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
if go#config#DefReuseBuffer() && bufloaded(filename) != 0 && bufwinnr(filename) != -1
" jumpt to existing buffer if it exists
execute bufwinnr(filename) . 'wincmd w'
else
@ -291,14 +315,25 @@ function! go#def#Stack(...) abort
endif
endfunction
function s:def_job(args) abort
let callbacks = go#job#Spawn(a:args)
function s:def_job(args, state) abort
let l:start_options = go#job#Options(a:args)
let start_options = {
\ 'callback': callbacks.callback,
\ 'exit_cb': callbacks.exit_cb,
\ 'close_cb': callbacks.close_cb,
\ }
let l:state = a:state
function! s:exit_cb(next, job, exitval) dict
call call(a:next, [a:job, a:exitval])
if has_key(self, 'winid')
call win_gotoid(self.winid)
endif
endfunction
let l:start_options.exit_cb = funcref('s:exit_cb', [l:start_options.exit_cb], l:state)
function! s:close_cb(next, ch) dict
call call(a:next, [a:ch])
if has_key(self, 'winid')
call win_gotoid(self.winid)
endif
endfunction
let l:start_options.close_cb = funcref('s:close_cb', [l:start_options.close_cb], l:state)
if &modified
let l:tmpname = tempname()
@ -307,7 +342,11 @@ function s:def_job(args) abort
let l:start_options.in_name = l:tmpname
endif
call job_start(a:args.cmd, start_options)
call go#job#Start(a:args.cmd, l:start_options)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_jump_to_declaration_guru() abort
try
let l:filename = 'def/jump.go'
@ -34,4 +38,37 @@ func! Test_jump_to_declaration_godef() abort
endtry
endfunc
func! Test_Jump_leaves_lists() abort
try
let filename = 'def/jump.go'
let l:tmp = gotest#load_fixture(l:filename)
let expected = [{'lnum': 10, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'quux'}]
call setloclist(winnr(), copy(expected), 'r' )
call setqflist(copy(expected), 'r' )
let l:bufnr = bufnr('%')
call cursor(6, 7)
call go#def#Jump('', 0)
let start = reltime()
while bufnr('%') == l:bufnr && reltimefloat(reltime(start)) < 10
sleep 100m
endwhile
let actual = getloclist(winnr())
call gotest#assert_quickfix(actual, expected)
let actual = getqflist()
call gotest#assert_quickfix(actual, expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -2,11 +2,11 @@
" Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file.
let s:buf_nr = -1
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
if !exists("g:go_doc_command")
let g:go_doc_command = ["godoc"]
endif
let s:buf_nr = -1
function! go#doc#OpenBrowser(...) abort
" check if we have gogetdoc as it gives us more and accurate information.
@ -14,8 +14,8 @@ function! go#doc#OpenBrowser(...) abort
" 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
let [l:json_out, l:err] = s:gogetdoc(1)
if l:err
call go#util#EchoError(json_out)
return
endif
@ -29,15 +29,13 @@ function! go#doc#OpenBrowser(...) abort
let name = out["name"]
let decl = out["decl"]
let godoc_url = s:custom_godoc_url()
let godoc_url = go#config#DocUrl()
let godoc_url .= "/" . import
if decl !~ "^package"
let godoc_url .= "#" . name
endif
echo godoc_url
call go#tool#OpenBrowser(godoc_url)
call go#util#OpenBrowser(godoc_url)
return
endif
@ -50,28 +48,22 @@ function! go#doc#OpenBrowser(...) abort
let exported_name = pkgs[1]
" example url: https://godoc.org/github.com/fatih/set#Set
let godoc_url = s:custom_godoc_url() . "/" . pkg . "#" . exported_name
call go#tool#OpenBrowser(godoc_url)
let godoc_url = go#config#DocUrl() . "/" . pkg . "#" . exported_name
call go#util#OpenBrowser(godoc_url)
endfunction
function! go#doc#Open(newmode, mode, ...) abort
" With argument: run "godoc [arg]".
if len(a:000)
if empty(go#path#CheckBinPath(g:go_doc_command[0]))
return
endif
let command = printf("%s %s", go#util#Shelljoin(g:go_doc_command), join(a:000, ' '))
let out = go#util#System(command)
" Without argument: run gogetdoc on cursor position.
else
let out = s:gogetdoc(0)
let [l:out, l:err] = go#util#Exec(['go', 'doc'] + a:000)
else " Without argument: run gogetdoc on cursor position.
let [l:out, l:err] = s:gogetdoc(0)
if out == -1
return
endif
endif
if go#util#ShellError() != 0
if l:err
call go#util#EchoError(out)
return
endif
@ -97,7 +89,7 @@ function! s:GodocView(newposition, position, content) abort
if !is_visible
if a:position == "split"
" cap window height to 20, but resize it for smaller contents
let max_height = get(g:, "go_doc_max_height", 20)
let max_height = go#config#DocMaxHeight()
let content_height = len(split(a:content, "\n"))
if content_height > max_height
exe 'resize ' . max_height
@ -129,39 +121,29 @@ function! s:GodocView(newposition, position, content) abort
setlocal nomodifiable
sil normal! gg
" close easily with <esc> or enter
" close easily with enter
noremap <buffer> <silent> <CR> :<C-U>close<CR>
noremap <buffer> <silent> <Esc> :<C-U>close<CR>
" make sure any key that sends an escape as a prefix (e.g. the arrow keys)
" don't cause the window to close.
nnoremap <buffer> <silent> <Esc>[ <Esc>[
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 = [go#util#Shellescape(bin_path)]
let offset = go#util#OffsetCursor()
let fname = expand("%:p:gs!\\!/!")
let pos = shellescape(fname.':#'.offset)
let cmd += ["-pos", pos]
let l:cmd = [
\ 'gogetdoc',
\ '-tags', go#config#BuildTags(),
\ '-pos', expand("%:p:gs!\\!/!") . ':#' . go#util#OffsetCursor()]
if a:json
let cmd += ["-json"]
let l:cmd += ['-json']
endif
let command = join(cmd, " ")
if &modified
let command .= " -modified"
let out = go#util#System(command, go#util#archive())
else
let out = go#util#System(command)
let l:cmd += ['-modified']
return go#util#Exec(l:cmd, go#util#archive())
endif
return out
return go#util#Exec(l:cmd)
endfunction
" returns the package and exported name. exported name might be empty.
@ -206,18 +188,8 @@ function! s:godocWord(args) abort
return [pkg, exported_name]
endfunction
function! s:custom_godoc_url() abort
let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org')
if godoc_url isnot 'https://godoc.org'
" strip last '/' character if available
let last_char = strlen(godoc_url) - 1
if godoc_url[last_char] == '/'
let godoc_url = strpart(godoc_url, 0, last_char)
endif
" custom godoc installations expect /pkg before package names
let godoc_url .= "/pkg"
endif
return godoc_url
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,8 +1,14 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#fillstruct#FillStruct() abort
let l:cmd = ['fillstruct',
\ '-file', bufname(''),
\ '-offset', go#util#OffsetCursor(),
\ '-line', line('.')]
" Needs: https://github.com/davidrjenni/reftools/pull/14
"\ '-tags', go#config#BuildTags()]
" Read from stdin if modified.
if &modified
@ -59,4 +65,8 @@ function! go#fillstruct#FillStruct() abort
endtry
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_fillstruct() abort
try
let l:tmp = gotest#write_file('a/a.go', [
@ -87,4 +91,8 @@ func! Test_fillstruct_two_cursor() abort
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -5,21 +5,9 @@
" 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"
endif
if !exists('g:go_fmt_options')
let g:go_fmt_options = ''
endif
if !exists('g:go_fmt_fail_silently')
let g:go_fmt_fail_silently = 0
endif
if !exists("g:go_fmt_experimental")
let g:go_fmt_experimental = 0
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" we have those problems :
" http://stackoverflow.com/questions/12741977/prevent-vim-from-updating-its-undo-tree
@ -30,7 +18,7 @@ endif
" this and have VimL experience, please look at the function for
" improvements, patches are welcome :)
function! go#fmt#Format(withGoimport) abort
if g:go_fmt_experimental == 1
if go#config#FmtExperimental()
" Using winsaveview to save/restore cursor state has the problem of
" closing folds on save:
" https://github.com/fatih/vim-go/issues/502
@ -64,18 +52,18 @@ function! go#fmt#Format(withGoimport) abort
let l:tmpname = tr(l:tmpname, '\', '/')
endif
let bin_name = g:go_fmt_command
let bin_name = go#config#FmtCommand()
if a:withGoimport == 1
let bin_name = "goimports"
endif
let current_col = col('.')
let out = go#fmt#run(bin_name, l:tmpname, expand('%'))
let [l:out, l:err] = go#fmt#run(bin_name, l:tmpname, expand('%'))
let diff_offset = len(readfile(l:tmpname)) - line('$')
if go#util#ShellError() == 0
if l:err == 0
call go#fmt#update_file(l:tmpname, expand('%'))
elseif g:go_fmt_fail_silently == 0
elseif !go#config#FmtFailSilently()
let errors = s:parse_errors(expand('%'), out)
call s:show_errors(errors)
endif
@ -83,7 +71,7 @@ function! go#fmt#Format(withGoimport) abort
" We didn't use the temp file, so clean up
call delete(l:tmpname)
if g:go_fmt_experimental == 1
if go#config#FmtExperimental()
" restore our undo history
silent! exe 'rundo ' . tmpundofile
call delete(tmpundofile)
@ -154,64 +142,27 @@ endfunction
" run runs the gofmt/goimport command for the given source file and returns
" the output of the executed command. Target is the real file to be formatted.
function! go#fmt#run(bin_name, source, target)
let cmd = s:fmt_cmd(a:bin_name, a:source, a:target)
if empty(cmd)
let l:cmd = s:fmt_cmd(a:bin_name, a:source, a:target)
if empty(l:cmd)
return
endif
let command = join(cmd, " ")
" execute our command...
let out = go#util#System(command)
return out
return go#util#Exec(l:cmd)
endfunction
" fmt_cmd returns a dict that contains the command to execute gofmt (or
" goimports). args is dict with
" fmt_cmd returns the command to run as a list.
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 bin_path = go#util#Shellescape(bin_path)
let cmd = [bin_path]
call add(cmd, "-w")
let l:cmd = [a:bin_name, '-w']
" add the options for binary (if any). go_fmt_options was by default of type
" string, however to allow customization it's now a dictionary of binary
" name mapping to options.
let opts = g:go_fmt_options
if type(g:go_fmt_options) == type({})
let opts = has_key(g:go_fmt_options, a:bin_name) ? g:go_fmt_options[a:bin_name] : ""
let opts = go#config#FmtOptions()
if type(opts) == type({})
let opts = has_key(opts, a:bin_name) ? opts[a:bin_name] : ""
endif
call extend(cmd, split(opts, " "))
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
" use the filename without the fully qualified name if the tree is
" symlinked into the GOPATH, goimports won't work properly.
call extend(cmd, ["-srcdir", shellescape(a:target)])
let &shellslash = ssl_save
endif
if a:bin_name is# 'goimports'
call extend(cmd, ["-srcdir", a:target])
endif
call add(cmd, a:source)
@ -254,14 +205,18 @@ function! s:show_errors(errors) abort
endfunction
function! go#fmt#ToggleFmtAutoSave() abort
if get(g:, "go_fmt_autosave", 1)
let g:go_fmt_autosave = 0
if go#config#FmtAutosave()
call go#config#SetFmtAutosave(0)
call go#util#EchoProgress("auto fmt disabled")
return
end
let g:go_fmt_autosave = 1
call go#config#SetFmtAutosave(1)
call go#util#EchoProgress("auto fmt enabled")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_run_fmt() abort
let actual_file = tempname()
call writefile(readfile("test-fixtures/fmt/hello.go"), actual_file)
@ -46,4 +50,8 @@ func! Test_goimports() abort
call assert_equal(expected, actual)
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,5 +1,9 @@
" guru.vim -- Vim integration for the Go guru.
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" guru_cmd returns a dict that contains the command to execute guru. args
" is dict with following options:
" mode : guru mode, such as 'implements'
@ -14,14 +18,9 @@ function! s:guru_cmd(args) range abort
let format = a:args.format
let needs_scope = a:args.needs_scope
let selected = a:args.selected
let postype = get(a:args, 'postype', 'cursor')
let result = {}
let pkg = go#package#ImportPath()
" 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")
@ -30,9 +29,8 @@ function! s:guru_cmd(args) range abort
endif
" start constructing the command
let cmd = [bin_path]
let cmd = [bin_path, '-tags', go#config#BuildTags()]
let filename = fnamemodify(expand("%"), ':p:gs?\\?/?')
if &modified
let result.stdin_content = go#util#archive()
call add(cmd, "-modified")
@ -43,59 +41,41 @@ function! s:guru_cmd(args) range abort
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"}
let scopes = go#config#GuruScope()
if empty(scopes)
" 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
if needs_scope
let pkg = go#package#ImportPath()
if pkg == -1
return {'err': "current directory is not inside of a valid GOPATH"}
endif
let scopes = [pkg]
endif
let scopes = get(g:, 'go_guru_scope')
endif
" now add the scope to our command if there is any
" Add the scope.
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 !has("nvim") && !go#util#has_job() | let scopes = go#util#Shelllist(scopes) | endif
" guru expect a comma-separated list of patterns, construct it
" guru expect a comma-separated list of patterns.
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)
if postype == 'balloon'
let pos = printf("#%s", go#util#Offset(v:beval_lnum, v:beval_col))
else
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
endif
let filename .= ':'.pos
call extend(cmd, [mode, filename])
let l:filename = fnamemodify(expand("%"), ':p:gs?\\?/?') . ':' . pos
call extend(cmd, [mode, l:filename])
let result.cmd = cmd
return result
@ -119,51 +99,22 @@ function! s:sync_guru(args) abort
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)
if has_key(l:result, 'stdin_content')
let [l:out, l:err] = go#util#Exec(l:result.cmd, l:result.stdin_content)
else
let out = go#util#System(command)
let [l:out, l:err] = go#util#Exec(l:result.cmd)
endif
if has_key(a:args, 'custom_parse')
call a:args.custom_parse(go#util#ShellError(), out, a:args.mode)
call a:args.custom_parse(l:err, l:out, a:args.mode)
else
call s:parse_guru_output(go#util#ShellError(), out, a:args.mode)
call s:parse_guru_output(l:err, l:out, a:args.mode)
endif
return out
return l:out
endfunc
" use vim or neovim job api as appropriate
function! s:job_start(cmd, start_options) abort
if go#util#has_job()
return job_start(a:cmd, a:start_options)
endif
let opts = {'stdout_buffered': v:true, 'stderr_buffered': v:true}
function opts.on_stdout(job_id, data, event) closure
call a:start_options.callback(a:job_id, join(a:data, "\n"))
endfunction
function opts.on_stderr(job_id, data, event) closure
call a:start_options.callback(a:job_id, join(a:data, "\n"))
endfunction
function opts.on_exit(job_id, exit_code, event) closure
call a:start_options.exit_cb(a:job_id, a:exit_code)
call a:start_options.close_cb(a:job_id)
endfunction
" use a shell for input redirection if needed
let cmd = a:cmd
if has_key(a:start_options, 'in_io') && a:start_options.in_io ==# 'file' && !empty(a:start_options.in_name)
let cmd = ['/bin/sh', '-c', join(a:cmd, ' ') . ' <' . a:start_options.in_name]
endif
return jobstart(cmd, opts)
endfunction
" async_guru runs guru in async mode with the given arguments
function! s:async_guru(args) abort
let result = s:guru_cmd(a:args)
@ -172,92 +123,51 @@ function! s:async_guru(args) abort
return
endif
if !has_key(a:args, 'disable_progress')
if a:args.needs_scope
call go#util#EchoProgress("analysing with scope " . result.scope .
\ " (see ':help go-guru-scope' if this doesn't work)...")
endif
endif
let state = {
\ 'status_dir': expand('%:p:h'),
\ 'statusline_type': printf("%s", a:args.mode),
\ 'mode': a:args.mode,
\ 'status': {},
\ 'exitval': 0,
\ 'closed': 0,
\ 'exited': 0,
\ 'messages': [],
\ 'parse' : get(a:args, 'custom_parse', funcref("s:parse_guru_output"))
\ }
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
" explicitly bind complete to state so that within it, self will
" always refer to state. See :help Partial for more information.
let state.complete = function('s:complete', [], state)
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let opts = {
\ 'statustype': get(a:args, 'statustype', a:args.mode),
\ 'for': '_',
\ 'errorformat': "%f:%l.%c-%[%^:]%#:\ %m,%f:%l:%c:\ %m",
\ 'complete': state.complete,
\ }
let status = {
\ 'desc': 'last status',
\ 'type': self.statusline_type,
\ 'state': "finished",
\ }
if has_key(a:args, 'disable_progress')
let opts.statustype = ''
endif
if a:exitval
let self.exitval = a:exitval
let status.state = "failed"
endif
call go#statusline#Update(self.status_dir, status)
if self.closed
call self.complete()
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call self.complete()
endif
endfunction
function state.complete() dict
let out = join(self.messages, "\n")
call self.parse(self.exitval, out, self.mode)
endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = {
\ 'callback': function('s:callback', [], state),
\ 'exit_cb': function('s:exit_cb', [], state),
\ 'close_cb': function('s:close_cb', [], state)
\ }
let opts = go#job#Options(l:opts)
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
let l:opts.in_io = "file"
let l:opts.in_name = l:tmpname
endif
call go#statusline#Update(state.status_dir, {
\ 'desc': "current status",
\ 'type': state.statusline_type,
\ 'state': "analysing",
\})
call go#job#Start(result.cmd, opts)
return s:job_start(result.cmd, start_options)
if a:args.needs_scope && go#config#EchoCommandInfo() && !has_key(a:args, 'disable_progress')
call go#util#EchoProgress("analysing with scope " . result.scope .
\ " (see ':help go-guru-scope' if this doesn't work)...")
endif
endfunc
function! s:complete(job, exit_status, messages) dict abort
let output = join(a:messages, "\n")
call self.parse(a:exit_status, output, self.mode)
endfunction
" run_guru runs the given guru argument
function! s:run_guru(args) abort
if has('nvim') || go#util#has_job()
if go#util#has_job()
let res = s:async_guru(a:args)
else
let res = s:sync_guru(a:args)
@ -278,6 +188,18 @@ function! go#guru#Implements(selected) abort
call s:run_guru(args)
endfunction
" Shows the set of possible objects to which a pointer may point.
function! go#guru#PointsTo(selected) abort
let l:args = {
\ 'mode': 'pointsto',
\ 'format': 'plain',
\ 'selected': a:selected,
\ 'needs_scope': 1,
\ }
call s:run_guru(l: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
@ -309,7 +231,7 @@ function! go#guru#Describe(selected) abort
call s:run_guru(args)
endfunction
function! go#guru#DescribeInfo() abort
function! go#guru#DescribeInfo(showstatus) 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
@ -318,103 +240,103 @@ function! go#guru#DescribeInfo() abort
return
endif
function! s:info(exit_val, output, mode)
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': 0,
\ 'custom_parse': function('s:info'),
\ 'disable_progress': 1,
\ 'disable_progress': a:showstatus == 0,
\ }
call s:run_guru(args)
endfunction
function! s:info(exit_val, output, mode)
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#ShowInfo(info)
endfunction
" Show possible targets of selected function call
function! go#guru#Callees(selected) abort
let args = {
@ -493,7 +415,7 @@ function! go#guru#Referrers(selected) abort
call s:run_guru(args)
endfunction
function! go#guru#SameIds() abort
function! go#guru#SameIds(showstatus) 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")
@ -516,6 +438,9 @@ function! go#guru#SameIds() abort
\ 'needs_scope': 0,
\ 'custom_parse': function('s:same_ids_highlight'),
\ }
if !a:showstatus
let args.disable_progress = 1
endif
call s:run_guru(args)
endfunction
@ -524,20 +449,20 @@ function! s:same_ids_highlight(exit_val, output, mode) abort
call go#guru#ClearSameIds() " run after calling guru to reduce flicker.
if a:output[0] !=# '{'
if !get(g:, 'go_auto_sameids', 0)
if !go#config#AutoSameids()
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)
if type(result) != type({}) && !go#config#AutoSameids()
call go#util#EchoError("malformed output from guru")
return
endif
if !has_key(result, 'sameids')
if !get(g:, 'go_auto_sameids', 0)
if !go#config#AutoSameids()
call go#util#EchoError("no same_ids founds for the given identifier")
endif
return
@ -563,12 +488,12 @@ function! s:same_ids_highlight(exit_val, output, mode) abort
call matchaddpos('goSameId', [[str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]])
endfor
if get(g:, "go_auto_sameids", 0)
if go#config#AutoSameids()
" re-apply SameIds at the current cursor position at the time the buffer
" is redisplayed: e.g. :edit, :GoRename, etc.
augroup vim-go-sameids
autocmd!
autocmd BufWinEnter <buffer> nested call go#guru#SameIds()
autocmd! * <buffer>
autocmd BufWinEnter <buffer> nested call go#guru#SameIds(0)
augroup end
endif
endfunction
@ -592,7 +517,7 @@ function! go#guru#ClearSameIds() abort
" remove the autocmds we defined
augroup vim-go-sameids
autocmd!
autocmd! * <buffer>
augroup end
return 0
@ -600,20 +525,20 @@ endfunction
function! go#guru#ToggleSameIds() abort
if go#guru#ClearSameIds() != 0
call go#guru#SameIds()
call go#guru#SameIds(1)
endif
endfunction
function! go#guru#AutoToogleSameIds() abort
if get(g:, "go_auto_sameids", 0)
function! go#guru#AutoToggleSameIds() abort
if go#config#AutoSameids()
call go#util#EchoProgress("sameids auto highlighting disabled")
call go#guru#ClearSameIds()
let g:go_auto_sameids = 0
call go#config#SetAutoSameids(0)
return
endif
call go#util#EchoSuccess("sameids auto highlighting enabled")
let g:go_auto_sameids = 1
call go#config#SetAutoSameids(1)
endfunction
@ -648,22 +573,165 @@ endfun
function! go#guru#Scope(...) abort
if a:0
let scope = a:000
if a:0 == 1 && a:1 == '""'
unlet g:go_guru_scope
let scope = []
endif
call go#config#SetGuruScope(scope)
if empty(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')
let scope = go#config#GuruScope()
if empty(scope)
call go#util#EchoError("guru scope is not set")
else
call go#util#EchoSuccess("current guru scope: ". join(g:go_guru_scope, ","))
call go#util#EchoSuccess("current guru scope: ". join(scope, ","))
endif
endfunction
function! go#guru#DescribeBalloon() abort
" don't even try if async isn't available.
if !go#util#has_job()
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("requires 'json_decode'. Update your Vim/Neovim version.")
return
endif
" change the active window to the window where the cursor is.
let l:winid = win_getid(winnr())
call win_gotoid(v:beval_winid)
let l:args = {
\ 'mode': 'describe',
\ 'format': 'json',
\ 'selected': -1,
\ 'needs_scope': 0,
\ 'custom_parse': function('s:describe_balloon'),
\ 'disable_progress': 1,
\ 'postype': 'balloon',
\ }
call s:async_guru(args)
" make the starting window active again
call win_gotoid(l:winid)
return ''
endfunction
function! s:describe_balloon(exit_val, output, mode)
if a:exit_val != 0
return
endif
if a:output[0] !=# '{'
return
endif
if empty(a:output) || type(a:output) != type("")
return
endif
let l:result = json_decode(a:output)
if type(l:result) != type({})
call go#util#EchoError(printf('malformed output from guru: %s', a:output))
return
endif
let l:info = []
if has_key(l:result, 'desc')
if l:result['desc'] != 'identifier'
let l:info = add(l:info, l:result['desc'])
endif
endif
if has_key(l:result, 'detail')
let l:detail = l:result['detail']
" guru gives different information based on the detail mode. Let try to
" extract the most useful information
if l:detail == 'value'
if !has_key(l:result, 'value')
call go#util#EchoError('value key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:val = l:result['value']
if !has_key(l:val, 'type')
call go#util#EchoError('type key is missing (value.type). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('type: %s', l:val['type']))
if has_key(l:val, 'value')
let l:info = add(l:info, printf('value: %s', l:val['value']))
endif
elseif l:detail == 'type'
if !has_key(l:result, 'type')
call go#util#EchoError('type key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:type = l:result['type']
if !has_key(l:type, 'type')
call go#util#EchoError('type key is missing (type.type). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('type: %s', l:type['type']))
if has_key(l:type, 'methods')
let l:info = add(l:info, 'methods:')
for l:m in l:type.methods
let l:info = add(l:info, printf("\t%s", l:m['name']))
endfor
endif
elseif l:detail == 'package'
if !has_key(l:result, 'package')
call go#util#EchoError('package key is missing. Please open a bug report on vim-go repo.')
return
endif
let l:package = result['package']
if !has_key(l:package, 'path')
call go#util#EchoError('path key is missing (package.path). Please open a bug report on vim-go repo.')
return
endif
let l:info = add(l:info, printf('package: %s', l:package["path"]))
elseif l:detail == 'unknown'
" the description is already included in l:info, and there's no other
" information on unknowns.
else
call go#util#EchoError(printf('unknown detail mode (%s) found. Please open a bug report on vim-go repo', l:detail))
return
endif
endif
if has('balloon_eval')
call balloon_show(join(l:info, "\n"))
return
endif
call balloon_show(l:info)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,23 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function Test_GuruScope_Set() abort
silent call go#guru#Scope("example.com/foo/bar")
let actual = go#config#GuruScope()
call assert_equal(["example.com/foo/bar"], actual)
silent call go#guru#Scope('""')
silent let actual = go#config#GuruScope()
call assert_equal([], actual, "setting scope to empty string should clear")
if exists('g:go_guru_scope')
unlet g:go_guru_scope
endif
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,105 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! Test_gomodVersion_highlight() abort
try
syntax on
let l:dir = gotest#write_file('gomodtest/go.mod', [
\ 'module github.com/fatih/vim-go',
\ '',
\ '\x1frequire (',
\ '\tversion/simple v1.0.0',
\ '\tversion/simple-pre-release v1.0.0-rc',
\ '\tversion/simple-pre-release v1.0.0+meta',
\ '\tversion/simple-pre-release v1.0.0-rc+meta',
\ '\tversion/pseudo/premajor v1.0.0-20060102150405-0123456789abcdef',
\ '\tversion/pseudo/prerelease v1.0.0-prerelease.0.20060102150405-0123456789abcdef',
\ '\tversion/pseudo/prepatch v1.0.1-0.20060102150405-0123456789abcdef',
\ '\tversion/simple/incompatible v2.0.0+incompatible',
\ '\tversion/pseudo/premajor/incompatible v2.0.0-20060102150405-0123456789abcdef+incompatible',
\ '\tversion/pseudo/prerelease/incompatible v2.0.0-prerelease.0.20060102150405-0123456789abcdef+incompatible',
\ '\tversion/pseudo/prepatch/incompatible v2.0.1-0.20060102150405-0123456789abcdef+incompatible',
\ ')'])
let l:lineno = 4
let l:lineclose = line('$')
while l:lineno < l:lineclose
let l:line = getline(l:lineno)
let l:col = col([l:lineno, '$']) - 1
let l:idx = len(l:line) - 1
let l:from = stridx(l:line, ' ') + 1
while l:idx >= l:from
call cursor(l:lineno, l:col)
let l:synname = synIDattr(synID(l:lineno, l:col, 1), 'name')
let l:errlen = len(v:errors)
call assert_equal('gomodVersion', l:synname, 'version on line ' . l:lineno)
" continue at the next line if there was an error at this column;
" there's no need to test each column once an error is detected.
if l:errlen < len(v:errors)
break
endif
let l:col -= 1
let l:idx -= 1
endwhile
let l:lineno += 1
endwhile
finally
call delete(l:dir, 'rf')
endtry
endfunc
function! Test_gomodVersion_incompatible_highlight() abort
try
syntax on
let l:dir = gotest#write_file('gomodtest/go.mod', [
\ 'module github.com/fatih/vim-go',
\ '',
\ '\x1frequire (',
\ '\tversion/invalid/premajor/incompatible v1.0.0-20060102150405-0123456789abcdef+incompatible',
\ '\tversion/invalid/prerelease/incompatible v1.0.0-prerelease.0.20060102150405-0123456789abcdef+incompatible',
\ '\tversion/invalid/prepatch/incompatible v1.0.1-0.20060102150405-0123456789abcdef+incompatible',
\ ')'])
let l:lineno = 4
let l:lineclose = line('$')
while l:lineno < l:lineclose
let l:line = getline(l:lineno)
let l:col = col([l:lineno, '$']) - 1
let l:idx = len(l:line) - 1
let l:from = stridx(l:line, '+')
while l:idx >= l:from
call cursor(l:lineno, l:col)
let l:synname = synIDattr(synID(l:lineno, l:col, 1), 'name')
let l:errlen = len(v:errors)
call assert_notequal('gomodVersion', l:synname, 'version on line ' . l:lineno)
" continue at the next line if there was an error at this column;
" there's no need to test each column once an error is detected.
if l:errlen < len(v:errors)
break
endif
let l:col -= 1
let l:idx -= 1
endwhile
let l:lineno += 1
endwhile
finally
call delete(l:dir, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,26 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#iferr#Generate()
let [l:out, l:err] = go#util#Exec(['iferr',
\ '-pos=' . go#util#OffsetCursor()], go#util#GetLines())
if len(l:out) == 1
return
endif
if getline('.') =~ '^\s*$'
silent delete _
silent normal! k
endif
let l:pos = getcurpos()
call append(l:pos[1], split(l:out, "\n"))
silent normal! j=2j
call setpos('.', l:pos)
silent normal! 4j
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#impl#Impl(...) abort
let recv = ""
let iface = ""
@ -101,13 +105,29 @@ function! s:root_dirs() abort
return dirs
endfunction
function! s:go_packages(dirs) abort
function! s:go_packages(dirs, arglead) 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"))
for dir in a:dirs
" this may expand to multiple lines
let scr_root = expand(dir . '/src/')
for pkg in split(globpath(scr_root, a:arglead.'*'), "\n")
if isdirectory(pkg)
let pkg .= '/'
elseif pkg !~ '\.a$'
continue
endif
" without this the result can have duplicates in form of
" 'encoding/json' and '/encoding/json/'
let pkg = go#util#StripPathSep(pkg)
" remove the scr root and keep the package in tact
let pkg = substitute(pkg, scr_root, "", "")
call add(pkgs, pkg)
endfor
endfor
return map(pkgs, "fnamemodify(v:val, ':t:r')")
return pkgs
endfunction
function! s:interface_list(pkg) abort
@ -124,17 +144,32 @@ 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
" if no words are given, just start completing the first package we found
return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead)))
elseif words[-1] =~# '^\(\h\w.*\.\%(\h\w*\)\=$\)\@!\S*$'
" start matching go packages. It's negate match of the below match
return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead)))
elseif words[-1] =~# '^\h\w.*\.\%(\h\w*\)\=$'
" match the following, anything that could indicate an interface candidate
"
" io.
" io.Wr
" github.com/fatih/color.
" github.com/fatih/color.U
" github.com/fatih/color.Un
let splitted = split(words[-1], '\.', 1)
let pkg = join(splitted[:-2], '.')
let interface = splitted[-1]
return s:uniq(sort(filter(s:interface_list(pkg), 'v:val =~? words[-1]')))
else
return []
endif
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_impl() abort
try
let l:tmp = gotest#write_file('a/a.go', [
@ -35,3 +39,9 @@ func! Test_impl_get() abort
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -4,6 +4,11 @@
"
" Check out the docs for more information at /doc/vim-go.txt
"
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#import#SwitchImport(enabled, localname, path, bang) abort
let view = winsaveview()
let path = substitute(a:path, '^\s*\(.\{-}\)\s*$', '\1', '')
@ -27,8 +32,8 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
endif
if a:bang == "!"
let out = go#util#System("go get -u -v ".shellescape(path))
if go#util#ShellError() != 0
let [l:out, l:err] = go#util#Exec(['go', 'get', '-u', '-v', path])
if err != 0
call s:Error("Can't find import: " . path . ":" . out)
endif
endif
@ -65,6 +70,9 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
let packageline = line
let appendline = line
elseif linestr =~# '^import\s\+(\+)'
let appendline = line
let appendstr = qlocalpath
elseif linestr =~# '^import\s\+('
let appendstr = qlocalpath
let indentstr = 1
@ -161,8 +169,16 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
let linesdelta += 3
let appendstr = qlocalpath
let indentstr = 1
call append(appendline, appendstr)
elseif getline(appendline) =~# '^import\s\+(\+)'
call setline(appendline, 'import (')
call append(appendline + 0, appendstr)
call append(appendline + 1, ')')
let linesdelta -= 1
let indentstr = 1
else
call append(appendline, appendstr)
endif
call append(appendline, appendstr)
execute appendline + 1
if indentstr
execute 'normal! >>'
@ -209,5 +225,8 @@ function! s:Error(s) abort
echohl Error | echo a:s | echohl None
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,72 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_indent_raw_string() abort
" The goRawString discovery requires that syntax be enabled.
syntax on
try
let l:dir= gotest#write_file('indent/indent.go', [
\ 'package main',
\ '',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ "\t\x1fconst msg = `",
\ '`',
\ '\tfmt.Println(msg)',
\ '}'])
silent execute "normal o" . "not indented\<Esc>"
let l:indent = indent(line('.'))
call assert_equal(0, l:indent)
finally
call delete(l:dir, 'rf')
endtry
try
let l:dir= gotest#write_file('indent/indent.go', [
\ 'package main',
\ '',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ "\t\x1fmsg := `",
\ '`',
\ '\tfmt.Println(msg)',
\ '}'])
silent execute "normal o" . "not indented\<Esc>"
let l:indent = indent(line('.'))
call assert_equal(0, l:indent)
finally
call delete(l:dir, 'rf')
endtry
try
let l:dir= gotest#write_file('indent/indent.go', [
\ 'package main',
\ '',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ "\tconst msg = `",
\ "\t\x1findented",
\ '`',
\ '\tfmt.Println(msg)',
\ '}'])
silent execute "normal o" . "indented\<Esc>"
let l:indent = indent(line('.'))
call assert_equal(shiftwidth(), l:indent)
finally
call delete(l:dir, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,42 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:templatepath = go#util#Join(expand('<sfile>:p:h:h:h'), '.github', 'ISSUE_TEMPLATE.md')
function! go#issue#New() abort
let body = go#uri#Encode(s:issuebody())
let url = "https://github.com/fatih/vim-go/issues/new?body=" . l:body
call go#util#OpenBrowser(l:url)
endfunction
function! s:issuebody() abort
let lines = readfile(s:templatepath)
let rtrimpat = '[[:space:]]\+$'
let body = []
for l in lines
let body = add(body, l)
if l =~ '^\* Vim version'
redir => out
silent version
redir END
let body = extend(body, split(out, "\n")[0:2])
elseif l =~ '^\* Go version'
let [out, err] = go#util#Exec(['go', 'version'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
elseif l =~ '^\* Go environment'
let [out, err] = go#util#Exec(['go', 'env'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
endif
endfor
return join(body, "\n")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,24 +1,46 @@
" Spawn returns callbacks to be used with job_start. It is abstracted to be
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" Spawn starts an asynchronous job. See the description of go#job#Options to
" understand the args parameter.
"
" Spawn returns a job.
function! go#job#Spawn(cmd, args)
let l:options = go#job#Options(a:args)
return go#job#Start(a:cmd, l:options)
endfunction
" Options returns callbacks to be used with job_start. It is abstracted to be
" used with various go commands, such as build, test, install, etc.. This
" allows us to avoid writing the same callback over and over for some
" commands. It's fully customizable so each command can change it to it's own
" commands. It's fully customizable so each command can change it to its own
" logic.
"
" args is a dictionary with the these keys:
" 'cmd':
" The value to pass to job_start().
" 'bang':
" Set to 0 to jump to the first error in the error list.
" Defaults to 0.
" 'statustype':
" The status type to use when updating the status.
" See statusline.vim.
" 'for':
" The g:go_list_type_command key to use to get the error list type to use.
" Errors will not be handled when the value is '_'.
" Defaults to '_job'
" 'errorformat':
" The errorformat string to use when parsing errors. Defaults to
" &errorformat.
" See :help 'errorformat'.
" 'complete':
" A function to call after the job exits and the channel is closed. The
" function will be passed three arguments: the job, its exit code, and the
" list of messages received from the channel. The default value will
" process the messages and manage the error list after the job exits and
" the channel is closed.
" list of messages received from the channel. The default is a no-op. A
" custom value can modify the messages before they are processed by the
" returned exit_cb and close_cb callbacks. When the function is called,
" the current window will be the window that was hosting the buffer when
" the job was started. After it returns, the current window will be
" restored to what it was before the function was called.
" The return value is a dictionary with these keys:
" 'callback':
@ -30,22 +52,30 @@
" 'close_cb':
" A function suitable to be passed as a job close_cb handler. See
" job-close_cb.
function go#job#Spawn(args)
" 'cwd':
" The path to the directory which contains the current buffer. The
" callbacks are configured to expect this directory is the working
" directory for the job; it should not be modified by callers.
function! go#job#Options(args)
let cbs = {}
let state = {
\ 'winnr': winnr(),
\ 'winid': win_getid(winnr()),
\ 'dir': getcwd(),
\ 'jobdir': fnameescape(expand("%:p:h")),
\ 'messages': [],
\ 'args': a:args.cmd,
\ 'bang': 0,
\ 'for': "_job",
\ 'exited': 0,
\ 'exit_status': 0,
\ 'closed': 0,
\ 'errorformat': &errorformat
\ 'errorformat': &errorformat,
\ 'statustype' : ''
\ }
if has("patch-8.0.0902") || has('nvim')
let cbs.cwd = state.jobdir
endif
if has_key(a:args, 'bang')
let state.bang = a:args.bang
endif
@ -54,90 +84,139 @@ function go#job#Spawn(args)
let state.for = a:args.for
endif
" do nothing in state.complete by default.
if has_key(a:args, 'statustype')
let state.statustype = a:args.statustype
endif
if has_key(a:args, 'errorformat')
let state.errorformat = a:args.errorformat
endif
function state.complete(job, exit_status, data)
if has_key(self, 'custom_complete')
let l:winid = win_getid(winnr())
" Always set the active window to the window that was active when the job
" was started. Among other things, this makes sure that the correct
" window's location list will be populated when the list type is
" 'location' and the user has moved windows since starting the job.
call win_gotoid(self.winid)
call self.custom_complete(a:job, a:exit_status, a:data)
call win_gotoid(l:winid)
endif
call self.show_errors(a:job, a:exit_status, a:data)
endfunction
function state.show_status(job, exit_status) dict
if self.statustype == ''
return
endif
if go#config#EchoCommandInfo()
let prefix = '[' . self.statustype . '] '
if a:exit_status == 0
call go#util#EchoSuccess(prefix . "SUCCESS")
else
call go#util#EchoError(prefix . "FAIL")
endif
endif
let status = {
\ 'desc': 'last status',
\ 'type': self.statustype,
\ 'state': "success",
\ }
if a:exit_status
let status.state = "failed"
endif
if has_key(self, 'started_at')
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)
endif
call go#statusline#Update(self.jobdir, status)
endfunction
if has_key(a:args, 'complete')
let state.complete = a:args.complete
let state.custom_complete = a:args.complete
endif
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
" explicitly bind _start to state so that within it, self will
" always refer to state. See :help Partial for more information.
"
" _start is intended only for internal use and should not be referenced
" outside of this file.
let cbs._start = function('s:start', [''], state)
" explicitly bind callback to state so that within it, self will
" always refer to state. See :help Partial for more information.
let cbs.callback = function('s:callback', [], state)
function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
if get(g:, 'go_echo_command_info', 1)
if a:exitval == 0
call go#util#EchoSuccess("SUCCESS")
else
call go#util#EchoError("FAILED")
endif
endif
if self.closed
call self.complete(a:job, self.exit_status, self.messages)
call self.show_errors(a:job, self.exit_status, self.messages)
endif
endfunction
" explicitly bind exit_cb to state so that within it, self will always refer
" to state. See :help Partial for more information.
let cbs.exit_cb = function('s:exit_cb', [], state)
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
let job = ch_getjob(a:ch)
call self.complete(job, self.exit_status, self.messages)
call self.show_errors(job, self.exit_status, self.messages)
endif
endfunction
" explicitly bind close_cb to state so that within it, self will
" always refer to state. See :help Partial for more information.
let cbs.close_cb = function('s:close_cb', [], state)
function state.show_errors(job, exit_status, data)
if self.for == '_'
return
endif
let l:winid = win_getid(winnr())
" Always set the active window to the window that was active when the job
" was started. Among other things, this makes sure that the correct
" window's location list will be populated when the list type is
" 'location' and the user has moved windows since starting the job.
call win_gotoid(self.winid)
let l:listtype = go#list#Type(self.for)
if a:exit_status == 0
call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
return
endif
let l:listtype = go#list#Type(self.for)
if len(a:data) == 0
call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
return
endif
let out = join(self.messages, "\n")
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
try
" parse the errors relative to self.jobdir
execute cd self.jobdir
execute l:cd self.jobdir
call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for)
let errors = go#list#Get(l:listtype)
finally
execute cd . fnameescape(self.dir)
execute l:cd fnameescape(self.dir)
endtry
if empty(errors)
" failed to parse errors, output the original content
call go#util#EchoError(self.messages + [self.dir])
call go#util#EchoError([self.dir] + self.messages)
call win_gotoid(l:winid)
return
endif
if self.winnr == winnr()
" only open the error window if user was still in the window from which
" the job was started.
if self.winid == l:winid
call go#list#Window(l:listtype, len(errors))
if !self.bang
if self.bang
call win_gotoid(l:winid)
else
call go#list#JumpToFirst(l:listtype)
endif
endif
@ -146,4 +225,311 @@ function go#job#Spawn(args)
return cbs
endfunction
function! s:start(args) dict
if go#config#EchoCommandInfo() && self.statustype != ""
let prefix = '[' . self.statustype . '] '
call go#util#EchoSuccess(prefix . "dispatched")
endif
if self.statustype != ''
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endif
let self.started_at = reltime()
endfunction
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exit_status = a:exitval
let self.exited = 1
call self.show_status(a:job, a:exitval)
if self.closed || has('nvim')
call self.complete(a:job, self.exit_status, self.messages)
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
let job = ch_getjob(a:ch)
call self.complete(job, self.exit_status, self.messages)
endif
endfunction
" go#job#Start runs a job. The options are expected to be the options
" suitable for Vim8 jobs. When called from Neovim, Vim8 options will be
" transformed to their Neovim equivalents.
function! go#job#Start(cmd, options)
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let l:options = copy(a:options)
if has('nvim')
let l:options = s:neooptions(l:options)
endif
" Verify that the working directory for the job actually exists. Return
" early if the directory does not exist. This helps avoid errors when
" working with plugins that use virtual files that don't actually exist on
" the file system.
let l:filedir = expand("%:p:h")
if has_key(l:options, 'cwd') && !isdirectory(l:options.cwd)
return
elseif !isdirectory(l:filedir)
return
endif
let l:manualcd = 0
if !has_key(l:options, 'cwd')
" pre start
let l:manualcd = 1
let dir = getcwd()
execute l:cd fnameescape(filedir)
elseif !(has("patch-8.0.0902") || has('nvim'))
let l:manualcd = 1
let l:dir = l:options.cwd
execute l:cd fnameescape(l:dir)
call remove(l:options, 'cwd')
endif
if has_key(l:options, '_start')
call l:options._start()
" remove _start to play nicely with vim (when vim encounters an unexpected
" job option it reports an "E475: invalid argument" error).
unlet l:options._start
endif
" noblock was added in 8.1.350; remove it if it's not supported.
if has_key(l:options, 'noblock') && (has('nvim') || !has("patch-8.1.350"))
call remove(l:options, 'noblock')
endif
if go#util#HasDebug('shell-commands')
call go#util#EchoInfo('job command: ' . string(a:cmd))
endif
if has('nvim')
let l:input = []
if has_key(a:options, 'in_io') && a:options.in_io ==# 'file' && !empty(a:options.in_name)
let l:input = readfile(a:options.in_name, "b")
endif
let job = jobstart(a:cmd, l:options)
if len(l:input) > 0
call chansend(job, l:input)
" close stdin to signal that no more bytes will be sent.
call chanclose(job, 'stdin')
endif
else
let l:cmd = a:cmd
if go#util#IsWin()
let l:cmd = join(map(copy(a:cmd), function('s:winjobarg')), " ")
endif
let job = job_start(l:cmd, l:options)
endif
if l:manualcd
" post start
execute l:cd fnameescape(l:dir)
endif
return job
endfunction
" s:neooptions returns a dictionary of job options suitable for use by Neovim
" based on a dictionary of job options suitable for Vim8.
function! s:neooptions(options)
let l:options = {}
let l:options['stdout_buf'] = ''
let l:options['stderr_buf'] = ''
let l:err_mode = get(a:options, 'err_mode', get(a:options, 'mode', ''))
let l:out_mode = get(a:options, 'out_mode', get(a:options, 'mode', ''))
for key in keys(a:options)
if key == 'cwd'
let l:options['cwd'] = a:options['cwd']
continue
endif
if key == 'callback'
let l:options['callback'] = a:options['callback']
if !has_key(a:options, 'out_cb')
let l:options['on_stdout'] = function('s:callback2on_stdout', [l:out_mode], l:options)
endif
if !has_key(a:options, 'err_cb')
let l:options['on_stderr'] = function('s:callback2on_stderr', [l:err_mode], l:options)
endif
continue
endif
if key == 'out_cb'
let l:options['out_cb'] = a:options['out_cb']
let l:options['on_stdout'] = function('s:on_stdout', [l:out_mode], l:options)
continue
endif
if key == 'err_cb'
let l:options['err_cb'] = a:options['err_cb']
let l:options['on_stderr'] = function('s:on_stderr', [l:err_mode], l:options)
continue
endif
if key == 'exit_cb'
let l:options['exit_cb'] = a:options['exit_cb']
let l:options['on_exit'] = function('s:on_exit', [], l:options)
continue
endif
if key == 'close_cb'
continue
endif
if key == 'stoponexit'
if a:options['stoponexit'] == ''
let l:options['detach'] = 1
endif
continue
endif
endfor
return l:options
endfunction
function! s:callback2on_stdout(mode, ch, data, event) dict
let self.stdout_buf = s:neocb(a:mode, a:ch, self.stdout_buf, a:data, self.callback)
endfunction
function! s:callback2on_stderr(mode, ch, data, event) dict
let self.stderr_buf = s:neocb(a:mode, a:ch, self.stderr_buf, a:data, self.callback)
endfunction
function! s:on_stdout(mode, ch, data, event) dict
let self.stdout_buf = s:neocb(a:mode, a:ch, self.stdout_buf, a:data, self.out_cb)
endfunction
function! s:on_stderr(mode, ch, data, event) dict
let self.stderr_buf = s:neocb(a:mode, a:ch, self.stderr_buf, a:data, self.err_cb )
endfunction
function! s:on_exit(jobid, exitval, event) dict
call self.exit_cb(a:jobid, a:exitval)
endfunction
function! go#job#Stop(job) abort
if has('nvim')
call jobstop(a:job)
return
endif
call job_stop(a:job)
call go#job#Wait(a:job)
return
endfunction
function! go#job#Wait(job) abort
if has('nvim')
call jobwait([a:job])
return
endif
while job_status(a:job) is# 'run'
sleep 50m
endwhile
endfunction
function! s:winjobarg(idx, val) abort
if empty(a:val)
return '""'
endif
return a:val
endfunction
function! s:neocb(mode, ch, buf, data, callback)
" dealing with the channel lines of Neovim is awful. The docs (:help
" channel-lines) say:
" stream event handlers may receive partial (incomplete) lines. For a
" given invocation of on_stdout etc, `a:data` is not guaranteed to end
" with a newline.
" - `abcdefg` may arrive as `['abc']`, `['defg']`.
" - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`,
" `['','efg']`, or even `['ab']`, `['c','efg']`.
"
" Thankfully, though, this is explained a bit better in an issue:
" https://github.com/neovim/neovim/issues/3555. Specifically in these two
" comments:
" * https://github.com/neovim/neovim/issues/3555#issuecomment-152290804
" * https://github.com/neovim/neovim/issues/3555#issuecomment-152588749
"
" The key is
" Every item in the list passed to job control callbacks represents a
" string after a newline(Except the first, of course). If the program
" outputs: "hello\nworld" the corresponding list is ["hello", "world"].
" If the program outputs "hello\nworld\n", the corresponding list is
" ["hello", "world", ""]. In other words, you can always determine if
" the last line received is complete or not.
" and
" for every list you receive in a callback, all items except the first
" represent newlines.
let l:buf = ''
" A single empty string means EOF was reached. The first item will never be
" an empty string except for when it's the only item and is signaling that
" EOF was reached.
if len(a:data) == 1 && a:data[0] == ''
" when there's nothing buffered, return early so that an
" erroneous message will not be added.
if a:buf == ''
return ''
endif
let l:data = [a:buf]
else
let l:data = copy(a:data)
let l:data[0] = a:buf . l:data[0]
" The last element may be a partial line; save it for next time.
if a:mode != 'raw'
let l:buf = l:data[-1]
let l:data = l:data[:-2]
endif
endif
let l:i = 0
let l:last = len(l:data) - 1
while l:i <= l:last
let l:msg = l:data[l:i]
if a:mode == 'raw' && l:i < l:last
let l:msg = l:msg . "\n"
endif
call a:callback(a:ch, l:msg)
let l:i += 1
endwhile
return l:buf
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,190 +0,0 @@
" 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, for, args) abort
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let job = s:spawn(a:bang, a:desc, a:for, 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. The job is started
" inside the current files folder.
function! s:spawn(bang, desc, for, 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(),
\ '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,
\ 'for' : a:for,
\ 'errorformat': &errorformat,
\ }
" 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)
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(self.for)
if a:exit_status == 0
call go#list#Clean(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
" parse the errors relative to self.jobdir
call go#list#ParseFormat(l:listtype, self.errorformat, std_combined, self.for)
let errors = go#list#Get(l:listtype)
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#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,20 +1,24 @@
function! go#keyify#Keyify()
let bin_path = go#path#CheckBinPath("keyify")
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
if empty(bin_path) || !exists('*json_decode')
function! go#keyify#Keyify()
" Needs: https://github.com/dominikh/go-tools/pull/272
"\ '-tags', go#config#BuildTags(),
let l:cmd = ['keyify',
\ '-json',
\ printf('%s:#%s', fnamemodify(expand('%'), ':p:gs?\\?/?'), go#util#OffsetCursor())]
let [l:out, l:err] = go#util#Exec(l:cmd)
if l:err
call go#util#EchoError("non-zero exit code: " . l:out)
return
endif
" Get result of command as json, that contains `start`, `end` and `replacement`
let command = printf("%s -json %s:#%s", go#util#Shellescape(bin_path),
\ go#util#Shellescape(fname), go#util#OffsetCursor())
let output = go#util#System(command)
silent! let result = json_decode(output)
silent! let result = json_decode(l:out)
" We want to output the error message in case the result isn't a JSON
if type(result) != type({})
call go#util#EchoError(s:chomp(output))
call go#util#EchoError(s:chomp(l:out))
return
endif
@ -53,4 +57,8 @@ function! s:chomp(string)
return substitute(a:string, '\n\+$', '', '')
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,62 +1,34 @@
if !exists("g:go_metalinter_command")
let g:go_metalinter_command = ""
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
if !exists("g:go_metalinter_autosave_enabled")
let g:go_metalinter_autosave_enabled = ['vet', 'golint']
endif
if !exists("g:go_metalinter_enabled")
let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck']
endif
if !exists("g:go_metalinter_disabled")
let g:go_metalinter_disabled = []
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
function! go#lint#Gometa(bang, autosave, ...) abort
if a:0 == 0
let goargs = [expand('%:p:h')]
else
let goargs = a:000
endif
let bin_path = go#path#CheckBinPath("gometalinter")
if empty(bin_path)
return
endif
let l:metalinter = go#config#MetalinterCommand()
let cmd = [bin_path]
let cmd += ["--disable-all"]
if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint'
let cmd = s:metalintercmd(l:metalinter)
if empty(cmd)
return
endif
if a:autosave || empty(g:go_metalinter_command)
" linters
let linters = a:autosave ? g:go_metalinter_autosave_enabled : g:go_metalinter_enabled
let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled()
for linter in linters
let cmd += ["--enable=".linter]
endfor
for linter in g:go_metalinter_disabled
for linter in go#config#MetalinterDisabled()
let cmd += ["--disable=".linter]
endfor
" gometalinter has a --tests flag to tell its linters whether to run
" against tests. While not all of its linters respect this flag, for those
" that do, it means if we don't pass --tests, the linter won't run against
" test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck.
let cmd += ["--tests"]
else
" the user wants something else, let us use it.
let cmd += split(g:go_metalinter_command, " ")
let cmd = split(go#config#MetalinterCommand(), " ")
endif
if a:autosave
@ -64,37 +36,44 @@ function! go#lint#Gometa(autosave, ...) abort
" will be cleared
redraw
" Include only messages for the active buffer for autosave.
let cmd += [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
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#util#System), 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]
if l:metalinter == "gometalinter"
" Include only messages for the active buffer for autosave.
let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
if go#util#has_job()
let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
endif
let cmd += include
elseif l:metalinter == "golangci-lint"
let goargs[0] = expand('%:p')
endif
let cmd += goargs
call s:lint_job({'cmd': cmd}, a:autosave)
return
endif
" We're calling gometalinter synchronously.
let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")]
" Call metalinter asynchronously.
let deadline = go#config#MetalinterDeadline()
if deadline != ''
let cmd += ["--deadline=" . deadline]
endif
let cmd += goargs
if l:metalinter == "gometalinter"
" Gometalinter can output one of the two, so we look for both:
" <file>:<line>:<column>:<severity>: <message> (<linter>)
" <file>:<line>::<severity>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
else
" Golangci-lint can output the following:
" <file>:<line>:<column>: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:\ %m"
endif
if go#util#has_job()
call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave)
return
endif
let [l:out, l:err] = go#util#Exec(cmd)
if a:autosave
@ -107,48 +86,46 @@ function! go#lint#Gometa(autosave, ...) abort
call go#list#Clean(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"
let l:winid = win_getid(winnr())
" 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)
if a:autosave || a:bang
call win_gotoid(l:winid)
return
endif
call go#list#JumpToFirst(l:listtype)
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
let bin_path = go#util#Shellescape(bin_path)
function! go#lint#Golint(bang, ...) abort
if a:0 == 0
let out = go#util#System(bin_path . " " . go#util#Shellescape(go#package#ImportPath()))
let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), go#package#ImportPath()])
else
let out = go#util#System(bin_path . " " . go#util#Shelljoin(a:000))
let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000)
endif
if empty(out)
echon "vim-go: " | echohl Function | echon "[lint] PASS" | echohl None
if empty(l:out)
call go#util#EchoSuccess('[lint] PASS')
return
endif
let l:winid = win_getid(winnr())
let l:listtype = go#list#Type("GoLint")
call go#list#Parse(l:listtype, out, "GoLint")
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
call go#list#Parse(l:listtype, l:out, "GoLint")
let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(l:errors))
if a:bang
call win_gotoid(l:winid)
return
endif
call go#list#JumpToFirst(l:listtype)
endfunction
@ -156,202 +133,152 @@ endfunction
" the location list
function! go#lint#Vet(bang, ...) abort
call go#cmd#autowrite()
echon "vim-go: " | echohl Identifier | echon "calling vet..." | echohl None
if go#config#EchoCommandInfo()
call go#util#EchoProgress('calling vet...')
endif
if a:0 == 0
let out = go#util#System('go vet ' . go#util#Shellescape(go#package#ImportPath()))
let [l:out, l:err] = go#util#Exec(['go', 'vet', go#package#ImportPath()])
else
let out = go#util#System('go tool vet ' . go#util#Shelljoin(a:000))
let [l:out, l:err] = go#util#Exec(['go', 'tool', 'vet'] + a:000)
endif
let l:listtype = go#list#Type("GoVet")
if go#util#ShellError() != 0
let errorformat="%-Gexit status %\\d%\\+," . &errorformat
if l:err != 0
let l:winid = win_getid(winnr())
let errorformat = "%-Gexit status %\\d%\\+," . &errorformat
call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet")
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 win_gotoid(l:winid)
endif
echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None
else
call go#list#Clean(l:listtype)
redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None
call go#util#EchoSuccess('[vet] PASS')
endif
endfunction
" ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in
" the location list
function! go#lint#Errcheck(...) abort
function! go#lint#Errcheck(bang, ...) abort
if a:0 == 0
let import_path = go#package#ImportPath()
let l:import_path = go#package#ImportPath()
if import_path == -1
echohl Error | echomsg "vim-go: package is not inside GOPATH src" | echohl None
call go#util#EchoError('package is not inside GOPATH src')
return
endif
else
let import_path = go#util#Shelljoin(a:000)
let l:import_path = join(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
call go#util#EchoProgress('[errcheck] analysing ...')
redraw
let command = go#util#Shellescape(bin_path) . ' -abspath ' . import_path
let out = go#tool#ExecuteInDir(command)
let [l:out, l:err] = go#util#Exec([go#config#ErrcheckBin(), '-abspath', l:import_path])
let l:listtype = go#list#Type("GoErrCheck")
if go#util#ShellError() != 0
if l:err != 0
let l:winid = win_getid(winnr())
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
let l:errors = go#list#Get(l:listtype)
if empty(l:errors)
call go#util#EchoError(l:out)
return
endif
if !empty(errors)
echohl Error | echomsg "GoErrCheck found errors" | echohl None
call go#list#Populate(l:listtype, errors, 'Errcheck')
call go#list#Window(l:listtype, len(errors))
if !empty(errors)
if !a:bang
call go#list#JumpToFirst(l:listtype)
else
call win_gotoid(l:winid)
endif
endif
else
call go#list#Clean(l:listtype)
echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None
call go#util#EchoSuccess('[errcheck] PASS')
endif
endfunction
function! go#lint#ToggleMetaLinterAutoSave() abort
if get(g:, "go_metalinter_autosave", 0)
let g:go_metalinter_autosave = 0
if go#config#MetalinterAutosave()
call go#config#SetMetalinterAutosave(0)
call go#util#EchoProgress("auto metalinter disabled")
return
end
let g:go_metalinter_autosave = 1
call go#config#SetMetalinterAutosave(1)
call go#util#EchoProgress("auto metalinter enabled")
endfunction
function! s:lint_job(args, autosave)
let state = {
\ 'status_dir': expand('%:p:h'),
\ 'started_at': reltime(),
\ 'messages': [],
\ 'exited': 0,
\ 'closed': 0,
\ 'exit_status': 0,
\ 'winnr': winnr(),
\ 'autosave': a:autosave
\ }
function! s:lint_job(args, bang, autosave)
let l:opts = {
\ 'statustype': a:args.statustype,
\ 'errorformat': a:args.errformat,
\ 'for': "GoMetaLinter",
\ 'bang': a:bang,
\ }
call go#statusline#Update(state.status_dir, {
\ 'desc': "current status",
\ 'type': "gometalinter",
\ 'state': "analysing",
\})
if a:autosave
let l:opts.for = "GoMetaLinterAutoSave"
endif
" autowrite is not enabled for jobs
call go#cmd#autowrite()
if a:autosave
let state.listtype = go#list#Type("GoMetaLinterAutoSave")
else
let state.listtype = go#list#Type("GoMetaLinter")
endif
function! s:callback(chan, msg) dict closure
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exit_status = a:exitval
let status = {
\ 'desc': 'last status',
\ 'type': "gometaliner",
\ 'state': "finished",
\ }
if a:exitval
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)
if self.closed
call self.show_errors()
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call self.show_errors()
endif
endfunction
function state.show_errors()
let l:winnr = winnr()
" make sure the current window is the window from which gometalinter was
" run when the listtype is locationlist so that the location list for the
" correct window will be populated.
if self.listtype == 'locationlist'
exe self.winnr . "wincmd w"
endif
let l:errorformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m'
call go#list#ParseFormat(self.listtype, l:errorformat, self.messages, 'GoMetaLinter')
let errors = go#list#Get(self.listtype)
call go#list#Window(self.listtype, len(errors))
" move to the window that was active before processing the errors, because
" the user may have moved around within the window or even moved to a
" different window since saving. Moving back to current window as of the
" start of this function avoids the perception that the quickfix window
" steals focus when linting takes a while.
if self.autosave
exe l:winnr . "wincmd w"
endif
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoSuccess("linting finished")
endif
endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = {
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state),
\ }
call job_start(a:args.cmd, start_options)
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("linting started ...")
endif
call go#job#Spawn(a:args.cmd, l:opts)
endfunction
function! s:metalintercmd(metalinter)
let l:cmd = []
let bin_path = go#path#CheckBinPath(a:metalinter)
if !empty(bin_path)
if a:metalinter == "gometalinter"
let l:cmd = s:gometalintercmd(bin_path)
elseif a:metalinter == "golangci-lint"
let l:cmd = s:golangcilintcmd(bin_path)
endif
endif
return cmd
endfunction
function! s:gometalintercmd(bin_path)
let cmd = [a:bin_path]
let cmd += ["--disable-all"]
" gometalinter has a --tests flag to tell its linters whether to run
" against tests. While not all of its linters respect this flag, for those
" that do, it means if we don't pass --tests, the linter won't run against
" test files. One example of a linter that will not run against tests if
" we do not specify this flag is errcheck.
let cmd += ["--tests"]
return cmd
endfunction
function! s:golangcilintcmd(bin_path)
let cmd = [a:bin_path]
let cmd += ["run"]
let cmd += ["--print-issued-lines=false"]
let cmd += ["--disable-all"]
" do not use the default exclude patterns, because doing so causes golint
" problems about missing doc strings to be ignored and other things that
" golint identifies.
let cmd += ["--exclude-use-default=false"]
return cmd
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,114 +1,131 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_Gometa() abort
call s:gometa('gometaliner')
endfunc
func! Test_GometaGolangciLint() abort
call s:gometa('golangci-lint')
endfunc
func! s:gometa(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
" clear the quickfix lists
call setqflist([], 'r')
" clear the quickfix lists
call setqflist([], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will
" be autoloaded and the default for g:go_metalinter_enabled will be set so
" we can capture it to restore it after the test is run.
call go#lint#ToggleMetaLinterAutoSave()
" And restore it back to its previous value
call go#lint#ToggleMetaLinterAutoSave()
let g:go_metalinter_enabled = ['golint']
let orig_go_metalinter_enabled = g:go_metalinter_enabled
let g:go_metalinter_enabled = ['golint']
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
call go#lint#Gometa(0, $GOPATH . '/src/foo')
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_enabled = orig_go_metalinter_enabled
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_enabled
endtry
endfunc
func! Test_GometaWithDisabled() abort
call s:gometawithdisabled('gometalinter')
endfunc
func! Test_GometaWithDisabledGolangciLint() abort
call s:gometawithdisabled('golangci-lint')
endfunc
func! s:gometawithdisabled(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'}
\ ]
" clear the quickfix lists
call setqflist([], 'r')
" clear the quickfix lists
call setqflist([], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will
" be autoloaded and the default for g:go_metalinter_disabled will be set so
" we can capture it to restore it after the test is run.
call go#lint#ToggleMetaLinterAutoSave()
" And restore it back to its previous value
call go#lint#ToggleMetaLinterAutoSave()
let g:go_metalinter_disabled = ['vet']
let orig_go_metalinter_disabled = g:go_metalinter_disabled
let g:go_metalinter_disabled = ['vet']
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
call go#lint#Gometa(0, $GOPATH . '/src/foo')
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_disabled = orig_go_metalinter_disabled
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_disabled
endtry
endfunc
func! Test_GometaAutoSave() abort
call s:gometaautosave('gometalinter')
endfunc
func! Test_GometaAutoSaveGolangciLint() abort
call s:gometaautosave('golangci-lint')
endfunc
func! s:gometaautosave(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ ]
try
let g:go_metalinter_comand = a:metalinter
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'}
\ ]
let winnr = winnr()
let winnr = winnr()
" clear the location lists
call setloclist(l:winnr, [], 'r')
" clear the location lists
call setloclist(l:winnr, [], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will
" be autoloaded and the default for g:go_metalinter_autosave_enabled will be
" set so we can capture it to restore it after the test is run.
call go#lint#ToggleMetaLinterAutoSave()
" And restore it back to its previous value
call go#lint#ToggleMetaLinterAutoSave()
let g:go_metalinter_autosave_enabled = ['golint']
let orig_go_metalinter_autosave_enabled = g:go_metalinter_autosave_enabled
let g:go_metalinter_autosave_enabled = ['golint']
call go#lint#Gometa(0, 1)
call go#lint#Gometa(1)
let actual = getloclist(l:winnr)
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(l:winnr)
endwhile
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(l:winnr)
endwhile
call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_autosave_enabled = orig_go_metalinter_autosave_enabled
call gotest#assert_quickfix(actual, expected)
finally
unlet g:go_metalinter_autosave_enabled
endtry
endfunc
func! Test_Vet()
func! Test_Vet() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
compiler go
let expected = [
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'arg str for printf verb %d of wrong type: string'}
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '',
\ 'text': 'Printf format %d has arg str of wrong type string'}
\ ]
let winnr = winnr()
@ -128,4 +145,8 @@ func! Test_Vet()
call gotest#assert_quickfix(actual, expected)
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,10 +1,6 @@
if !exists("g:go_list_type")
let g:go_list_type = ""
endif
if !exists("g:go_list_type_commands")
let g:go_list_type_commands = {}
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" Window opens the list with the given height up to 10 lines maximum.
" Otherwise g:go_loclist_height is used.
@ -22,7 +18,7 @@ function! go#list#Window(listtype, ...) abort
return
endif
let height = get(g:, "go_list_height", 0)
let height = go#config#ListHeight()
if height == 0
" prevent creating a large location height for a large set of numbers
if a:1 > 10
@ -113,7 +109,7 @@ endfunction
" Close closes the location list
function! go#list#Close(listtype) abort
let autoclose_window = get(g:, 'go_list_autoclose', 1)
let autoclose_window = go#config#ListAutoclose()
if !autoclose_window
return
endif
@ -126,13 +122,12 @@ function! go#list#Close(listtype) abort
endfunction
function! s:listtype(listtype) abort
if g:go_list_type == "locationlist"
return "locationlist"
elseif g:go_list_type == "quickfix"
return "quickfix"
let listtype = go#config#ListType()
if empty(listtype)
return a:listtype
endif
return a:listtype
return listtype
endfunction
" s:default_list_type_commands is the defaults that will be used for each of
@ -143,6 +138,7 @@ endfunction
" in g:go_list_type_commands.
let s:default_list_type_commands = {
\ "GoBuild": "quickfix",
\ "GoDebug": "quickfix",
\ "GoErrCheck": "quickfix",
\ "GoFmt": "locationlist",
\ "GoGenerate": "quickfix",
@ -150,6 +146,7 @@ let s:default_list_type_commands = {
\ "GoLint": "quickfix",
\ "GoMetaLinter": "quickfix",
\ "GoMetaLinterAutoSave": "locationlist",
\ "GoModFmt": "locationlist",
\ "GoModifyTags": "locationlist",
\ "GoRename": "quickfix",
\ "GoRun": "quickfix",
@ -169,7 +166,11 @@ function! go#list#Type(for) abort
let l:listtype = "quickfix"
endif
return get(g:go_list_type_commands, a:for, l:listtype)
return get(go#config#ListTypeCommands(), a:for, l:listtype)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,533 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
let s:lspfactory = {}
function! s:lspfactory.get() dict abort
if !has_key(self, 'current')
" TODO(bc): check that the lsp is still running.
let self.current = s:newlsp()
endif
return self.current
endfunction
function! s:lspfactory.reset() dict abort
if has_key(self, 'current')
call remove(self, 'current')
endif
endfunction
function! s:newlsp() abort
if !go#util#has_job()
" TODO(bc): start the server in the background using a shell that waits for the right output before returning.
call go#util#EchoError('This feature requires either Vim 8.0.0087 or newer with +job or Neovim.')
return
endif
" job is the job used to talk to the backing instance of gopls.
" ready is 0 until the initialize response has been received. 1 afterwards.
" queue is messages to send after initialization
" last_request_id is id of the most recently sent request.
" buf is unprocessed/incomplete responses
" handlers is a mapping of request ids to dictionaries of functions.
" request id -> {start, requestComplete, handleResult, error}
" * start is a function that takes no arguments
" * requestComplete is a function that takes 1 argument. The parameter will be 1
" if the call was succesful.
" * handleResult takes a single argument, the result message received from gopls
" * error takes a single argument, the error message received from gopls.
" The error method is optional.
let l:lsp = {
\ 'job': '',
\ 'ready': 0,
\ 'queue': [],
\ 'last_request_id': 0,
\ 'buf': '',
\ 'handlers': {},
\ }
function! l:lsp.readMessage(data) dict abort
let l:responses = []
let l:rest = a:data
while 1
" Look for the end of the HTTP headers
let l:body_start_idx = matchend(l:rest, "\r\n\r\n")
if l:body_start_idx < 0
" incomplete header
break
endif
" Parse the Content-Length header.
let l:header = l:rest[:l:body_start_idx - 4]
let l:length_match = matchlist(
\ l:header,
\ '\vContent-Length: *(\d+)'
\)
if empty(l:length_match)
" TODO(bc): shutdown gopls?
throw "invalid JSON-RPC header:\n" . l:header
endif
" get the start of the rest
let l:rest_start_idx = l:body_start_idx + str2nr(l:length_match[1])
if len(l:rest) < l:rest_start_idx
" incomplete response body
break
endif
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "<-\n" . l:rest[:l:rest_start_idx - 1])
endif
let l:body = l:rest[l:body_start_idx : l:rest_start_idx - 1]
let l:rest = l:rest[l:rest_start_idx :]
try
" add the json body to the list.
call add(l:responses, json_decode(l:body))
catch
" TODO(bc): log the message and/or show an error message.
finally
" intentionally left blank.
endtry
endwhile
return [l:rest, l:responses]
endfunction
function! l:lsp.handleMessage(ch, data) dict abort
let self.buf .= a:data
let [self.buf, l:responses] = self.readMessage(self.buf)
" TODO(bc): handle notifications (e.g. window/showMessage).
for l:response in l:responses
if has_key(l:response, 'id') && has_key(self.handlers, l:response.id)
try
let l:handler = self.handlers[l:response.id]
if has_key(l:response, 'error')
call l:handler.requestComplete(0)
if has_key(l:handler, 'error')
call call(l:handler.error, [l:response.error.message])
else
call go#util#EchoError(l:response.error.message)
endif
return
endif
call l:handler.requestComplete(1)
call call(l:handler.handleResult, [l:response.result])
finally
call remove(self.handlers, l:response.id)
endtry
endif
endfor
endfunction
function! l:lsp.handleInitializeResult(result) dict abort
let self.ready = 1
" TODO(bc): send initialized message to the server?
" send messages queued while waiting for ready.
for l:item in self.queue
call self.sendMessage(l:item.data, l:item.handler)
endfor
" reset the queue
let self.queue = []
endfunction
function! l:lsp.sendMessage(data, handler) dict abort
if !self.last_request_id
" TODO(bc): run a server per module and one per GOPATH? (may need to
" keep track of servers by rootUri).
let l:msg = self.newMessage(go#lsp#message#Initialize(getcwd()))
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('self.handleInitializeResult', [], l:self)
let self.handlers[l:msg.id] = l:state
call l:state.start()
call self.write(l:msg)
endif
if !self.ready
call add(self.queue, {'data': a:data, 'handler': a:handler})
return
endif
let l:msg = self.newMessage(a:data)
if has_key(l:msg, 'id')
let self.handlers[l:msg.id] = a:handler
endif
call a:handler.start()
call self.write(l:msg)
endfunction
" newMessage returns a message constructed from data. data should be a dict
" with 2 or 3 keys: notification, method, and optionally params.
function! l:lsp.newMessage(data) dict abort
let l:msg = {
\ 'method': a:data.method,
\ 'jsonrpc': '2.0',
\ }
if !a:data.notification
let self.last_request_id += 1
let l:msg.id = self.last_request_id
endif
if has_key(a:data, 'params')
let l:msg.params = a:data.params
endif
return l:msg
endfunction
function! l:lsp.write(msg) dict abort
let l:body = json_encode(a:msg)
let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "->\n" . l:data)
endif
if has('nvim')
call chansend(self.job, l:data)
return
endif
call ch_sendraw(self.job, l:data)
endfunction
function! l:lsp.exit_cb(job, exit_status) dict abort
call s:lspfactory.reset()
endfunction
" explicitly bind close_cb to state so that within it, self will always refer
function! l:lsp.close_cb(ch) dict abort
" TODO(bc): does anything need to be done here?
endfunction
function! l:lsp.err_cb(ch, msg) dict abort
if go#util#HasDebug('lsp')
let g:go_lsp_log = add(go#config#LspLog(), "<-stderr\n" . a:msg)
endif
endfunction
" explicitly bind callbacks to l:lsp so that within it, self will always refer
" to l:lsp instead of l:opts. See :help Partial for more information.
let l:opts = {
\ 'in_mode': 'raw',
\ 'out_mode': 'raw',
\ 'err_mode': 'nl',
\ 'noblock': 1,
\ 'err_cb': funcref('l:lsp.err_cb', [], l:lsp),
\ 'out_cb': funcref('l:lsp.handleMessage', [], l:lsp),
\ 'close_cb': funcref('l:lsp.close_cb', [], l:lsp),
\ 'exit_cb': funcref('l:lsp.exit_cb', [], l:lsp),
\ 'cwd': getcwd(),
\}
let l:bin_path = go#path#CheckBinPath("gopls")
if empty(l:bin_path)
return
endif
" TODO(bc): output a message indicating which directory lsp is going to
" start in.
let l:lsp.job = go#job#Start([l:bin_path], l:opts)
" TODO(bc): send the initialize message now?
return l:lsp
endfunction
function! s:noop(...) abort
endfunction
function! s:newHandlerState(statustype) abort
let l:state = {
\ 'winid': win_getid(winnr()),
\ 'statustype': a:statustype,
\ 'jobdir': getcwd(),
\ }
" explicitly bind requestComplete to state so that within it, self will
" always refer to state. See :help Partial for more information.
let l:state.requestComplete = funcref('s:requestComplete', [], l:state)
" explicitly bind start to state so that within it, self will
" always refer to state. See :help Partial for more information.
let l:state.start = funcref('s:start', [], l:state)
return l:state
endfunction
function! s:requestComplete(ok) abort dict
if self.statustype == ''
return
endif
if go#config#EchoCommandInfo()
let prefix = '[' . self.statustype . '] '
if a:ok
call go#util#EchoSuccess(prefix . "SUCCESS")
else
call go#util#EchoError(prefix . "FAIL")
endif
endif
let status = {
\ 'desc': 'last status',
\ 'type': self.statustype,
\ 'state': "success",
\ }
if !a:ok
let status.state = "failed"
endif
if has_key(self, 'started_at')
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)
endif
call go#statusline#Update(self.jobdir, status)
endfunction
function! s:start() abort dict
if self.statustype != ''
let status = {
\ 'desc': 'current status',
\ 'type': self.statustype,
\ 'state': "started",
\ }
call go#statusline#Update(self.jobdir, status)
endif
let self.started_at = reltime()
endfunction
" go#lsp#Definition calls gopls to get the definition of the identifier at
" line and col in fname. handler should be a dictionary function that takes a
" list of strings in the form 'file:line:col: message'. handler will be
" attached to a dictionary that manages state (statuslines, sets the winid,
" etc.)
function! go#lsp#Definition(fname, line, col, handler) abort
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('definition')
let l:state.handleResult = funcref('s:definitionHandler', [function(a:handler, [], l:state)], l:state)
let l:msg = go#lsp#message#Definition(fnamemodify(a:fname, ':p'), a:line, a:col)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:definitionHandler(next, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
" go#lsp#Type calls gopls to get the type definition of the identifier at
" line and col in fname. handler should be a dictionary function that takes a
" list of strings in the form 'file:line:col: message'. handler will be
" attached to a dictionary that manages state (statuslines, sets the winid,
" etc.)
function! go#lsp#TypeDef(fname, line, col, handler) abort
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:state = s:newHandlerState('type definition')
let l:msg = go#lsp#message#TypeDefinition(fnamemodify(a:fname, ':p'), a:line, a:col)
let l:state.handleResult = funcref('s:typeDefinitionHandler', [function(a:handler, [], l:state)], l:state)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:typeDefinitionHandler(next, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:args = [[printf('%s:%d:%d: %s', go#path#FromURI(l:msg.uri), l:msg.range.start.line+1, l:msg.range.start.character+1, 'lsp does not supply a description')]]
call call(a:next, l:args)
endfunction
function! go#lsp#DidOpen(fname) abort
if get(b:, 'go_lsp_did_open', 0)
return
endif
if !filereadable(a:fname)
return
endif
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidOpen(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
let b:go_lsp_did_open = 1
endfunction
function! go#lsp#DidChange(fname) abort
" DidChange is called even when fname isn't open in a buffer (e.g. via
" go#lsp#Info); don't report the file as open or as having changed when it's
" not actually a buffer.
if bufnr(a:fname) == -1
return
endif
call go#lsp#DidOpen(a:fname)
if !filereadable(a:fname)
return
endif
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidChange(fnamemodify(a:fname, ':p'), join(go#util#GetLines(), "\n") . "\n")
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! go#lsp#DidClose(fname) abort
if !filereadable(a:fname)
return
endif
if !get(b:, 'go_lsp_did_open', 0)
return
endif
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#DidClose(fnamemodify(a:fname, ':p'))
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
let b:go_lsp_did_open = 0
endfunction
function! go#lsp#Completion(fname, line, col, handler) abort
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#Completion(a:fname, a:line, a:col)
let l:state = s:newHandlerState('completion')
let l:state.handleResult = funcref('s:completionHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:completionErrorHandler', [function(a:handler, [], l:state)], l:state)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:completionHandler(next, msg) abort dict
" gopls returns a CompletionList.
let l:matches = []
for l:item in a:msg.items
let l:match = {'abbr': l:item.label, 'word': l:item.textEdit.newText, 'info': '', 'kind': go#lsp#completionitemkind#Vim(l:item.kind)}
if has_key(l:item, 'detail')
let l:item.info = l:item.detail
endif
if has_key(l:item, 'documentation')
let l:match.info .= "\n\n" . l:item.documentation
endif
let l:matches = add(l:matches, l:match)
endfor
let l:args = [l:matches]
call call(a:next, l:args)
endfunction
function! s:completionErrorHandler(next, error) abort dict
call call(a:next, [[]])
endfunction
function! go#lsp#Hover(fname, line, col, handler) abort
call go#lsp#DidChange(a:fname)
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#Hover(a:fname, a:line, a:col)
let l:state = s:newHandlerState('')
let l:state.handleResult = funcref('s:hoverHandler', [function(a:handler, [], l:state)], l:state)
let l:state.error = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:hoverHandler(next, msg) abort dict
let l:content = split(a:msg.contents.value, '; ')
if len(l:content) > 1
let l:curly = stridx(l:content[0], '{')
let l:content = extend([l:content[0][0:l:curly]], map(extend([l:content[0][l:curly+1:]], l:content[1:]), '"\t" . v:val'))
let l:content[len(l:content)-1] = '}'
endif
let l:args = [l:content]
call call(a:next, l:args)
endfunction
function! go#lsp#Info(showstatus)
let l:fname = expand('%:p')
let [l:line, l:col] = getpos('.')[1:2]
call go#lsp#DidChange(l:fname)
let l:lsp = s:lspfactory.get()
if a:showstatus
let l:state = s:newHandlerState('info')
else
let l:state = s:newHandlerState('')
endif
let l:state.handleResult = funcref('s:infoDefinitionHandler', [function('s:info', []), a:showstatus], l:state)
let l:state.error = funcref('s:noop')
let l:msg = go#lsp#message#Definition(l:fname, l:line, l:col)
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:infoDefinitionHandler(next, showstatus, msg) abort dict
" gopls returns a []Location; just take the first one.
let l:msg = a:msg[0]
let l:fname = go#path#FromURI(l:msg.uri)
let l:line = l:msg.range.start.line+1
let l:col = l:msg.range.start.character+1
let l:lsp = s:lspfactory.get()
let l:msg = go#lsp#message#Hover(l:fname, l:line, l:col)
if a:showstatus
let l:state = s:newHandlerState('info')
else
let l:state = s:newHandlerState('')
endif
let l:state.handleResult = funcref('s:hoverHandler', [function('s:info', [], l:state)], l:state)
let l:state.error = funcref('s:noop')
call l:lsp.sendMessage(l:msg, l:state)
endfunction
function! s:info(content) abort dict
" strip off the method set and fields of structs and interfaces.
let l:content = substitute(a:content[0], '{.*', '', '')
call go#util#ShowInfo(l:content)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,47 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:Text = 1
let s:Method = 2
let s:Function = 3
let s:Constructor = 4
let s:Field = 5
let s:Variable = 6
let s:Class = 7
let s:Interface = 8
let s:Module = 9
let s:Property = 10
let s:Unit = 11
let s:Value = 12
let s:Enum = 13
let s:Keyword = 14
let s:Snippet = 15
let s:Color = 16
let s:File = 17
let s:Reference = 18
let s:Folder = 19
let s:EnumMember = 20
let s:Constant = 21
let s:Struct = 22
let s:Event = 23
let s:Operator = 24
let s:TypeParameter = 25
function! go#lsp#completionitemkind#Vim(kind)
if a:kind == s:Method || a:kind == s:Function || a:kind == s:Constructor
return 'f'
elseif a:kind == s:Variable || a:kind == s:Constant
return 'v'
elseif a:kind == s:Field || a:kind == s:Property
return 'm'
elseif a:kind == s:Class || a:kind == s:Interface || a:kind == s:Struct
return 't'
endif
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,127 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#lsp#message#Initialize(wd) abort
return {
\ 'notification': 0,
\ 'method': 'initialize',
\ 'params': {
\ 'processId': getpid(),
\ 'rootUri': go#path#ToURI(a:wd),
\ 'capabilities': {
\ 'workspace': {},
\ 'textDocument': {
\ 'hover': {
\ 'contentFormat': ['plaintext'],
\ },
\ }
\ }
\ }
\ }
endfunction
function! go#lsp#message#Definition(file, line, col) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/definition',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col)
\ }
\ }
endfunction
function! go#lsp#message#TypeDefinition(file, line, col) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/typeDefinition',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col)
\ }
\ }
endfunction
function! go#lsp#message#DidOpen(file, content) abort
return {
\ 'notification': 1,
\ 'method': 'textDocument/didOpen',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
\ 'languageId': 'go',
\ 'text': a:content,
\ }
\ }
\ }
endfunction
function! go#lsp#message#DidChange(file, content) abort
return {
\ 'notification': 1,
\ 'method': 'textDocument/didChange',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
\ },
\ 'contentChanges': [
\ {
\ 'text': a:content,
\ }
\ ]
\ }
\ }
endfunction
function! go#lsp#message#DidClose(file) abort
return {
\ 'notification': 1,
\ 'method': 'textDocument/didClose',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file),
\ }
\ }
\ }
endfunction
function! go#lsp#message#Completion(file, line, col) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/completion',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col),
\ }
\ }
endfunction
function! go#lsp#message#Hover(file, line, col) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/hover',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col),
\ }
\ }
endfunction
function! s:position(line, col) abort
return {'line': a:line - 1, 'character': a:col-1}
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,150 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:go_major_version = ""
function! go#mod#Format() abort
" go mod only exists in `v1.11`
if empty(s:go_major_version)
let tokens = matchlist(go#util#System("go version"), '\d\+.\(\d\+\)\(\.\d\+\)\? ')
let s:go_major_version = str2nr(tokens[1])
endif
if s:go_major_version < "11"
call go#util#EchoError("Go v1.11 is required to format go.mod file")
return
endif
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" Save cursor position and many other things.
let l:curw = winsaveview()
" Write current unsaved buffer to a temp file
let l:tmpname = tempname() . '.mod'
call writefile(go#util#GetLines(), l:tmpname)
if go#util#IsWin()
let l:tmpname = tr(l:tmpname, '\', '/')
endif
let current_col = col('.')
let l:args = ['go', 'mod', 'edit', '--fmt', l:tmpname]
let [l:out, l:err] = go#util#Exec(l:args)
let diff_offset = len(readfile(l:tmpname)) - line('$')
if l:err == 0
call go#mod#update_file(l:tmpname, fname)
else
let errors = s:parse_errors(fname, l:out)
call s:show_errors(errors)
endif
" We didn't use the temp file, so clean up
call delete(l:tmpname)
" Restore our cursor/windows positions.
call winrestview(l:curw)
" be smart and jump to the line the new statement was added/removed
call cursor(line('.') + diff_offset, current_col)
" Syntax highlighting breaks less often.
syntax sync fromstart
endfunction
" update_file updates the target file with the given formatted source
function! go#mod#update_file(source, target)
" remove undo point caused via BufWritePre
try | silent undojoin | catch | endtry
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
let l:listtype = go#list#Type("GoModFmt")
" the title information was introduced with 7.4-2200
" https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640
if has('patch-7.4.2200')
" clean up previous list
if l:listtype == "quickfix"
let l:list_title = getqflist({'title': 1})
else
let l:list_title = getloclist(0, {'title': 1})
endif
else
" can't check the title, so assume that the list was for go fmt.
let l:list_title = {'title': 'Format'}
endif
if has_key(l:list_title, "title") && l:list_title['title'] == "Format"
call go#list#Clean(l:listtype)
endif
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\+\):\s*\(.*\)')
if !empty(tokens)
call add(errors,{
\"filename": a:filename,
\"lnum": tokens[2],
\"text": tokens[3],
\ })
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 = go#list#Type("GoModFmt")
if !empty(a:errors)
call go#list#Populate(l:listtype, a:errors, 'Format')
call go#util#EchoError("GoModFmt returned error")
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#mod#ToggleModFmtAutoSave() abort
if go#config#ModFmtAutosave()
call go#config#SetModFmtAutosave(0)
call go#util#EchoProgress("auto mod fmt disabled")
return
end
call go#config#SetModFmtAutosave(1)
call go#util#EchoProgress("auto mod fmt enabled")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -5,6 +5,10 @@
" This file provides a utility function that performs auto-completion of
" package names, for use by other commands.
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:goos = $GOOS
let s:goarch = $GOARCH
@ -28,7 +32,7 @@ if len(s:goarch) == 0
endif
endif
function! go#package#Paths() abort
function! s:paths() abort
let dirs = []
if !exists("s:goroot")
@ -54,63 +58,119 @@ function! go#package#Paths() abort
return dirs
endfunction
function! s:module() abort
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-m', '-f', '{{.Dir}}'])
if l:err != 0
return {}
endif
let l:dir = split(l:out, '\n')[0]
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-m', '-f', '{{.Path}}'])
if l:err != 0
return {}
endif
let l:path = split(l:out, '\n')[0]
return {'dir': l:dir, 'path': l:path}
endfunction
function! s:vendordirs() abort
let l:vendorsuffix = go#util#PathSep() . 'vendor'
let l:module = s:module()
if empty(l:module)
let [l:root, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Root}}'])
if l:err != 0
return []
endif
let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src'
let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}'])
if l:err != 0
return []
endif
let l:dir = split(l:dir, '\n')[0]
let l:vendordirs = []
while l:dir != l:root
let l:vendordir = l:dir . l:vendorsuffix
if isdirectory(l:vendordir)
let l:vendordirs = add(l:vendordirs, l:vendordir)
endif
let l:dir = fnamemodify(l:dir, ':h')
endwhile
return l:vendordirs
endif
let l:vendordir = l:module.dir . l:vendorsuffix
if !isdirectory(l:vendordir)
return []
endif
return [l:vendordir]
endfunction
let s:import_paths = {}
" ImportPath returns the import path in the current directory it was executed
" ImportPath returns the import path of the package for current buffer.
function! go#package#ImportPath() abort
let dir = expand("%:p:h")
if has_key(s:import_paths, dir)
return s:import_paths[dir]
endif
let out = go#tool#ExecuteInDir("go list")
if go#util#ShellError() != 0
let [l:out, l:err] = go#util#ExecInDir(['go', 'list'])
if l:err != 0
return -1
endif
let import_path = split(out, '\n')[0]
let l:importpath = split(out, '\n')[0]
" go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH.
" Check it and retun an error if that is the case
if import_path[0] ==# '_'
if l:importpath[0] ==# '_'
return -1
endif
let s:import_paths[dir] = import_path
let s:import_paths[dir] = l:importpath
return import_path
return l:importpath
endfunction
" FromPath returns the import path of arg.
function! go#package#FromPath(arg) abort
let path = fnamemodify(resolve(a:arg), ':p')
let dirs = go#package#Paths()
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let l:dir = getcwd()
for dir in dirs
if len(dir) && match(path, dir) == 0
let workspace = dir
break
endif
endfor
let l:path = a:arg
if !isdirectory(l:path)
let l:path = fnamemodify(l:path, ':h')
endif
if !exists('workspace')
execute l:cd fnameescape(l:path)
let [l:out, l:err] = go#util#Exec(['go', 'list'])
execute l:cd fnameescape(l:dir)
if l:err != 0
return -1
endif
let path = substitute(path, '/*$', '', '')
let workspace = substitute(workspace . '/src/', '/+', '', '')
if isdirectory(path)
return substitute(path, workspace, '', '')
else
return substitute(substitute(path, workspace, '', ''),
\ '/' . fnamemodify(path, ':t'), '', '')
let l:importpath = split(l:out, '\n')[0]
" go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH.
" Check it and retun an error if that is the case
if l:importpath[0] ==# '_'
return -1
endif
return l:importpath
endfunction
function! go#package#CompleteMembers(package, member) abort
silent! let content = go#util#System('godoc ' . a:package)
if go#util#ShellError() || !len(content)
let [l:content, l:err] = go#util#Exec(['go', 'doc', a:package])
if l:err || !len(content)
return []
endif
let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
try
let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*'
@ -136,37 +196,88 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
return go#package#CompleteMembers(words[1], words[2])
endif
let dirs = go#package#Paths()
let dirs = s:paths()
let module = s:module()
if len(dirs) == 0
" should not happen
return []
endif
if len(dirs) == 0 && empty(module)
" should not happen
return []
endif
let ret = {}
for dir in dirs
" this may expand to multiple lines
let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
call add(root, expand(dir . '/src'))
for r in root
for i in split(globpath(r, a:ArgLead.'*'), "\n")
if isdirectory(i)
let i .= '/'
elseif i !~ '\.a$'
continue
endif
let i = substitute(substitute(i[len(r)+1:], '[\\]', '/', 'g'),
\ '\.a$', '', 'g')
let vendordirs = s:vendordirs()
" without this the result can have duplicates in form of
" 'encoding/json' and '/encoding/json/'
let i = go#util#StripPathSep(i)
let ret = {}
for dir in dirs
" this may expand to multiple lines
let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
let root = add(root, expand(dir . '/src'), )
let root = extend(root, vendordirs)
let root = add(root, module)
for item in root
" item may be a dictionary when operating in a module.
if type(item) == type({})
if empty(item)
continue
endif
let dir = item.dir
let path = item.path
else
let dir = item
let path = item
endif
let ret[i] = i
endfor
endfor
if !empty(module) && dir ==# module.dir
if stridx(a:ArgLead, module.path) == 0
if len(a:ArgLead) != len(module.path)
let glob = globpath(module.dir, substitute(a:ArgLead, module.path . '/\?', '', '').'*')
else
let glob = module.dir
endif
elseif stridx(module.path, a:ArgLead) == 0 && stridx(module.path, '/', len(a:ArgLead)) < 0
" use the module directory when a:ArgLead is contained in
" module.path and module.path does not have any path segments after
" a:ArgLead.
let glob = module.dir
else
continue
endif
else
let glob = globpath(dir, a:ArgLead.'*')
endif
for candidate in split(glob)
if isdirectory(candidate)
" TODO(bc): use wildignore instead of filtering out vendor
" directories manually?
if fnamemodify(candidate, ':t') == 'vendor'
continue
endif
let candidate .= '/'
elseif candidate !~ '\.a$'
continue
endif
if dir !=# path
let candidate = substitute(candidate, '^' . dir, path, 'g')
else
let candidate = candidate[len(dir)+1:]
endif
" replace a backslash with a forward slash and drop .a suffixes
let candidate = substitute(substitute(candidate, '[\\]', '/', 'g'),
\ '\.a$', '', 'g')
" without this the result can have duplicates in form of
" 'encoding/json' and '/encoding/json/'
let candidate = go#util#StripPathSep(candidate)
let ret[candidate] = candidate
endfor
endfor
return sort(keys(ret))
endfor
return sort(keys(ret))
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,58 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_Complete_GOPATH_simple() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package'
silent exe 'edit ' . $GOPATH . '/src/package/package.go'
call s:complete('package', ['package'])
endfunc
func! Test_Complete_Module_simple() abort
silent exe 'edit ' . fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package/src/package/package.go'
call s:complete('package', ['package'])
endfunc
func! Test_Complete_GOPATH_subdirs() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package'
silent exe 'edit ' . $GOPATH . '/src/package/package.go'
call s:complete('package/', ['package/bar', 'package/baz'])
endfunc
func! Test_Complete_Module_subdirs() abort
silent exe 'edit ' . fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package/src/package/package.go'
call s:complete('package/', ['package/bar', 'package/baz'])
endfunc
func! Test_Complete_GOPATH_baronly() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package'
silent exe 'edit ' . $GOPATH . '/src/package/package.go'
call s:complete('package/bar', ['package/bar'])
endfunc
func! Test_Complete_Module_baronly() abort
silent exe 'edit ' . fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package/src/package/package.go'
call s:complete('package/bar', ['package/bar'])
endfunc
func! Test_Complete_GOPATH_vendor() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package'
silent exe 'edit ' . $GOPATH . '/src/package/package.go'
call s:complete('foo', ['foo'])
endfunc
func! Test_Complete_Module_vendor() abort
silent exe 'edit ' . fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/package/src/package/package.go'
call s:complete('foo', ['foo'])
endfunc
func! s:complete(arglead, expected) abort
let l:candidates = go#package#Complete(a:arglead, '', 1)
call assert_equal(a:expected, l:candidates)
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" 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
@ -121,13 +125,14 @@ endfunction
" BinPath returns the binary path of installed go tools.
function! go#path#BinPath() abort
let bin_path = ""
let bin_path = go#config#BinPath()
if bin_path != ""
return bin_path
endif
" check if our global custom path is set, if not check if $GOBIN is set so
" we can use it, otherwise use default GOPATH
if exists("g:go_bin_path")
let bin_path = g:go_bin_path
elseif $GOBIN != ""
if $GOBIN != ""
let bin_path = $GOBIN
else
let go_paths = split(go#path#Default(), go#util#PathListSep())
@ -141,11 +146,13 @@ function! go#path#BinPath() abort
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.
" path of the binary, respecting the go_bin_path and go_search_bin_path_first
" settings. It returns an empty string if the binary doesn't exist.
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
" save original path
let old_path = $PATH
" check if we have an appropriate bin_path
@ -153,7 +160,12 @@ function! go#path#CheckBinPath(binpath) abort
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
" respect the ordering specified by go_search_bin_path_first
if go#config#SearchBinPathFirst()
let $PATH = go_bin_path . go#util#PathListSep() . $PATH
else
let $PATH = $PATH . go#util#PathListSep() . go_bin_path
endif
endif
" if it's in PATH just return it
@ -193,4 +205,44 @@ function! s:CygwinPath(path)
return substitute(a:path, '\\', '/', "g")
endfunction
" go#path#ToURI converts path to a file URI. path should be an absolute path.
" Relative paths cannot be properly converted to a URI; when path is a
" relative path, the file scheme will not be prepended.
function! go#path#ToURI(path)
let l:absolute = !go#util#IsWin() && a:path[0] is# '/'
let l:prefix = ''
let l:path = a:path
if go#util#IsWin() && l:path[1:2] is# ':\'
let l:absolute = 1
let l:prefix = '/' . l:path[0:1]
let l:path = l:path[2:]
endif
return substitute(
\ (l:absolute ? 'file://' : '') . l:prefix . go#uri#EncodePath(l:path),
\ '\\',
\ '/',
\ 'g',
\)
endfunction
function! go#path#FromURI(uri) abort
let l:i = len('file://')
let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri
let l:path = go#uri#Decode(l:encoded_path)
" If the path is like /C:/foo/bar, it should be C:\foo\bar instead.
if go#util#IsWin() && l:path =~# '^/[a-zA-Z]:'
let l:path = substitute(l:path[1:], '/', '\\', 'g')
endif
return l:path
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,7 +1,6 @@
if !exists("g:go_play_open_browser")
let g:go_play_open_browser = 1
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#play#Share(count, line1, line2) abort
if !executable('curl')
@ -13,15 +12,16 @@ function! go#play#Share(count, line1, line2) abort
let share_file = tempname()
call writefile(split(content, "\n"), share_file, "b")
let command = "curl -s -X POST https://play.golang.org/share --data-binary '@".share_file."'"
let snippet_id = go#util#System(command)
let l:cmd = ['curl', '-s', '-X', 'POST', 'https://play.golang.org/share',
\ '--data-binary', '@' . l:share_file]
let [l:snippet_id, l:err] = go#util#Exec(l:cmd)
" we can remove the temp file because it's now posted.
call delete(share_file)
if go#util#ShellError() != 0
echo 'A error has occurred. Run this command to see what the problem is:'
echo command
if l:err != 0
echom 'A error has occurred. Run this command to see what the problem is:'
echom go#util#Shelljoin(l:cmd)
return
endif
@ -34,8 +34,8 @@ function! go#play#Share(count, line1, line2) abort
let @+ = url
endif
if g:go_play_open_browser != 0
call go#tool#OpenBrowser(url)
if go#config#PlayOpenBrowser()
call go#util#OpenBrowser(url)
endif
echo "vim-go: snippet uploaded: ".url
@ -70,4 +70,8 @@ function! s:get_visual_selection() abort
return join(lines, "\n")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,26 +1,14 @@
if !exists("g:go_gorename_bin")
let g:go_gorename_bin = "gorename"
endif
" Set the default value. A value of "1" is a shortcut for this, for
" compatibility reasons.
function! s:default() abort
if !exists("g:go_gorename_prefill") || g:go_gorename_prefill == 1
let g:go_gorename_prefill = 'expand("<cword>") =~# "^[A-Z]"' .
\ '? go#util#pascalcase(expand("<cword>"))' .
\ ': go#util#camelcase(expand("<cword>"))'
endif
endfunction
call s:default()
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#rename#Rename(bang, ...) abort
call s:default()
let to_identifier = ""
if a:0 == 0
let ask = printf("vim-go: rename '%s' to: ", expand("<cword>"))
if g:go_gorename_prefill != ''
let to_identifier = input(ask, eval(g:go_gorename_prefill))
let prefill = go#config#GorenamePrefill()
if prefill != ''
let to_identifier = input(ask, eval(prefill))
else
let to_identifier = input(ask)
endif
@ -33,7 +21,7 @@ function! go#rename#Rename(bang, ...) abort
endif
" return with a warning if the bin doesn't exist
let bin_path = go#path#CheckBinPath(g:go_gorename_bin)
let bin_path = go#path#CheckBinPath(go#config#GorenameBin())
if empty(bin_path)
return
endif
@ -41,22 +29,9 @@ function! go#rename#Rename(bang, ...) abort
let fname = expand('%:p')
let pos = go#util#OffsetCursor()
let offset = printf('%s:#%d', fname, pos)
" 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)
let cmd = [bin_path, "-offset", offset, "-to", to_identifier]
" check for any tags
if exists('g:go_build_tags')
let tags = get(g:, 'go_build_tags')
call extend(cmd, ["-tags", tags])
endif
let cmd = [bin_path, "-offset", offset, "-to", to_identifier, '-tags', go#config#BuildTags()]
if go#util#has_job()
call go#util#EchoProgress(printf("renaming to '%s' ...", to_identifier))
call s:rename_job({
\ 'cmd': cmd,
\ 'bang': a:bang,
@ -64,71 +39,42 @@ function! go#rename#Rename(bang, ...) abort
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)
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
call s:parse_errors(l:err, a:bang, split(l:out, '\n'))
endfunction
function s:rename_job(args)
let state = {
\ 'exited': 0,
\ 'closed': 0,
\ 'exitval': 0,
\ 'messages': [],
\ 'status_dir': expand('%:p:h'),
\ 'bang': a:args.bang
\ }
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exitval = a:exitval
let status = {
\ 'desc': 'last status',
\ 'type': "gorename",
\ 'state': "finished",
\ }
if a:exitval
let status.state = "failed"
endif
call go#statusline#Update(self.status_dir, status)
if self.closed
call s:parse_errors(self.exitval, self.bang, self.messages)
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call s:parse_errors(self.exitval, self.bang, self.messages)
endif
endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = {
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state),
let l:job_opts = {
\ 'bang': a:args.bang,
\ 'for': 'GoRename',
\ 'statustype': 'gorename',
\ }
call go#statusline#Update(state.status_dir, {
\ 'desc': "current status",
\ 'type': "gorename",
\ 'state': "started",
\})
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let l:cbs = go#job#Options(l:job_opts)
call job_start(a:args.cmd, start_options)
" wrap l:cbs.exit_cb in s:exit_cb.
let l:cbs.exit_cb = funcref('s:exit_cb', [l:cbs.exit_cb])
call go#job#Start(a:args.cmd, l:cbs)
endfunction
function! s:reload_changed() abort
" 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
endfunction
" s:exit_cb reloads any changed buffers and then calls next.
function! s:exit_cb(next, job, exitval) abort
call s:reload_changed()
call call(a:next, [a:job, a:exitval])
endfunction
function s:parse_errors(exit_val, bang, out)
@ -143,8 +89,7 @@ function s:parse_errors(exit_val, bang, out)
let l:listtype = go#list#Type("GoRename")
if a:exit_val != 0
call go#util#EchoError("FAILED")
let errors = go#tool#ParseErrors(a:out)
let errors = go#util#ParseErrors(a:out)
call go#list#Populate(l:listtype, errors, 'Rename')
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang
@ -163,9 +108,6 @@ function s:parse_errors(exit_val, bang, out)
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
@ -178,4 +120,8 @@ function! go#rename#Complete(lead, cmdline, cursor)
\ 'strpart(v:val, 0, len(a:lead)) == a:lead')
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" Statusline
""""""""""""""""""""""""""""""""
@ -26,7 +30,7 @@ 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 interval = go#config#StatuslineDuration()
let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1})
endif
@ -54,11 +58,11 @@ function! go#statusline#Show() abort
" only update highlight if status has changed.
if status_text != s:last_status
if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass"
hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22
hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22 guibg=#5fd700 guifg=#005f00
elseif status.state =~ "started" || status.state =~ "analysing" || status.state =~ "compiling"
hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88
hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88 guibg=#ff8700 guifg=#870000
elseif status.state =~ "failed"
hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52
hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52 guibg=#ff0000 guifg=#5f0000
endif
endif
@ -109,4 +113,8 @@ function! go#statusline#Clear(timer_id) abort
exe 'let &ro = &ro'
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" mapped to :GoAddTags
function! go#tags#Add(start, end, count, ...) abort
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
@ -31,24 +35,22 @@ function! go#tags#run(start, end, offset, mode, fname, test_mode, ...) abort
let args["modified"] = 1
endif
let result = s:create_cmd(args)
let l:result = s:create_cmd(args)
if has_key(result, 'err')
call go#util#EchoError(result.err)
return -1
endif
let command = join(result.cmd, " ")
if &modified
let filename = expand("%:p:gs!\\!/!")
let content = join(go#util#GetLines(), "\n")
let in = filename . "\n" . strlen(content) . "\n" . content
let out = go#util#System(command, in)
let [l:out, l:err] = go#util#Exec(l:result.cmd, in)
else
let out = go#util#System(command)
let [l:out, l:err] = go#util#Exec(l:result.cmd)
endif
if go#util#ShellError() != 0
if l:err != 0
call go#util#EchoError(out)
return
endif
@ -115,19 +117,18 @@ func s:create_cmd(args) abort
if empty(bin_path)
return {'err': "gomodifytags does not exist"}
endif
let bin_path = go#util#Shellescape(bin_path)
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
let l:modifytags_transform = get(g:, 'go_addtags_transform', "snakecase")
let l:modifytags_transform = go#config#AddtagsTransform()
" start constructing the command
let cmd = [bin_path]
call extend(cmd, ["-format", "json"])
call extend(cmd, ["-file", go#util#Shellescape(a:args.fname)])
call extend(cmd, ["-file", a:args.fname])
call extend(cmd, ["-transform", l:modifytags_transform])
if has_key(a:args, "modified")
@ -162,17 +163,17 @@ func s:create_cmd(args) abort
endfor
endif
" construct options
" default value
if empty(l:tags)
let l:tags = ["json"]
endif
" construct tags
call extend(cmd, ["-add-tags", join(l:tags, ",")])
" 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)
@ -211,4 +212,8 @@ func s:create_cmd(args) abort
return {'cmd': cmd}
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,4 +1,8 @@
func! Test_add_tags() abort
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! TestAddTags() abort
try
let l:tmp = gotest#load_fixture('tags/add_all_input.go')
silent call go#tags#run(0, 0, 40, "add", bufname(''), 1)
@ -9,6 +13,28 @@ func! Test_add_tags() abort
endfunc
func! TestAddTags_WithOptions() abort
try
let l:tmp = gotest#load_fixture('tags/add_all_input.go')
silent call go#tags#run(0, 0, 40, "add", bufname(''), 1, 'json,omitempty')
call gotest#assert_fixture('tags/add_all_golden_options.go')
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! TestAddTags_AddOptions() abort
try
let l:tmp = gotest#load_fixture('tags/add_all_input.go')
silent call go#tags#run(0, 0, 40, "add", bufname(''), 1, 'json')
call gotest#assert_fixture('tags/add_all_golden.go')
silent call go#tags#run(0, 0, 40, "add", bufname(''), 1, 'json,omitempty')
call gotest#assert_fixture('tags/add_all_golden_options.go')
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_remove_tags() abort
try
let l:tmp = gotest#load_fixture('tags/remove_all_input.go')
@ -19,4 +45,8 @@ func! Test_remove_tags() abort
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim:ts=2:sts=2:sw=2:et

View file

@ -1,53 +1,56 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
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:go_template_use_pkg = go#config#TemplateUsePkg()
let l:root_dir = fnamemodify(s:current_file, ':h:h:h')
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
let l:package_name = -1
let l:package_name = go#tool#PackageName()
if isdirectory(expand('%:p:h'))
execute cd . fnameescape(expand('%:p:h'))
let l:package_name = go#tool#PackageName()
endif
" 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:filename = fnamemodify(expand("%"), ':t')
if l:filename =~ "_test.go$"
let l:template_file = get(g:, 'go_template_test_file', "hello_world_test.go")
" if we can't figure out any package name (i.e. no Go files in the directory)
" from the directory create the template or use the directory as the name.
if l:package_name == -1
if l:go_template_use_pkg == 1
let l:path = fnamemodify(expand('%:p:h'), ':t')
let l:content = printf("package %s", l:path)
call append(0, l:content)
else
let l:template_file = get(g:, 'go_template_file', "hello_world.go")
let l:filename = expand('%:t')
if l:filename =~ "_test.go$"
let l:template_file = go#config#TemplateTestFile()
else
let l:template_file = go#config#TemplateFile()
endif
let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file)
silent exe 'keepalt 0r ' . fnameescape(l:template_path)
endif
let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file)
silent exe 'keepalt 0r ' . fnameescape(l:template_path)
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)
else
let l:content = printf("package %s", l:package_name)
call append(0, l:content)
endif
$delete _
execute cd . fnameescape(dir)
" checking that the last line is empty shouldn't be necessary, but for some
" reason the last line isn't the expected empty line when run via tests.
if getline('$') is ''
$delete _
endif
endfunction
function! go#template#ToggleAutoCreate() abort
if get(g:, "go_template_autocreate", 1)
let g:go_template_autocreate = 0
if go#config#TemplateAutocreate()
call go#config#SetTemplateAutocreate(0)
call go#util#EchoProgress("auto template create disabled")
return
end
let g:go_template_autocreate = 1
call go#config#SetTemplateAutocreate(1)
call go#util#EchoProgress("auto template create enabled")
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,62 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_TemplateCreate() abort
try
let l:tmp = gotest#write_file('foo/empty.txt', [''])
edit foo/bar.go
call gotest#assert_buffer(1, [
\ 'func main() {',
\ '\tfmt.Println("vim-go")',
\ '}'])
finally
call delete(l:tmp, 'rf')
endtry
try
let l:tmp = gotest#write_file('foo/empty.txt', [''])
edit foo/bar_test.go
call gotest#assert_buffer(1, [
\ 'func TestHelloWorld(t *testing.T) {',
\ '\t// t.Fatal("not implemented")',
\ '}'])
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_TemplateCreate_UsePkg() abort
try
let l:tmp = gotest#write_file('foo/empty.txt', [''])
let g:go_template_use_pkg = 1
edit foo/bar.go
call gotest#assert_buffer(0, ['package foo'])
finally
unlet g:go_template_use_pkg
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_TemplateCreate_PackageExists() abort
try
let l:tmp = gotest#write_file('quux/quux.go', ['package foo'])
edit quux/bar.go
call gotest#assert_buffer(0, ['package foo'])
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,34 +1,36 @@
if has('nvim') && !exists("g:go_term_mode")
let g:go_term_mode = 'vsplit'
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" 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)
function! go#term#new(bang, cmd, errorformat) abort
return go#term#newmode(a:bang, a:cmd, a:errorformat, go#config#TermMode())
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
" go#term#newmode creates a new terminal with the given command and window mode.
function! go#term#newmode(bang, cmd, errorformat, mode) abort
let l:mode = a:mode
if empty(l:mode)
let l:mode = go#config#TermMode()
endif
let state = {
let l:state = {
\ 'cmd': a:cmd,
\ 'bang' : a:bang,
\ 'winid': win_getid(winnr()),
\ 'stdout': []
\ 'stdout': [],
\ 'stdout_buf': '',
\ 'errorformat': a:errorformat,
\ }
" execute go build in the files directory
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let l:dir = getcwd()
execute cd . fnameescape(expand("%:p:h"))
execute l:cd . fnameescape(expand("%:p:h"))
execute mode.' __go_term__'
execute l:mode . ' __go_term__'
setlocal filetype=goterm
setlocal bufhidden=delete
@ -41,79 +43,107 @@ function! go#term#newmode(bang, cmd, mode) abort
"
" Don't set an on_stderr, because it will be passed the same data as
" on_stdout. See https://github.com/neovim/neovim/issues/2836
let job = {
let l:job = {
\ 'on_stdout': function('s:on_stdout', [], state),
\ 'on_exit' : function('s:on_exit', [], state),
\ }
let state.id = termopen(a:cmd, job)
let state.termwinid = win_getid(winnr())
let l:state.id = termopen(a:cmd, l:job)
let l:state.termwinid = win_getid(winnr())
execute cd . fnameescape(dir)
execute l:cd . fnameescape(l:dir)
" resize new term if needed.
let height = get(g:, 'go_term_height', winheight(0))
let width = get(g:, 'go_term_width', winwidth(0))
let l:height = go#config#TermHeight()
let l:width = go#config#TermWidth()
" Adjust the window width or height depending on whether it's a vertical or
" horizontal split.
if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew"
exe 'vertical resize ' . width
if l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew"
exe 'vertical resize ' . l:width
elseif mode =~ "split" || mode =~ "new"
exe 'resize ' . height
exe 'resize ' . l:height
endif
" we also need to resize the pty, so there you go...
call jobresize(state.id, width, height)
call jobresize(l:state.id, l:width, l:height)
call win_gotoid(state.winid)
call win_gotoid(l:state.winid)
return state.id
return l:state.id
endfunction
function! s:on_stdout(job_id, data, event) dict abort
call extend(self.stdout, a:data)
" A single empty string means EOF was reached. The first item will never be
" the empty string except for when it's the only item and is signaling that
" EOF was reached.
if len(a:data) == 1 && a:data[0] == ''
" when there's nothing buffered, return early so that an
" erroneous message will not be added.
if self.stdout_buf == ''
return
endif
let self.stdout = add(self.stdout, self.stdout_buf)
else
let l:data = copy(a:data)
let l:data[0] = self.stdout_buf . l:data[0]
" The last element may be a partial line; save it for next time.
let self.stdout_buf = l:data[-1]
let self.stdout = extend(self.stdout, l:data[:-2])
endif
endfunction
function! s:on_exit(job_id, exit_status, event) dict abort
let l:winid = win_getid(winnr())
call win_gotoid(self.winid)
let l:listtype = go#list#Type("_term")
" usually there is always output so never branch into this clause
if empty(self.stdout)
call s:cleanlist(self.winid, l:listtype)
if a:exit_status == 0
call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
return
endif
let errors = go#tool#ParseErrors(self.stdout)
let errors = go#tool#FilterValids(errors)
call win_gotoid(self.winid)
if !empty(errors)
" close terminal; we don't need it anymore
call win_gotoid(self.termwinid)
close
let l:title = self.cmd
if type(l:title) == v:t_list
let l:title = join(self.cmd)
endif
call win_gotoid(self.winid)
let l:i = 0
while l:i < len(self.stdout)
let self.stdout[l:i] = substitute(self.stdout[l:i], "\r$", '', 'g')
let l:i += 1
endwhile
call go#list#Populate(l:listtype, errors, self.cmd)
call go#list#Window(l:listtype, len(errors))
if !self.bang
call go#list#JumpToFirst(l:listtype)
endif
call go#list#ParseFormat(l:listtype, self.errorformat, self.stdout, l:title)
let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(l:errors))
if empty(l:errors)
call go#util#EchoError( '[' . l:title . '] ' . "FAIL")
call win_gotoid(l:winid)
return
endif
call s:cleanlist(self.winid, l:listtype)
" close terminal; we don't need it anymore
call win_gotoid(self.termwinid)
close!
if self.bang
call win_gotoid(l:winid)
return
endif
call win_gotoid(self.winid)
call go#list#JumpToFirst(l:listtype)
endfunction
function! s:cleanlist(winid, listtype) abort
" There are no errors. Clean and close the list. Jump to the window to which
" the location list is attached, close the list, and then jump back to the
" current window.
let winid = win_getid(winnr())
call win_gotoid(a:winid)
call go#list#Clean(a:listtype)
call win_gotoid(l:winid)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_GoTermNewMode()
if !has('nvim')
return
@ -13,7 +17,7 @@ func! Test_GoTermNewMode()
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set nosplitright
call go#term#newmode(0, cmd, '')
call go#term#new(0, cmd, &errorformat)
let actual = expand('%:p')
call assert_equal(actual, l:expected)
@ -37,7 +41,7 @@ func! Test_GoTermNewMode_SplitRight()
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set splitright
call go#term#newmode(0, cmd, '')
call go#term#new(0, cmd, &errorformat)
let actual = expand('%:p')
call assert_equal(actual, l:expected)
@ -47,4 +51,8 @@ func! Test_GoTermNewMode_SplitRight()
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,9 @@
package complete
type T struct {
V string
}
func Example(s string) {
Example("")
}

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 @@
../imports/

View file

@ -0,0 +1,3 @@
module package
go 1.12

View file

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

View file

@ -0,0 +1,16 @@
package main
type Server struct {
Name string `json:"name,omitempty"`
ID int `json:"id,omitempty"`
MyHomeAddress string `json:"my_home_address,omitempty"`
SubDomains []string `json:"sub_domains,omitempty"`
Empty string `json:"empty,omitempty"`
Example int64 `json:"example,omitempty"`
Example2 string `json:"example_2,omitempty"`
Bar struct {
Four string `json:"four,omitempty"`
Five string `json:"five,omitempty"`
} `json:"bar,omitempty"`
Lala interface{} `json:"lala,omitempty"`
}

View file

@ -0,0 +1,11 @@
package main
import (
"io/ioutil"
"testing"
)
func TestSomething(t *testing.T) {
r := struct{}{}
ioutil.ReadAll(r)
}

View file

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Errorf("%v")
}

View file

@ -1,8 +1,12 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" 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 appended to the final `go test` command.
function! go#test#Test(bang, compile, ...) abort
let args = ["test"]
let args = ["test", '-tags', go#config#BuildTags()]
" don't run the test, only compile it. Useful to capture and fix errors.
if a:compile
@ -10,11 +14,6 @@ function! go#test#Test(bang, compile, ...) abort
call extend(args, ["-c", "-o", testfile])
endif
if exists('g:go_build_tags')
let tags = get(g:, 'go_build_tags')
call extend(args, ["-tags", tags])
endif
if a:0
let goargs = a:000
@ -24,18 +23,35 @@ function! go#test#Test(bang, compile, ...) abort
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')
let timeout = go#config#TestTimeout()
call add(args, printf("-timeout=%s", timeout))
endif
if get(g:, 'go_echo_command_info', 1)
if go#config#TermEnabled()
call go#term#new(a:bang, ["go"] + args, s:errorformat())
endif
if go#util#has_job()
" use vim's job functionality to call it asynchronously
let job_options = {
\ 'bang': a:bang,
\ 'for': 'GoTest',
\ 'statustype': 'test',
\ 'errorformat': s:errorformat(),
\ }
if a:compile
let job_options.statustype = 'compile ' . job_options.statustype
endif
call s:test_job(['go'] + args, job_options)
return
endif
if go#config#EchoCommandInfo()
if a:compile
call go#util#EchoProgress("compiling tests ...")
else
@ -43,35 +59,12 @@ function! go#test#Test(bang, compile, ...) abort
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,
\ 'winnr': winnr(),
\ 'dir': getcwd(),
\ 'compile_test': a:compile,
\ 'jobdir': fnameescape(expand("%:p:h")),
\ }
call s:test_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", "GoTest", args)
endif
return id
endif
call go#cmd#autowrite()
redraw
let command = "go " . join(args, ' ')
let out = go#tool#ExecuteInDir(command)
let l:cmd = ['go'] + l:args
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
" TODO(bc): When the output is JSON, the JSON should be run through a
" filter to produce lines that are more easily described by errorformat.
@ -81,17 +74,19 @@ function! go#test#Test(bang, compile, ...) abort
let dir = getcwd()
execute cd fnameescape(expand("%:p:h"))
if go#util#ShellError() != 0
call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), command)
if l:err != 0
let l:winid = win_getid(winnr())
call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), l:cmd)
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)
elseif empty(errors)
if empty(errors)
" failed to parse errors, output the original content
call go#util#EchoError(out)
elseif a:bang
call win_gotoid(l:winid)
else
call go#list#JumpToFirst(l:listtype)
endif
call go#util#EchoError("[test] FAIL")
else
call go#list#Clean(l:listtype)
@ -130,156 +125,22 @@ function! go#test#Func(bang, ...) abort
call extend(args, a:000)
else
" only add this if no custom flags are passed
let timeout = get(g:, 'go_test_timeout', '10s')
let timeout = go#config#TestTimeout()
call add(args, printf("-timeout=%s", timeout))
endif
call call('go#test#Test', args)
endfunction
function! s:test_job(args) abort
let status = {
\ 'desc': 'current status',
\ 'type': "test",
\ 'state': "started",
\ }
if a:args.compile_test
let status.state = "compiling"
endif
function! s:test_job(cmd, args) abort
" autowrite is not enabled for jobs
call go#cmd#autowrite()
let state = {
\ 'exited': 0,
\ 'closed': 0,
\ 'exitval': 0,
\ 'messages': [],
\ 'args': a:args,
\ 'compile_test': a:args.compile_test,
\ 'status_dir': expand('%:p:h'),
\ 'started_at': reltime()
\ }
call go#statusline#Update(state.status_dir, status)
function! s:callback(chan, msg) dict
call add(self.messages, a:msg)
endfunction
function! s:exit_cb(job, exitval) dict
let self.exited = 1
let self.exitval = a:exitval
let status = {
\ 'desc': 'last status',
\ 'type': "test",
\ 'state': "pass",
\ }
if self.compile_test
let status.state = "success"
endif
if a:exitval
let status.state = "failed"
endif
if get(g:, 'go_echo_command_info', 1)
if a:exitval == 0
if self.compile_test
call go#util#EchoSuccess("[test] SUCCESS")
else
call go#util#EchoSuccess("[test] PASS")
endif
else
call go#util#EchoError("[test] FAIL")
endif
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)
if self.closed
call s:show_errors(self.args, self.exitval, self.messages)
endif
endfunction
function! s:close_cb(ch) dict
let self.closed = 1
if self.exited
call s:show_errors(self.args, self.exitval, self.messages)
endif
endfunction
" explicitly bind the callbacks to state so that self within them always
" refers to state. See :help Partial for more information.
let start_options = {
\ 'callback': funcref("s:callback", [], state),
\ 'exit_cb': funcref("s:exit_cb", [], state),
\ 'close_cb': funcref("s:close_cb", [], state)
\ }
" 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)
call go#job#Spawn(a:cmd, a:args)
endfunction
" show_errors parses the given list of lines of a 'go test' output and returns
" a quickfix compatible list of errors. It's intended to be used only for go
" test output.
function! s:show_errors(args, exit_val, messages) abort
let l:listtype = go#list#Type("GoTest")
if a:exit_val == 0
call go#list#Clean(l:listtype)
return
endif
" TODO(bc): When messages is JSON, the JSON should be run through a
" filter to produce lines that are more easily described by errorformat.
let l:listtype = go#list#Type("GoTest")
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
try
execute cd a:args.jobdir
call go#list#ParseFormat(l:listtype, s:errorformat(), a:messages, join(a:args.cmd))
let errors = go#list#Get(l:listtype)
finally
execute cd . fnameescape(a:args.dir)
endtry
if !len(errors)
" failed to parse errors, output the original content
call go#util#EchoError(a:messages)
call go#util#EchoError(a:args.dir)
return
endif
if a:args.winnr == winnr()
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:args.bang
call go#list#JumpToFirst(l:listtype)
endif
endif
endfunction
let s:efm= ""
let s:go_test_show_name=0
let s:efm = ""
let s:go_test_show_name = 0
function! s:errorformat() abort
" NOTE(arslan): once we get JSON output everything will be easier :).
@ -288,7 +149,7 @@ function! s:errorformat() abort
" https://github.com/golang/go/issues/2981.
let goroot = go#util#goroot()
let show_name=get(g:, 'go_test_show_name', 0)
let show_name = go#config#TestShowName()
if s:efm != "" && s:go_test_show_name == show_name
return s:efm
endif
@ -297,15 +158,12 @@ function! s:errorformat() abort
" each level of test indents the test output 4 spaces. Capturing groups
" (e.g. \(\)) cannot be used in an errorformat, but non-capturing groups can
" (e.g. \%(\)).
let indent = '%\\%( %\\)%#'
" match compiler errors
let format = "%f:%l:%c: %m"
let indent = '%\\%( %\\)'
" ignore `go test -v` output for starting tests
let format .= ",%-G=== RUN %.%#"
let format = "%-G=== RUN %.%#"
" ignore `go test -v` output for passing tests
let format .= ",%-G" . indent . "--- PASS: %.%#"
let format .= ",%-G" . indent . "%#--- PASS: %.%#"
" Match failure lines.
"
@ -315,24 +173,25 @@ function! s:errorformat() abort
" e.g.:
" '--- FAIL: TestSomething (0.00s)'
if show_name
let format .= ",%G" . indent . "--- FAIL: %m (%.%#)"
let format .= ",%G" . indent . "%#--- FAIL: %m (%.%#)"
else
let format .= ",%-G" . indent . "--- FAIL: %.%#"
let format .= ",%-G" . indent . "%#--- FAIL: %.%#"
endif
" Go 1.10 test output {{{1
" Matches test output lines.
"
" All test output lines start with the test indentation and a tab, followed
" by the filename, a colon, the line number, another colon, a space, and the
" message. e.g.:
" '\ttime_test.go:30: Likely problem: the time zone files have not been installed.'
let format .= ",%A" . indent . "%\\t%\\+%f:%l: %m"
let format .= ",%A" . indent . "%#%\\t%\\+%f:%l: %m"
" also match lines that don't have a message (i.e. the message begins with a
" newline or is the empty string):
" e.g.:
" t.Errorf("\ngot %v; want %v", actual, expected)
" t.Error("")
let format .= ",%A" . indent . "%\\t%\\+%f:%l: "
let format .= ",%A" . indent . "%#%\\t%\\+%f:%l: "
" Match the 2nd and later lines of multi-line output. These lines are
" indented the number of spaces for the level of nesting of the test,
@ -345,7 +204,17 @@ function! s:errorformat() abort
" indicate that they're multiple lines of output, but in that case the lines
" get concatenated in the quickfix list, which is not what users typically
" want when writing a newline into their test output.
let format .= ",%G" . indent . "%\\t%\\{2}%m"
let format .= ",%G" . indent . "%#%\\t%\\{2}%m"
" }}}1
" Go 1.11 test output {{{1
" Match test output lines similarly to Go 1.10 test output lines, but they
" use an indent level where the Go 1.10 test output uses tabs, so they'll
" always have at least one level indentation...
let format .= ",%A" . indent . "%\\+%f:%l: %m"
let format .= ",%A" . indent . "%\\+%f:%l: "
let format .= ",%G" . indent . "%\\{2\\,}%m"
" }}}1
" set the format for panics.
@ -375,7 +244,7 @@ function! s:errorformat() abort
" e.g.:
" '\t/usr/local/go/src/time.go:1313 +0x5d'
" panicaddress, and readyaddress are identical except for
" panicaddress and readyaddress are identical except for
" panicaddress sets the filename and line number.
let panicaddress = "%\\t%f:%l +0x%[0-9A-Fa-f]%\\+"
let readyaddress = "%\\t%\\f%\\+:%\\d%\\+ +0x%[0-9A-Fa-f]%\\+"
@ -398,6 +267,11 @@ function! s:errorformat() abort
" the running goroutine's stack.
let format .= ",%Z" . panicaddress
" Match and ignore errors from runtime.goparkunlock(). These started
" appearing in stack traces from Go 1.12 test timeouts.
let format .= ",%-Gruntime.goparkunlock(%.%#"
let format .= ",%-G%\\t" . goroot . "%\\f%\\+:%\\d%\\+"
" Match and ignore panic address without being part of a multi-line message.
" This is to catch those lines that come after the top most non-standard
" library line in stack traces.
@ -409,12 +283,26 @@ function! s:errorformat() abort
let format .= ",%-Cexit status %[0-9]%\\+"
"let format .= ",exit status %[0-9]%\\+"
" Match and ignore exit failure lines whether part of a multi-line message
" Match and ignore failure lines whether part of a multi-line message
" or not, because these lines sometimes come before and sometimes after
" panic stacktraces.
let format .= ",%-CFAIL%\\t%.%#"
"let format .= ",FAIL%\\t%.%#"
" match compiler errors.
" These are very smilar to errors from <=go1.10 test output, but lack
" leading tabs for the first line of an error, and subsequent lines only
" have one tab instead of two.
let format .= ",%A%f:%l:%c: %m"
let format .= ",%A%f:%l: %m"
" It would be nice if this weren't necessary, but panic lines from tests are
" prefixed with a single leading tab, making them very similar to 2nd and
" later lines of a multi-line compiler error. Swallow it so that it doesn't
" cause a quickfix entry since the next %G entry can add a quickfix entry
" for 2nd and later lines of a multi-line compiler error.
let format .= ",%-C%\\tpanic: %.%#"
let format .= ",%G%\\t%m"
" Match and ignore everything else in multi-line messages.
let format .= ",%-C%.%#"
" Match and ignore everything else not in a multi-line message:
@ -425,4 +313,8 @@ function! s:errorformat() abort
return s:efm
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_GoTest() abort
let expected = [
\ {'lnum': 12, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'log message'},
@ -62,26 +66,33 @@ endfunc
func! Test_GoTestShowName() abort
let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'},
\ {'lnum': 6, 'bufnr': 9, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'},
\ {'lnum': 6, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld/sub'},
\ {'lnum': 9, 'bufnr': 9, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'},
\ {'lnum': 9, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'},
\ ]
let g:go_test_show_name=1
call s:test('showname/showname_test.go', expected)
let g:go_test_show_name=0
unlet g:go_test_show_name
endfunc
func! Test_GoTestVet() abort
let expected = [
\ {'lnum': 6, 'bufnr': 10, 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'},
\ ]
call s:test('veterror/veterror.go', expected)
endfunc
func! Test_GoTestTestCompilerError() abort
let expected = [
\ {'lnum': 10, 'bufnr': 8, 'col': 16, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'cannot use r (type struct {}) as type io.Reader in argument to ioutil.ReadAll:'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'struct {} does not implement io.Reader (missing Read method)'}
\ ]
call s:test('testcompilerror/testcompilerror_test.go', expected)
endfunc
func! s:test(file, expected, ...) abort
if has('nvim')
" nvim mostly shows test errors correctly, but the the expected errors are
" slightly different; buffer numbers are not the same and stderr doesn't
" seem to be redirected to the job, so the lines from the panic aren't in
" the output to be parsed, and hence are not in the quickfix lists. Once
" those two issues are resolved, this early return should be removed so
" the tests will run for Neovim, too.
return
endif
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test'
silent exe 'e ' . $GOPATH . '/src/' . a:file
@ -94,7 +105,7 @@ func! s:test(file, expected, ...) abort
endif
" run the tests
call call(function('go#test#Test'), args)
silent call call(function('go#test#Test'), args)
let actual = getqflist()
let start = reltime()
@ -118,4 +129,8 @@ func! s:normalize_durations(str) abort
return substitute(a:str, '[0-9]\+\(\.[0-9]\+\)\?s', '0.000s', 'g')
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,48 +1,132 @@
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
if !exists("g:go_textobj_include_variable")
let g:go_textobj_include_variable = 1
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" ( ) motions
" { } motions
" s for sentence
" p for parapgrah
" p for paragraph
" < >
" t for tag
" Select a function in visual mode.
function! go#textobj#Function(mode) abort
let offset = go#util#OffsetCursor()
function! go#textobj#Comment(mode) abort
let l:fname = expand('%:p')
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
try
if &modified
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
let l:fname = l:tmpname
endif
let bin_path = go#path#CheckBinPath('motion')
if empty(bin_path)
let l:cmd = ['motion',
\ '-format', 'json',
\ '-file', l:fname,
\ '-offset', go#util#OffsetCursor(),
\ '-mode', 'comment',
\ ]
let [l:out, l:err] = go#util#Exec(l:cmd)
if l:err
call go#util#EchoError(l:out)
return
endif
finally
if exists("l:tmpname")
call delete(l:tmpname)
endif
endtry
let l:result = json_decode(l:out)
if type(l:result) != 4 || !has_key(l:result, 'comment')
return
endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset)
let command .= " -mode enclosing"
let l:info = l:result.comment
call cursor(l:info.startLine, l:info.startCol)
if g:go_textobj_include_function_doc
let command .= " -parse-comments"
" Adjust cursor to exclude start comment markers. Try to be a little bit
" clever when using multi-line '/*' markers.
if a:mode is# 'i'
" trim whitespace so matching below works correctly
let l:line = substitute(getline('.'), '^\s*\(.\{-}\)\s*$', '\1', '')
" //text
if l:line[:2] is# '// '
call cursor(l:info.startLine, l:info.startCol+3)
" // text
elseif l:line[:1] is# '//'
call cursor(l:info.startLine, l:info.startCol+2)
" /*
" text
elseif l:line =~# '^/\* *$'
call cursor(l:info.startLine+1, 0)
" /*
" * text
if getline('.')[:2] is# ' * '
call cursor(l:info.startLine+1, 4)
" /*
" *text
elseif getline('.')[:1] is# ' *'
call cursor(l:info.startLine+1, 3)
endif
" /* text
elseif l:line[:2] is# '/* '
call cursor(l:info.startLine, l:info.startCol+3)
" /*text
elseif l:line[:1] is# '/*'
call cursor(l:info.startLine, l:info.startCol+2)
endif
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
normal! v
" Exclude trailing newline.
if a:mode is# 'i'
let l:info.endCol -= 1
endif
call cursor(l:info.endLine, l:info.endCol)
" Exclude trailing '*/'.
if a:mode is# 'i'
let l:line = getline('.')
" text
" */
if l:line =~# '^ *\*/$'
call cursor(l:info.endLine - 1, len(getline(l:info.endLine - 1)))
" text */
elseif l:line[-3:] is# ' */'
call cursor(l:info.endLine, l:info.endCol - 3)
" text*/
elseif l:line[-2:] is# '*/'
call cursor(l:info.endLine, l:info.endCol - 2)
endif
endif
endfunction
" Select a function in visual mode.
function! go#textobj#Function(mode) abort
let l:fname = expand("%:p")
if &modified
let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname)
let l:fname = l:tmpname
endif
let l:cmd = ['motion',
\ '-format', 'vim',
\ '-file', l:fname,
\ '-offset', go#util#OffsetCursor(),
\ '-mode', 'enclosing',
\ ]
if go#config#TextobjIncludeFunctionDoc()
let l:cmd += ['-parse-comments']
endif
let [l:out, l:err] = go#util#Exec(l:cmd)
if l:err
call go#util#EchoError(out)
return
endif
@ -63,9 +147,9 @@ function! go#textobj#Function(mode) abort
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
if has_key(info, 'doc') && go#config#TextobjIncludeFunctionDoc()
call cursor(info.doc.line, info.doc.col)
elseif info['sig']['name'] == '' && g:go_textobj_include_variable
elseif info['sig']['name'] == '' && go#config#TextobjIncludeVariable()
" one liner anonymous functions
if info.lbrace.line == info.rbrace.line
" jump to first nonblack char, to get the correct column
@ -101,36 +185,28 @@ endfunction
" Get the location of the previous or next function.
function! go#textobj#FunctionLocation(direction, cnt) abort
let offset = go#util#OffsetCursor()
let fname = shellescape(expand("%:p"))
let l:fname = 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
let l:fname = l:tmpname
endif
let bin_path = go#path#CheckBinPath('motion')
if empty(bin_path)
return
let l:cmd = ['motion',
\ '-format', 'vim',
\ '-file', l:fname,
\ '-offset', go#util#OffsetCursor(),
\ '-shift', a:cnt,
\ '-mode', a:direction,
\ ]
if go#config#TextobjIncludeFunctionDoc()
let l:cmd += ['-parse-comments']
endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset)
let command .= ' -shift ' . a: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
let [l:out, l:err] = go#util#Exec(l:cmd)
if l:err
call go#util#EchoError(out)
return
endif
@ -190,7 +266,7 @@ function! go#textobj#FunctionJump(mode, direction) abort
endif
if a:mode == 'v' && a:direction == 'prev'
if has_key(info, 'doc') && g:go_textobj_include_function_doc
if has_key(info, 'doc') && go#config#TextobjIncludeFunctionDoc()
keepjumps call cursor(info.doc.line, 1)
else
keepjumps call cursor(info.func.line, 1)
@ -201,4 +277,8 @@ function! go#textobj#FunctionJump(mode, direction) abort
keepjumps call cursor(info.func.line, 1)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" From "go list -h".
function! go#tool#ValidFiles(...)
let l:list = ["GoFiles", "CgoFiles", "IgnoredGoFiles", "CFiles", "CXXFiles",
@ -36,8 +40,8 @@ function! go#tool#Files(...) abort
endif
endfor
let out = go#tool#ExecuteInDir('go list -f ' . shellescape(combined))
return split(out, '\n')
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:combined])
return split(l:out, '\n')
endfunction
function! go#tool#Deps() abort
@ -46,9 +50,8 @@ function! go#tool#Deps() abort
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')
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format])
return split(l:out, '\n')
endfunction
function! go#tool#Imports() abort
@ -58,188 +61,79 @@ function! go#tool#Imports() abort
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
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format])
if l:err != 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 [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}', l:package_path])
if l:err != 0
echo out
return imports
endif
let package_name = substitute(l:out, '\n$', '', '')
let imports[package_name] = package_path
endfor
return imports
endfunction
function! go#tool#Info(auto) abort
let l:mode = get(g:, 'go_info_mode', 'gocode')
function! go#tool#Info(showstatus) abort
let l:mode = go#config#InfoMode()
if l:mode == 'gocode'
call go#complete#Info(a:auto)
call go#complete#Info(a:showstatus)
elseif l:mode == 'guru'
call go#guru#DescribeInfo()
call go#guru#DescribeInfo(a:showstatus)
elseif l:mode == 'gopls'
call go#lsp#Info(a:showstatus)
else
call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [gocode, guru]')
call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [gocode, guru, gopls]')
endif
endfunction
function! go#tool#PackageName() abort
let command = "go list -f \"{{.Name}}\""
let out = go#tool#ExecuteInDir(command)
if go#util#ShellError() != 0
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}'])
if l:err != 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 !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
" Verify that the directory actually exists. If the directory does not
" exist, then assume that the a:cmd should not be executed. Callers expect
" to check v:shell_error (via go#util#ShellError()), so execute a command
" that will return an error as if a:cmd was run and exited with an error.
" This helps avoid errors when working with plugins that use virtual files
" that don't actually exist on the file system (e.g. vim-fugitive's
" GitDiff).
if !isdirectory(expand("%:p:h"))
let [out, err] = go#util#Exec(["false"])
return ''
endif
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
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) abort
let command = "go list ". a:importpath
let out = go#tool#ExecuteInDir(command)
if go#util#ShellError() != 0
let [l:out, l:err] = go#util#ExecInDir(['go', 'list', a:importpath])
if l:err != 0
return -1
endif
return 0
endfunction
" following two functions are from: https://github.com/mattn/gist-vim
" thanks @mattn
function! s:get_browser_command() abort
let go_play_browser_command = get(g:, 'go_play_browser_command', '')
if go_play_browser_command == ''
if go#util#IsWin()
let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%'
elseif go#util#IsMac()
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% &'
elseif executable('chromium')
let go_play_browser_command = 'chromium %URL% &'
else
let go_play_browser_command = ''
endif
endif
return go_play_browser_command
function! go#tool#DescribeBalloon()
let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p')
call go#lsp#Hover(l:fname, v:beval_lnum, v:beval_col, funcref('s:balloon', []))
return ''
endfunction
function! go#tool#OpenBrowser(url) abort
let cmd = s:get_browser_command()
if len(cmd) == 0
redraw
echohl WarningMsg
echo "It seems that you don't have general web browser. Open URL below."
echohl None
echo a:url
return
endif
if cmd =~ '^!'
let cmd = substitute(cmd, '%URL%', '\=escape(shellescape(a:url),"#")', 'g')
silent! exec cmd
elseif cmd =~ '^:[A-Z]'
let cmd = substitute(cmd, '%URL%', '\=escape(a:url,"#")', 'g')
exec cmd
function! s:balloon(msg)
let l:msg = a:msg
if has('balloon_eval')
if has('balloon_multiline')
let l:msg = join(a:msg, "\n")
else
let cmd = substitute(cmd, '%URL%', '\=shellescape(a:url)', 'g')
call go#util#System(cmd)
let l:msg = substitute(join(map(deepcopy(a:msg), 'substitute(v:val, "\t", "", "")'), '; '), '{;', '{', '')
endif
endif
call balloon_show(l:msg)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,8 +1,12 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_ExecuteInDir() abort
let l:tmp = gotest#write_file('a/a.go', ['package a'])
try
let l:out = go#tool#ExecuteInDir("pwd")
call assert_equal(l:tmp . "/src/a\n", l:out)
let l:out = go#util#ExecInDir(['pwd'])
call assert_equal([l:tmp . "/src/a\n", 0], l:out)
finally
call delete(l:tmp, 'rf')
endtry
@ -13,11 +17,15 @@ func! Test_ExecuteInDir_nodir() abort
exe ':e ' . l:tmp . '/new-dir/a'
try
let l:out = go#tool#ExecuteInDir("pwd")
call assert_equal('', l:out)
let l:out = go#util#ExecInDir(['pwd'])
call assert_equal(['', 1], l:out)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let s:buf_nr = -1
"OpenWindow opens a new scratch window and put's the content into the window
@ -111,4 +115,8 @@ function! go#ui#OpenDefinition(filter) abort
norm! zz
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,38 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#uri#Encode(value) abort
return s:encode(a:value, '[^A-Za-z0-9_.~-]')
endfunction
function! go#uri#EncodePath(value) abort
let l:separator = '/'
if go#util#IsWin()
let l:separator = '\\'
endif
return s:encode(a:value, '[^' . l:separator . 'A-Za-z0-9_.~-]')
endfunction
function! s:encode(value, unreserved)
return substitute(
\ a:value,
\ a:unreserved,
\ '\="%".printf(''%02X'', char2nr(submatch(0)))',
\ 'g'
\)
endfunction
function! go#uri#Decode(value) abort
return substitute(
\ a:value,
\ '%\(\x\x\)',
\ '\=nr2char(''0X'' . submatch(1))',
\ 'g'
\)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" PathSep returns the appropriate OS specific path separator.
function! go#util#PathSep() abort
if go#util#IsWin()
@ -48,7 +52,7 @@ function! go#util#IsMac() abort
return has('mac') ||
\ has('macunix') ||
\ has('gui_macvim') ||
\ go#util#System('uname') =~? '^darwin'
\ go#util#Exec(['uname'])[0] =~? '^darwin'
endfunction
" Checks if using:
@ -59,7 +63,20 @@ function! go#util#IsUsingCygwinShell()
return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*'
endfunction
function! go#util#has_job() abort
" Check if Vim jobs API is supported.
"
" The (optional) first parameter can be added to indicate the 'cwd' or 'env'
" parameters will be used, which wasn't added until a later version.
function! go#util#has_job(...) abort
if has('nvim')
return 1
endif
" cwd and env parameters to job_start was added in this version.
if a:0 > 0 && a:1 is 1
return has('job') && has("patch-8.0.0902")
endif
" 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")
@ -93,27 +110,34 @@ endfunction
" goarch returns 'go env GOARCH'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goarch")'
function! go#util#goarch() abort
return substitute(go#util#System('go env GOARCH'), '\n', '', 'g')
return substitute(s:exec(['go', 'env', 'GOARCH'])[0], '\n', '', 'g')
endfunction
" goos returns 'go env GOOS'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goos")'
function! go#util#goos() abort
return substitute(go#util#System('go env GOOS'), '\n', '', 'g')
return substitute(s:exec(['go', 'env', 'GOOS'])[0], '\n', '', 'g')
endfunction
" goroot returns 'go env GOROOT'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goroot")'
function! go#util#goroot() abort
return substitute(go#util#System('go env GOROOT'), '\n', '', 'g')
return substitute(s:exec(['go', 'env', 'GOROOT'])[0], '\n', '', 'g')
endfunction
" gopath returns 'go env GOPATH'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("gopath")'
function! go#util#gopath() abort
return substitute(go#util#System('go env GOPATH'), '\n', '', 'g')
return substitute(s:exec(['go', 'env', 'GOPATH'])[0], '\n', '', 'g')
endfunction
" gomod returns 'go env GOMOD'. gomod changes depending on the folder. Don't
" use go#util#env as it caches the value.
function! go#util#gomod() abort
return substitute(s:exec(['go', 'env', 'GOMOD'])[0], '\n', '', 'g')
endfunction
function! go#util#osarch() abort
return go#util#env("goos") . '_' . go#util#env("goarch")
endfunction
@ -124,12 +148,13 @@ endfunction
" so that we always use a standard POSIX-compatible Bourne shell (and not e.g.
" csh, fish, etc.) See #988 and #1276.
function! s:system(cmd, ...) abort
" Preserve original shell and shellredir values
" Preserve original shell, shellredir and shellcmdflag values
let l:shell = &shell
let l:shellredir = &shellredir
let l:shellcmdflag = &shellcmdflag
if !go#util#IsWin() && executable('/bin/sh')
set shell=/bin/sh shellredir=>%s\ 2>&1
set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c
endif
try
@ -138,6 +163,7 @@ function! s:system(cmd, ...) abort
" Restore original values
let &shell = l:shell
let &shellredir = l:shellredir
let &shellcmdflag = l:shellcmdflag
endtry
endfunction
@ -153,16 +179,47 @@ endfunction
function! go#util#Exec(cmd, ...) abort
if len(a:cmd) == 0
call go#util#EchoError("go#util#Exec() called with empty a:cmd")
return
return ['', 1]
endif
let l:bin = a:cmd[0]
" Lookup the full path, respecting settings such as 'go_bin_path'. On errors,
" CheckBinPath will show a warning for us.
let l:bin = go#path#CheckBinPath(a:cmd[0])
let l:bin = go#path#CheckBinPath(l:bin)
if empty(l:bin)
return ["", 1]
return ['', 1]
endif
let l:out = call('s:system', [go#util#Shelljoin([l:bin] + a:cmd[1:])] + a:000)
" Finally execute the command using the full, resolved path. Do not pass the
" unmodified command as the correct program might not exist in $PATH.
return call('s:exec', [[l:bin] + a:cmd[1:]] + a:000)
endfunction
function! go#util#ExecInDir(cmd, ...) abort
if !isdirectory(expand("%:p:h"))
return ['', 1]
endif
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd()
try
execute cd . fnameescape(expand("%:p:h"))
let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000)
finally
execute cd . fnameescape(l:dir)
endtry
return [l:out, l:err]
endfunction
function! s:exec(cmd, ...) abort
let l:bin = a:cmd[0]
let l:cmd = go#util#Shelljoin([l:bin] + a:cmd[1:])
if go#util#HasDebug('shell-commands')
call go#util#EchoInfo('shell command: ' . l:cmd)
endif
let l:out = call('s:system', [l:cmd] + a:000)
return [l:out, go#util#ShellError()]
endfunction
@ -259,7 +316,7 @@ 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_addtags_transform', "snakecase")
let l:snippet_case = go#config#AddtagsTransform()
if l:snippet_case == "snakecase"
return go#util#snakecase(a:word)
elseif l:snippet_case == "camelcase"
@ -397,7 +454,73 @@ endfunction
" Report if the user enabled a debug flag in g:go_debug.
function! go#util#HasDebug(flag)
return index(get(g:, 'go_debug', []), a:flag) >= 0
return index(go#config#Debug(), a:flag) >= 0
endfunction
function! go#util#OpenBrowser(url) abort
let l:cmd = go#config#PlayBrowserCommand()
if len(l:cmd) == 0
redraw
echohl WarningMsg
echo "It seems that you don't have general web browser. Open URL below."
echohl None
echo a:url
return
endif
" if setting starts with a !.
if l:cmd =~ '^!'
let l:cmd = substitute(l:cmd, '%URL%', '\=escape(shellescape(a:url), "#")', 'g')
silent! exec l:cmd
elseif cmd =~ '^:[A-Z]'
let l:cmd = substitute(l:cmd, '%URL%', '\=escape(a:url,"#")', 'g')
exec l:cmd
else
let l:cmd = substitute(l:cmd, '%URL%', '\=shellescape(a:url)', 'g')
call go#util#System(l:cmd)
endif
endfunction
function! go#util#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": substitute(line, '\r$', '', '')})
endif
endif
endfor
return errors
endfunction
function! go#util#ShowInfo(info)
if empty(a:info)
return
endif
echo "vim-go: " | echohl Function | echon a:info | echohl None
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" Write a Go file to a temporary directory and append this directory to $GOPATH.
"
" The file will written to a:path, which is relative to the temporary directory,
@ -127,4 +131,8 @@ func! gotest#assert_quickfix(got, want) abort
endwhile
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -28,7 +28,7 @@ function! s:source.gather_candidates(args, context) abort
return []
endif
let l:include = get(g:, 'go_decls_includes', 'func,type')
let l:include = go#config#DeclsIncludes()
let l:command = printf('%s -format vim -mode decls -include %s -%s %s', l:bin_path, l:include, l:mode, shellescape(l:path))
let l:candidates = []
try

View file

@ -9,6 +9,10 @@ if exists("g:current_compiler")
endif
let g:current_compiler = "go"
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
if exists(":CompilerSet") != 2
command -nargs=* CompilerSet setlocal <args>
endif
@ -38,4 +42,8 @@ CompilerSet errorformat+=%-G%.%# " All lines not matching a
let &cpo = s:save_cpo
unlet s:save_cpo
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -38,11 +38,11 @@ and individual features can be toggled easily. vim-go leverages a number of
tools developed by the Go community to provide a seamless Vim experience.
* Compile your package with |:GoBuild|, install it with |:GoInstall| or
test it with |:GoTest|. Run a single tests with |:GoTestFunc|).
test it with |:GoTest|. Run a single test with |:GoTestFunc|).
* Quickly execute your current file(s) with |:GoRun|.
* Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with |:GoDebugStart|.
* Completion support via `gocode`.
* Completion support via `gocode` and `gopls`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with |:GoDef|.
* Look up documentation with |:GoDoc| or |:GoDocBrowser|.
@ -76,6 +76,12 @@ tools developed by the Go community to provide a seamless Vim experience.
==============================================================================
INSTALL *go-install*
vim-go requires at least Vim 7.4.2009 or Neovim 0.3.1. On macOS, if you are
still using your system version of vim, you can use homebrew to keep your
version of Vim up-to-date with the following terminal command:
>
brew install vim
The latest stable release, https://github.com/fatih/vim-go/releases/latest, is
the recommended version to use. If you choose to use the master branch
instead, please do so with caution; it is a _development_ branch.
@ -106,12 +112,13 @@ manager's install command.
<
* https://github.com/gmarik/vundle >
Plugin 'fatih/vim-go'
Plugin 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
<
* Manual (not recommended) >
Copy all of the files into your `~/.vim` directory
<
You will also need to install all the necessary binaries. vim-go makes it easy
to install all of them by providing a command, |:GoInstallBinaries|, to
`go get` all the required binaries. The binaries will be installed to $GOBIN
@ -131,8 +138,12 @@ The following plugins are supported for use with vim-go:
https://github.com/Shougo/neocomplete.vim
* Real-time completion (Neovim and Vim 8):
https://github.com/Shougo/deoplete.nvim and
https://github.com/zchee/deoplete-go
https://github.com/Shougo/deoplete.nvim
Add the following line to your vimrc. This instructs deoplete to use omni
completion for Go files.
call deoplete#custom#option('omni_patterns', { 'go': '[^. *\t]\.\w*' })
* Display source code navigation in a sidebar:
https://github.com/majutsushi/tagbar
@ -142,12 +153,6 @@ The following plugins are supported for use with vim-go:
https://github.com/SirVer/ultisnips or
https://github.com/joereynolds/vim-minisnip
* For a better documentation viewer check out:
https://github.com/garyburd/go-explorer
* Integration with `delve` (Neovim only):
https://github.com/jodosha/vim-godebug
* Interactive |:GoDecls| and |:GoDeclsDir|:
https://github.com/ctrlpvim/ctrlp.vim or
https://github.com/junegunn/fzf.vim or
@ -157,6 +162,11 @@ The following plugins are supported for use with vim-go:
==============================================================================
COMMANDS *go-commands*
*:GoReportGitHubIssue*
:GoReportGitHubIssue
GoReportGitHubIssue opens the default browser and starts a new bug report
with useful system information.
*:GoPath*
:GoPath [path]
@ -187,11 +197,13 @@ COMMANDS *go-commands*
displayed and the buffer will be untouched.
*:GoLint*
:GoLint [packages]
:GoLint! [packages]
Run golint for the directory under your current file, or for the given
packages.
If [!] is not given the first error is jumped to.
*:GoDoc*
:GoDoc [word]
@ -249,7 +261,7 @@ CTRL-]
g<C-LeftMouse>
<C-LeftMouse>
Goto declaration/definition for the declaration under the cursor. By
Go to declaration/definition for the identifier under the cursor. By
default the CTRL-] shortcut, the mapping `gd` and <C-LeftMouse>,
g<LeftMouse> are enabled to invoke :GoDef for the identifier under the
cursor. See |'g:go_def_mapping_enabled'| to disable them. No explicit
@ -261,6 +273,14 @@ g<C-LeftMouse>
list of file locations you have visited with :GoDef that is retained to
help you navigate software.
The per-window location stack is shared with |:GoDefType|.
*:GoDefType*
:GoDefType
Go to type definition for the identifier under the cursor.
The per-window location stack is shared with |:GoDef|.
*:GoDefStack*
:GoDefStack [number]
@ -455,7 +475,7 @@ CTRL-t
If [!] is not given the first error is jumped to.
*:GoErrCheck*
:GoErrCheck [options]
:GoErrCheck! [options]
Check for unchecked errors in you current package. Errors are populated in
the quickfix window.
@ -463,6 +483,8 @@ CTRL-t
You may optionally pass any valid errcheck flags/options. See
`errcheck -h` for a full list.
If [!] is not given the first error is jumped to.
*:GoFiles*
:GoFiles [source_files]
@ -629,7 +651,7 @@ CTRL-t
disabled it clears and stops automatic highlighting.
*:GoMetaLinter*
:GoMetaLinter [path]
:GoMetaLinter! [path]
Calls the underlying `gometalinter` tool and displays all warnings and
errors in the |quickfix| window. By default the following linters are
@ -638,16 +660,18 @@ CTRL-t
use the variable |'g:go_metalinter_command'|. To override the maximum
linters execution time use |'g:go_metalinter_deadline'| variable.
If [!] is not given the first error is jumped to.
*:GoBuildTags*
:GoBuildTags [tags]
Changes the build tags for various commands. If you have any file that
uses a custom build tag, such as `//+build integration` , this command can
be used to pass it to all tools that accepts tags, such as guru, gorename,
etc..
uses a custom build tag, such as `// +build integration` , this command
can be used to pass it to all tools that accepts tags, such as guru,
gorename, etc.
The build tags is cleared (unset) if `""` is given. If no arguments is
given it prints the current custom build tags.
The build tags is cleared (unset) if `""` is given. If no arguments are
given it prints the current build tags.
*:AsmFmt*
:AsmFmt
@ -676,6 +700,12 @@ CTRL-t
\| command! -bang AS call go#alternate#Switch(<bang>0, 'split')
augroup END
<
*:GoPointsTo*
:GoPointsTo
Show all variables to which the pointer under the cursor may point to.
*:GoWhicherrs*
:GoWhicherrs
@ -762,7 +792,7 @@ CTRL-t
*:GoRemoveTags*
:[range]GoRemoveTags [key],[option] [key1],[option1] ...
Rmove field tags for the fields of a struct. If called inside a struct it
Remove field tags for the fields of a struct. If called inside a struct it
automatically remove all field tags. An error message is given if it's
called outside a struct definition or if the file is not correctly
formatted
@ -789,6 +819,11 @@ CTRL-t
Toggles |'g:go_fmt_autosave'|.
*:GoModFmtAutoSaveToggle*
:GoModFmtAutoSaveToggle
Toggles |'g:go_mod_fmt_autosave'|.
*:GoAsmFmtAutoSaveToggle*
:GoAsmFmtAutoSaveToggle
@ -842,6 +877,34 @@ CTRL-t
}
<
*:GoIfErr*
:GoIfErr
Generate if err != nil { return ... } automatically which infer the type
of return values and the numbers.
For example:
>
func doSomething() (string, error) {
f, err := os.Open("file")
}
<
Becomes:
>
func doSomething() (string, error) {
f, err := os.Open("file")
if err != nil {
return "", err
}
}
<
*:GoModFmt*
:GoModFmt
Filter the current go.mod buffer through "go mod edit -fmt" command. It
tries to preserve cursor position and avoids replacing the buffer with
stderr output.
==============================================================================
MAPPINGS *go-mappings*
@ -1030,6 +1093,10 @@ Show send/receive corresponding to selected channel op
Show all refs to entity denoted by selected identifier
*(go-pointsto)*
Show all variables to which the pointer under the cursor may point to.
*(go-metalinter)*
Calls `go-metalinter` for the current directory
@ -1050,6 +1117,14 @@ Alternates between the implementation and test code in a new vertical split
Calls `:GoImport` for the current package
*(go-iferr)*
Generate if err != nil { return ... } automatically which infer the type of
return values and the numbers.
*(go-mod-fmt)*
Calls |:GoModFmt| for the current buffer
==============================================================================
TEXT OBJECTS *go-text-objects*
@ -1070,6 +1145,12 @@ if "inside a function", select contents of a function,
excluding the function definition and the closing bracket. This
text-object also supports literal functions
*go-v_ac* *go-ac*
ac "a comment", select contents of the current comment block.
*go-v_ic* *go-ic*
ic "inner comment", select contents of the current comment block,
excluding the start and end comment markers.
vim-go also defines the following text motion objects:
@ -1090,10 +1171,10 @@ FUNCTIONS *go-functions*
*go#statusline#Show()*
Shows the status of a job running asynchronously. Can be used to plug into the
statusline. It works to show the status per package instead of per
file. Assume you have three files open, all belonging to the same package, if
the package build (`:GoBuild`) is successful, all statusline's will show
`success`, if you it fails all file's statusline will show `failed`.
statusline. It works to show the status per package instead of per file.
Assume you have three files open, all belonging to the same package, if the
package build (`:GoBuild`) is successful, all statuslines will show `success`,
if it fails all windows' statuslines will show `failed`.
To avoid always showing old status information, the status information is
cleaned for each package after `60` seconds. This can be changed with the
@ -1104,6 +1185,21 @@ cleaned for each package after `60` seconds. This can be changed with the
Returns the description of the identifer under the cursor. Can be used to plug
into the statusline.
*go#complete#Complete()*
Uses `gopls` for autocompletion. By default, it is hooked up to |'omnifunc'|
for Vim8 and Neovim.
*go#complete#GocodeComplete()*
Uses `gocode` for autocompletion. By default, it is hooked up to |'omnifunc'|
for Vim 7.4.
*go#tool#DescribeBalloon()*
Suitable to be used as an expression to show the evaluation balloon. See `help
balloonexpr`.
==============================================================================
SETTINGS *go-settings*
@ -1156,8 +1252,8 @@ updated. By default it's disabled. The delay can be configured with the
Use this option to define the command to be used for |:GoInfo|. By default
`gocode` is being used as it's the fastest option. But one might also use
`guru` as it's covers more cases and is more accurate. Current valid options
are: `[gocode, guru]` >
`gopls` or `guru` as they cover more cases and are more accurate. Current
valid options are: `[gocode, guru, gopls]` >
let g:go_info_mode = 'gocode'
<
@ -1231,11 +1327,19 @@ fails. By default the location list is shown. >
Use this option to enable fmt's experimental mode. This experimental mode is
superior to the current mode as it fully saves the undo history, so undo/redo
doesn't break. However it's slows (creates/deletes a file for every save) and
doesn't break. However, it's slow (creates/deletes a file for every save) and
it's causing problems on some Vim versions. By default it's disabled. >
let g:go_fmt_experimental = 0
<
*'g:go_mod_fmt_autosave'*
Use this option to auto |:GoModFmt| on save. By default it's enabled >
let g:go_mod_fmt_autosave = 1
<
*'g:go_doc_keywordprg_enabled'*
Use this option to run `godoc` on words under the cursor with |K|; this will
@ -1264,7 +1368,7 @@ a private internal service. Default is 'https://godoc.org'.
Use this option to define the command to be used for |:GoDef|. By default
`guru` is being used as it covers all edge cases. But one might also use
`godef` as it's faster. Current valid options are: `[guru, godef]` >
`godef` as it's faster. Current valid options are: `[guru, godef, gopls]` >
let g:go_def_mode = 'guru'
<
@ -1284,21 +1388,26 @@ mappings of |:GoDef|. By default it's disabled. >
let g:go_def_reuse_buffer = 0
<
*'g:go_doc_command'*
Command to use for |:GoDoc|; only used when invoked with a package name. The
`gogetdoc` command is always used when |:GoDoc| is used on the identifier
under the cursor (i.e. without argument or from |K|). >
let g:go_doc_command = ["godoc"]
< *'g:go_bin_path'*
*'g:go_bin_path'*
Use this option to change default path for vim-go tools when using
|:GoInstallBinaries| and |:GoUpdateBinaries|. If not set `$GOBIN` or
`$GOPATH/bin` is used. >
let g:go_bin_path = ""
<
*'g:go_search_bin_path_first'*
This option lets |'g:go_bin_path'| (or its default value) take precedence over
$PATH when invoking a tool command such as |:GoFmt| or |:GoImports|.
Enabling this option ensures that the binaries installed via
|:GoInstallBinaries| and |:GoUpdateBinaries| are the same ones that are
invoked via the tool commands.
By default it is enabled. >
let g:go_search_bin_path_first = 1
<
*'g:go_snippet_engine'*
@ -1336,10 +1445,10 @@ By default it's not set, so the relevant commands defaults are being used.
<
*'g:go_build_tags'*
These options that will be automatically passed to the `-tags` option of
various tools, such as `guru`, `gorename`, etc... This is a permanent
setting. A more useful way is to use |:GoBuildTags| to dynamically change or
remove build tags. By default it's not set.
Space-separated list of build tags passed to the `-tags` flag of tools that
support it.
There is also the |:GoBuildTags| convenience command to change or remove build
tags.
>
let g:go_build_tags = ''
<
@ -1407,11 +1516,12 @@ it's empty
<
*'g:go_metalinter_command'*
Overrides the command to be executed when |:GoMetaLinter| is called. This is
an advanced settings and is for users who want to have a complete control
over how `gometalinter` should be executed. By default it's empty.
Overrides the command to be executed when |:GoMetaLinter| is called. By
default it's `gometalinter`. `golangci-lint` is also supported. It can also be
used as an advanced setting for users who want to have more control over
the metalinter.
>
let g:go_metalinter_command = ""
let g:go_metalinter_command = "gometalinter"
<
*'g:go_metalinter_deadline'*
@ -1445,10 +1555,10 @@ that was called. Supported values are "", "quickfix", and "locationlist".
Specifies the type of list to use for command outputs (such as errors from
builds, results from static analysis commands, etc...). When an expected key
is not present in the dictionary, |'g:go_list_type'| will be used instead.
Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint",
"GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for both
:GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". Supported
values for each command are "quickfix" and "locationlist".
Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoModFmt", "GoInstall",
"GoLint", "GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for
both :GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest".
Supported values for each command are "quickfix" and "locationlist".
>
let g:go_list_type_commands = {}
<
@ -1522,14 +1632,6 @@ same.
let g:go_gorename_prefill = 'expand("<cword>") =~# "^[A-Z]"' .
\ '? go#util#pascalcase(expand("<cword>"))' .
\ ': go#util#camelcase(expand("<cword>"))'
<
*'g:go_gocode_autobuild'*
Specifies whether `gocode` should automatically build out-of-date packages
when their source fields are modified, in order to obtain the freshest
autocomplete results for them. By default it is enabled.
>
let g:go_gocode_autobuild = 1
<
*'g:go_gocode_propose_builtins'*
@ -1537,6 +1639,14 @@ Specifies whether `gocode` should add built-in types, functions and constants
to an autocompletion proposals. By default it is enabled.
>
let g:go_gocode_propose_builtins = 1
<
*'g:go_gocode_propose_source'*
Specifies whether `gocode` should use source files instead of binary packages
for autocompletion proposals. When disabled, only identifiers from the current
package and packages that have been installed will proposed.
>
let g:go_gocode_propose_source = 0
<
*'g:go_gocode_unimported_packages'*
@ -1666,9 +1776,11 @@ A list of options to debug; useful for development and/or reporting bugs.
Currently accepted values:
shell-commands Echo all shell commands that vim-go runs.
debugger-state Expose debugger state in 'g:go_debug_diag'.
debugger-commands Echo communication between vim-go and `dlv`; requests and
responses are recorded in `g:go_debug_commands`.
lsp Record lsp requests and responses in g:go_lsp_log.
>
let g:go_debug = []
<
@ -1750,13 +1862,13 @@ Highlight function and method declarations.
>
let g:go_highlight_functions = 0
<
*'g:go_highlight_function_arguments'*
*'g:go_highlight_function_parameters'*
Highlight the variable names in arguments and return values in function
declarations. Setting this implies the functionality from
Highlight the variable names in parameters (including named return parameters)
in function declarations. Setting this implies the functionality from
|'g:go_highlight_functions'|.
>
let g:go_highlight_function_arguments = 0
let g:go_highlight_function_parameters = 0
<
*'g:go_highlight_function_calls'*
@ -1828,6 +1940,13 @@ filetype.
The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the
`gotexttmpl` is never automatically set and needs to be set manually.
==============================================================================
*gomod* *ft-gomod-syntax*
go.mod file syntax~
The `gomod` 'filetype' provides syntax highlighting for Go's module file
`go.mod`
==============================================================================
DEBUGGER *go-debug*
@ -1847,10 +1966,10 @@ features:
* Toggle breakpoint.
* Stack operation continue/next/step out.
This feature requires Vim 8.0.0087 or newer with the |+job| feature. Neovim
does _not_ work (yet).
This requires Delve 1.0.0 or newer, and it is recommended to use Go 1.10 or
newer, as its new caching will speed up recompiles.
This feature requires either Vim 8.0.0087 or newer with the |+job| feature or
Neovim. This features also requires Delve 1.0.0 or newer, and it is
recommended to use Go 1.10 or newer, as its new caching will speed up
recompiles.
*go-debug-intro*
GETTING STARTED WITH THE DEBUGGER~
@ -2035,6 +2154,24 @@ Defaults to `127.0.0.1:8181`:
let g:go_debug_address = '127.0.0.1:8181'
<
*'g:go_debug_log_output'*
Specifies log output options for `dlv`. Value should be a single string
of comma-separated options suitable for passing to `dlv`. An empty string
(`''`) will suppress logging entirely.
Default: `'debugger, rpc'`:
>
let g:go_debug_log = 'debugger, rpc'
<
*'g:go_highlight_debug'*
Highlight the current line and breakpoints in the debugger.
>
let g:go_highlight_debug = 1
<
==============================================================================
FAQ TROUBLESHOOTING *go-troubleshooting*
@ -2067,7 +2204,7 @@ Many vim-go commands use the `guru` commandline tool to get information. Some
a reasonable amount of performance `guru` limits this analysis to a selected
list of packages. This is known as the "guru scope".
The default is to use the package the curent buffer belongs to, but this may
The default is to use the package the current buffer belongs to, but this may
not always be correct. For example for the file `guthub.com/user/pkg/a/a.go`
the scope will be set to `github.com/user/pkg/a`, but you probably want
`github.com/user/pkg`
@ -2093,11 +2230,22 @@ Also see |:GoGuruScope| and |'g:go_guru_scope'|.
Vim becomes slow while editing Go files~
This is usually caused by `g:go_highlight_*` options. Try disabling them if
you've enabled some of them.
The most common cause for this is using an older version of Vim that doesn't
support asynchronous jobs. |'g:go_auto_sameids'| and |'g:go_auto_type_info'|
run jobs that can cause noticable delays when used with vim74. The problem is
most pronounced on vim74, but can occur on vim8 and nvim. On vim8 and nvim,
the problem should be restricted to a short period when the first buffer in a
package is first loaded.
Other common culprits are |'g:go_auto_sameids'| and |go#statusline#Show()|.
If you see unexpected characters rendered in the current window, the problem
is most likely due to |'g:go_auto_sameids'| or |'g:go_auto_type_info'|. First,
try using another mode for |'g:go_info_mode'|. If that doesn't work, try
disabling |'g:go_auto_sameids'| and |'g:go_auto_type_info'|.
To a lesser extent, this can be caused by `g:go_highlight_*` options. If Vim
is just slower than normal, but doesn't render unexpected characters in the
currrent window, then the problem is most likely the `g:go_highlight_*`
options. Try disabling them if you've enabled some of them.
I get errors when using GoInstallBinaries~
@ -2165,9 +2313,9 @@ Using with NeoVim~
Note: Neovim currently is not a first class citizen for vim-go. You are free
to open bug, however I'm not using Neovim so it's hard for me to test it.
vim-go might not work well as good as in Vim. I'm happy to accept pull
requests or very detailed bug reports. If you're interested to improve the
state of Neovim in vim-go you're always welcome!
vim-go might not work as well in Neovim as it does in Vim. I'm happy to accept
pull requests or very detailed bug reports. If you're interested to improve
the state of Neovim in vim-go you're always welcome!
Run `:GoRun` in a new tab, horizontal split or vertical split terminal
>
@ -2180,6 +2328,18 @@ By default new terminals are opened in a vertical split. To change it
let g:go_term_mode = "split"
>
How can I customize the highlighting?~
All the highlight groups used by vim-go are prefixed with `go` (e.g.
`goType`) and are defined in the files in the `syntax` directory. To change
the highlighting for any group, add a `highlight` command for the group to
your vimrc. To turn off the highlighting for any group, add `highlight link
group-name NONE` (where `group-name` is the name of the group whose highlight
you'd like to turn off) to your vimrc.
Some people may wish to highlight Go's builtins as keywords. To do so, one
should simply add `highlight link goBuiltins Keyword` to the `vimrc` file.
==============================================================================
DEVELOPMENT *go-development*
@ -2215,14 +2375,14 @@ You can install and test all Vim versions by running `make`.
DONATION *go-donation*
People have asked for this for a long time, now you can be a fully supporter
by being a patreon at: https://www.patreon.com/fatih
by being a patreon at: https://www.patreon.com/bhcleek
By being a patron, you are enabling vim-go to grow and mature, helping me to
invest in bug fixes, new documentation, and improving both current and future
features. It's completely optional and is just a direct way to support
vim-go's ongoing development. Thanks!
Check it out: https://www.patreon.com/fatih
Check it out: https://www.patreon.com/bhcleek
==============================================================================

View file

@ -1,34 +1,40 @@
" vint: -ProhibitAutocmdWithNoGroup
" We take care to preserve the user's fileencodings and fileformats,
" because those settings are global (not buffer local), yet we want
" to override them for loading Go files, which are defined to be UTF-8.
let s:current_fileformats = ''
let s:current_fileencodings = ''
" define fileencodings to open as utf-8 encoding even if it's ascii.
function! s:gofiletype_pre(type)
let s:current_fileformats = &g:fileformats
let s:current_fileencodings = &g:fileencodings
set fileencodings=utf-8 fileformats=unix
let &l:filetype = a:type
endfunction
" restore fileencodings as others
function! s:gofiletype_post()
let &g:fileformats = s:current_fileformats
let &g:fileencodings = s:current_fileencodings
endfunction
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" Note: should not use augroup in ftdetect (see :help ftdetect)
au BufNewFile *.go setfiletype go | setlocal fileencoding=utf-8 fileformat=unix
au BufRead *.go call s:gofiletype_pre("go")
au BufReadPost *.go call s:gofiletype_post()
au BufRead,BufNewFile *.go setfiletype go
au BufRead,BufNewFile *.s setfiletype asm
au BufRead,BufNewFile *.tmpl setfiletype gohtmltmpl
au BufNewFile *.s setfiletype asm | setlocal fileencoding=utf-8 fileformat=unix
au BufRead *.s call s:gofiletype_pre("asm")
au BufReadPost *.s call s:gofiletype_post()
" remove the autocommands for modsim3, and lprolog files so that their
" highlight groups, syntax, etc. will not be loaded. *.MOD is included, so
" that on case insensitive file systems the module2 autocmds will not be
" executed.
au! BufRead,BufNewFile *.mod,*.MOD
" Set the filetype if the first non-comment and non-blank line starts with
" 'module <path>'.
au BufRead,BufNewFile go.mod call s:gomod()
au BufRead,BufNewFile *.tmpl set filetype=gohtmltmpl
fun! s:gomod()
for l:i in range(1, line('$'))
let l:l = getline(l:i)
if l:l ==# '' || l:l[:1] ==# '//'
continue
endif
if l:l =~# '^module .\+'
setfiletype gomod
endif
break
endfor
endfun
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -5,7 +5,12 @@ if exists("b:did_ftplugin")
endif
let b:did_ftplugin = 1
let b:undo_ftplugin = "setl fo< com< cms<"
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let b:undo_ftplugin = "setl fo< com< cms<
\ | exe 'au! vim-go-asm-buffer * <buffer>'"
setlocal formatoptions-=t
@ -16,4 +21,17 @@ setlocal noexpandtab
command! -nargs=0 AsmFmt call go#asmfmt#Format()
" Autocommands
" ============================================================================
augroup vim-go-asm-buffer
autocmd! * <buffer>
autocmd BufWritePre <buffer> call go#auto#asmfmt_autosave()
augroup end
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -9,7 +9,12 @@ if exists("b:did_ftplugin")
endif
let b:did_ftplugin = 1
let b:undo_ftplugin = "setl fo< com< cms<"
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let b:undo_ftplugin = "setl fo< com< cms<
\ | exe 'au! vim-go-buffer * <buffer>'"
setlocal formatoptions-=t
@ -20,8 +25,11 @@ setlocal noexpandtab
compiler go
" Set gocode completion
" Set autocompletion
setlocal omnifunc=go#complete#Complete
if !go#util#has_job()
setlocal omnifunc=go#complete#GocodeComplete
endif
if get(g:, "go_doc_keywordprg_enabled", 1)
" keywordprg doesn't allow to use vim commands, override it
@ -35,18 +43,24 @@ if get(g:, "go_def_mapping_enabled", 1)
nnoremap <buffer> <silent> <C-]> :GoDef<cr>
nnoremap <buffer> <silent> <C-LeftMouse> <LeftMouse>:GoDef<cr>
nnoremap <buffer> <silent> g<LeftMouse> <LeftMouse>:GoDef<cr>
nnoremap <buffer> <silent> <C-w><C-]> :<C-u>call go#def#Jump("split")<CR>
nnoremap <buffer> <silent> <C-w>] :<C-u>call go#def#Jump("split")<CR>
nnoremap <buffer> <silent> <C-w><C-]> :<C-u>call go#def#Jump("split", 0)<CR>
nnoremap <buffer> <silent> <C-w>] :<C-u>call go#def#Jump("split", 0)<CR>
nnoremap <buffer> <silent> <C-t> :<C-U>call go#def#StackPop(v:count1)<cr>
endif
if get(g:, "go_textobj_enabled", 1)
onoremap <buffer> <silent> af :<c-u>call go#textobj#Function('a')<cr>
onoremap <buffer> <silent> if :<c-u>call go#textobj#Function('i')<cr>
xnoremap <buffer> <silent> af :<c-u>call go#textobj#Function('a')<cr>
onoremap <buffer> <silent> if :<c-u>call go#textobj#Function('i')<cr>
xnoremap <buffer> <silent> if :<c-u>call go#textobj#Function('i')<cr>
onoremap <buffer> <silent> ac :<c-u>call go#textobj#Comment('a')<cr>
xnoremap <buffer> <silent> ac :<c-u>call go#textobj#Comment('a')<cr>
onoremap <buffer> <silent> ic :<c-u>call go#textobj#Comment('i')<cr>
xnoremap <buffer> <silent> ic :<c-u>call go#textobj#Comment('i')<cr>
" Remap ]] and [[ to jump betweeen functions as they are useless in Go
nnoremap <buffer> <silent> ]] :<c-u>call go#textobj#FunctionJump('n', 'next')<cr>
nnoremap <buffer> <silent> [[ :<c-u>call go#textobj#FunctionJump('n', 'prev')<cr>
@ -58,76 +72,60 @@ if get(g:, "go_textobj_enabled", 1)
xnoremap <buffer> <silent> [[ :<c-u>call go#textobj#FunctionJump('v', 'prev')<cr>
endif
if get(g:, "go_auto_type_info", 0) || get(g:, "go_auto_sameids", 0)
if go#config#AutoTypeInfo() || go#config#AutoSameids()
let &l:updatetime= get(g:, "go_updatetime", 800)
endif
" NOTE(arslan): experimental, disabled by default, doesn't work well. No
" documentation as well. If anyone feels adventurous, enable the following and
" try to search for Go identifiers ;)
" Autocommands
" ============================================================================
"
" if get(g:, "go_sameid_search_enabled", 0)
" autocmd FileType go nnoremap <buffer> <silent> * :<c-u>call Sameids_search(0)<CR>
" autocmd FileType go nnoremap <buffer> <silent> # :<c-u>call Sameids_search(1)<CR>
" autocmd FileType go nnoremap <buffer> <silent> n :<c-u>call Sameids_repeat(0)<CR>
" autocmd FileType go nnoremap <buffer> <silent> N :<c-u>call Sameids_repeat(1)<CR>
" autocmd FileType go cabbrev nohlsearch <C-r>=Sameids_nohlsearch()<CR>
" endif
augroup vim-go-buffer
autocmd! * <buffer>
" " mode 0: next 1: prev
" function! Sameids_repeat(mode)
" let matches = getmatches()
" if empty(matches)
" return
" endif
" let cur_offset = go#util#OffsetCursor()
" The file is registered (textDocument/DidOpen) with gopls in in
" plugin/go.vim on the FileType event.
" TODO(bc): handle all the other events that may be of interest to gopls,
" too (e.g. BufFilePost , CursorHold , CursorHoldI, FileReadPost,
" StdinReadPre, BufWritePost, TextChange, TextChangedI)
if go#util#has_job()
autocmd BufWritePost <buffer> call go#lsp#DidChange(expand('<afile>:p'))
autocmd FileChangedShell <buffer> call go#lsp#DidChange(expand('<afile>:p'))
autocmd BufDelete <buffer> call go#lsp#DidClose(expand('<afile>:p'))
endif
" " reverse list to make it easy to find the prev occurrence
" if a:mode
" call reverse(matches)
" endif
autocmd CursorHold <buffer> call go#auto#auto_type_info()
autocmd CursorHold <buffer> call go#auto#auto_sameids()
" for m in matches
" if !has_key(m, "group")
" return
" endif
" Echo the identifier information when completion is done. Useful to see
" the signature of a function, etc...
if exists('##CompleteDone')
autocmd CompleteDone <buffer> call go#auto#echo_go_info()
endif
" if m.group != "goSameId"
" return
" endif
autocmd BufWritePre <buffer> call go#auto#fmt_autosave()
autocmd BufWritePost <buffer> call go#auto#metalinter_autosave()
" let offset = go#util#Offset(m.pos1[0], m.pos1[1])
" clear SameIds when the buffer is unloaded so that loading another buffer
" in the same window doesn't highlight the most recently matched
" identifier's positions.
autocmd BufWinEnter <buffer> call go#guru#ClearSameIds()
" if a:mode && cur_offset > offset
" call cursor(m.pos1[0], m.pos1[1])
" return
" elseif !a:mode && cur_offset < offset
" call cursor(m.pos1[0], m.pos1[1])
" return
" endif
" endfor
autocmd BufEnter <buffer>
\ if go#config#AutodetectGopath() && !exists('b:old_gopath')
\| let b:old_gopath = exists('$GOPATH') ? $GOPATH : -1
\| let $GOPATH = go#path#Detect()
\| endif
autocmd BufLeave <buffer>
\ if exists('b:old_gopath')
\| if b:old_gopath isnot -1
\| let $GOPATH = b:old_gopath
\| endif
\| unlet b:old_gopath
\| endif
augroup end
" " reached start/end, jump to the end/start
" let initial_match = matches[0]
" if !has_key(initial_match, "group")
" return
" endif
" if initial_match.group != "goSameId"
" return
" endif
" call cursor(initial_match.pos1[0], initial_match.pos1[1])
" endfunction
" function! Sameids_search(mode)
" call go#guru#SameIds()
" call Sameids_repeat(a:mode)
" endfunction
" function! Sameids_nohlsearch()
" call go#guru#ClearSameIds()
" return "nohlsearch"
" endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -4,6 +4,7 @@ command! -nargs=? -complete=customlist,go#rename#Complete GoRename call go#renam
" -- guru
command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope(<f-args>)
command! -range=% GoImplements call go#guru#Implements(<count>)
command! -range=% GoPointsTo call go#guru#PointsTo(<count>)
command! -range=% GoWhicherrs call go#guru#Whicherrs(<count>)
command! -range=% GoCallees call go#guru#Callees(<count>)
command! -range=% GoDescribe call go#guru#Describe(<count>)
@ -13,19 +14,22 @@ command! -range=% GoFreevars call go#guru#Freevars(<count>)
command! -range=% GoChannelPeers call go#guru#ChannelPeers(<count>)
command! -range=% GoReferrers call go#guru#Referrers(<count>)
command! -range=0 GoSameIds call go#guru#SameIds()
command! -range=0 GoSameIds call go#guru#SameIds(1)
command! -range=0 GoSameIdsClear call go#guru#ClearSameIds()
command! -range=0 GoSameIdsToggle call go#guru#ToggleSameIds()
command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToogleSameIds()
command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToggleSameIds()
" -- tags
command! -nargs=* -range GoAddTags call go#tags#Add(<line1>, <line2>, <count>, <f-args>)
command! -nargs=* -range GoRemoveTags call go#tags#Remove(<line1>, <line2>, <count>, <f-args>)
" -- mod
command! -nargs=0 -range GoModFmt call go#mod#Format()
" -- tool
command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files(<f-args>)
command! -nargs=0 GoDeps echo go#tool#Deps()
command! -nargs=* GoInfo call go#tool#Info(0)
command! -nargs=0 GoInfo call go#tool#Info(1)
command! -nargs=0 GoAutoTypeInfoToggle call go#complete#ToggleAutoTypeInfo()
" -- cmd
@ -50,7 +54,8 @@ command! -nargs=* -bang GoCoverageBrowser call go#coverage#Browser(<bang>0, <f-a
command! -nargs=0 -range=% GoPlay call go#play#Share(<count>, <line1>, <line2>)
" -- def
command! -nargs=* -range GoDef :call go#def#Jump('')
command! -nargs=* -range GoDef :call go#def#Jump('', 0)
command! -nargs=* -range GoDefType :call go#def#Jump('', 1)
command! -nargs=? GoDefPop :call go#def#StackPop(<f-args>)
command! -nargs=? GoDefStack :call go#def#Stack(<f-args>)
command! -nargs=? GoDefStackClear :call go#def#StackClear(<f-args>)
@ -73,11 +78,11 @@ command! -nargs=1 -bang -complete=customlist,go#package#Complete GoImport call g
command! -nargs=* -bang -complete=customlist,go#package#Complete GoImportAs call go#import#SwitchImport(1, <f-args>, '<bang>')
" -- linters
command! -nargs=* GoMetaLinter call go#lint#Gometa(0, <f-args>)
command! -nargs=* -bang GoMetaLinter call go#lint#Gometa(<bang>0, 0, <f-args>)
command! -nargs=0 GoMetaLinterAutoSaveToggle call go#lint#ToggleMetaLinterAutoSave()
command! -nargs=* GoLint call go#lint#Golint(<f-args>)
command! -nargs=* -bang GoLint call go#lint#Golint(<bang>0, <f-args>)
command! -nargs=* -bang GoVet call go#lint#Vet(<bang>0, <f-args>)
command! -nargs=* -complete=customlist,go#package#Complete GoErrCheck call go#lint#Errcheck(<f-args>)
command! -nargs=* -bang -complete=customlist,go#package#Complete GoErrCheck call go#lint#Errcheck(<bang>0, <f-args>)
" -- alternate
command! -bang GoAlternate call go#alternate#Switch(<bang>0, '')
@ -105,4 +110,10 @@ if !exists(':GoDebugStart')
command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
endif
" -- issue
command! -nargs=0 GoReportGitHubIssue call go#issue#New()
" -- iferr
command! -nargs=0 GoIfErr call go#iferr#Generate()
" vim: sw=2 ts=2 et

View file

@ -31,7 +31,7 @@ nnoremap <silent> <Plug>(go-coverage-browser) :<C-u>call go#coverage#Browser(!g:
nnoremap <silent> <Plug>(go-files) :<C-u>call go#tool#Files()<CR>
nnoremap <silent> <Plug>(go-deps) :<C-u>call go#tool#Deps()<CR>
nnoremap <silent> <Plug>(go-info) :<C-u>call go#tool#Info(0)<CR>
nnoremap <silent> <Plug>(go-info) :<C-u>call go#tool#Info(1)<CR>
nnoremap <silent> <Plug>(go-import) :<C-u>call go#import#SwitchImport(1, '', expand('<cword>'), '')<CR>
nnoremap <silent> <Plug>(go-imports) :<C-u>call go#fmt#Format(1)<CR>
@ -43,16 +43,25 @@ nnoremap <silent> <Plug>(go-callstack) :<C-u>call go#guru#Callstack(-1)<CR>
xnoremap <silent> <Plug>(go-freevars) :<C-u>call go#guru#Freevars(0)<CR>
nnoremap <silent> <Plug>(go-channelpeers) :<C-u>call go#guru#ChannelPeers(-1)<CR>
nnoremap <silent> <Plug>(go-referrers) :<C-u>call go#guru#Referrers(-1)<CR>
nnoremap <silent> <Plug>(go-sameids) :<C-u>call go#guru#SameIds()<CR>
nnoremap <silent> <Plug>(go-sameids) :<C-u>call go#guru#SameIds(1)<CR>
nnoremap <silent> <Plug>(go-pointsto) :<C-u>call go#guru#PointsTo(-1)<CR>
nnoremap <silent> <Plug>(go-whicherrs) :<C-u>call go#guru#Whicherrs(-1)<CR>
nnoremap <silent> <Plug>(go-sameids-toggle) :<C-u>call go#guru#ToggleSameIds()<CR>
nnoremap <silent> <Plug>(go-rename) :<C-u>call go#rename#Rename(!g:go_jump_to_error)<CR>
nnoremap <silent> <Plug>(go-def) :<C-u>call go#def#Jump('')<CR>
nnoremap <silent> <Plug>(go-def-vertical) :<C-u>call go#def#Jump("vsplit")<CR>
nnoremap <silent> <Plug>(go-def-split) :<C-u>call go#def#Jump("split")<CR>
nnoremap <silent> <Plug>(go-def-tab) :<C-u>call go#def#Jump("tab")<CR>
nnoremap <silent> <Plug>(go-decls) :<C-u>call go#decls#Decls(0, '')<CR>
nnoremap <silent> <Plug>(go-decls-dir) :<C-u>call go#decls#Decls(1, '')<CR>
nnoremap <silent> <Plug>(go-def) :<C-u>call go#def#Jump('', 0)<CR>
nnoremap <silent> <Plug>(go-def-vertical) :<C-u>call go#def#Jump("vsplit", 0)<CR>
nnoremap <silent> <Plug>(go-def-split) :<C-u>call go#def#Jump("split", 0)<CR>
nnoremap <silent> <Plug>(go-def-tab) :<C-u>call go#def#Jump("tab", 0)<CR>
nnoremap <silent> <Plug>(go-def-type) :<C-u>call go#def#Jump('', 1)<CR>
nnoremap <silent> <Plug>(go-def-type-vertical) :<C-u>call go#def#Jump("vsplit", 1)<CR>
nnoremap <silent> <Plug>(go-def-type-split) :<C-u>call go#def#Jump("split", 1)<CR>
nnoremap <silent> <Plug>(go-def-type-tab) :<C-u>call go#def#Jump("tab", 1)<CR>
nnoremap <silent> <Plug>(go-def-pop) :<C-u>call go#def#StackPop()<CR>
nnoremap <silent> <Plug>(go-def-stack) :<C-u>call go#def#Stack()<CR>
@ -64,12 +73,14 @@ nnoremap <silent> <Plug>(go-doc-vertical) :<C-u>call go#doc#Open("vnew", "vsplit
nnoremap <silent> <Plug>(go-doc-split) :<C-u>call go#doc#Open("new", "split")<CR>
nnoremap <silent> <Plug>(go-doc-browser) :<C-u>call go#doc#OpenBrowser()<CR>
nnoremap <silent> <Plug>(go-metalinter) :<C-u>call go#lint#Gometa(0)<CR>
nnoremap <silent> <Plug>(go-lint) :<C-u>call go#lint#Golint()<CR>
nnoremap <silent> <Plug>(go-metalinter) :<C-u>call go#lint#Gometa(!g:go_jump_to_error, 0)<CR>
nnoremap <silent> <Plug>(go-lint) :<C-u>call go#lint#Golint(!g:go_jump_to_error)<CR>
nnoremap <silent> <Plug>(go-vet) :<C-u>call go#lint#Vet(!g:go_jump_to_error)<CR>
nnoremap <silent> <Plug>(go-alternate-edit) :<C-u>call go#alternate#Switch(0, "edit")<CR>
nnoremap <silent> <Plug>(go-alternate-vertical) :<C-u>call go#alternate#Switch(0, "vsplit")<CR>
nnoremap <silent> <Plug>(go-alternate-split) :<C-u>call go#alternate#Switch(0, "split")<CR>
nnoremap <silent> <Plug>(go-iferr) :<C-u>call go#iferr#Generate()<CR>
" vim: sw=2 ts=2 et

View file

@ -1,3 +1,7 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
if exists("g:go_loaded_gosnippets")
finish
endif
@ -47,7 +51,7 @@ function! s:GoMinisnip() abort
endfunction
let s:engine = get(g:, 'go_snippet_engine', 'automatic')
let s:engine = go#config#SnippetEngine()
if s:engine is? "automatic"
if get(g:, 'did_plugin_ultisnips') is 1
call s:GoUltiSnips()
@ -64,4 +68,8 @@ elseif s:engine is? "minisnip"
call s:GoMinisnip()
endif
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -9,6 +9,10 @@ elseif globpath(&rtp, 'plugin/tagbar.vim') == ""
finish
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
if !exists("g:go_gotags_bin")
let g:go_gotags_bin = "gotags"
endif
@ -54,4 +58,8 @@ endfunction
call s:SetTagbar()
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,33 @@
" gomod.vim: Vim filetype plugin for Go assembler.
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
let b:undo_ftplugin = "setl fo< com< cms<
\ | exe 'au! vim-go-gomod-buffer * <buffer>'"
setlocal formatoptions-=t
setlocal comments=s1:/*,mb:*,ex:*/,://
setlocal commentstring=//\ %s
" Autocommands
" ============================================================================
augroup vim-go-gomod-buffer
autocmd! * <buffer>
autocmd BufWritePre <buffer> call go#auto#modfmt_autosave()
augroup end
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,3 @@
command! -nargs=0 -range GoModFmt call go#mod#Format()
command! -nargs=0 GoModFmtAutoSaveToggle call go#mod#ToggleModFmtAutoSave()

View file

@ -0,0 +1 @@
nnoremap <silent> <Plug>(go-mod-fmt) :<C-u>call go#mod#Format()<CR>

View file

@ -169,7 +169,7 @@ endsnippet
# error multiple return
snippet errn, "Error return with two return values" !b
if err != nil {
return ${1:nil}, err
return ${1:nil}, ${2:err}
}
${0}
endsnippet
@ -363,6 +363,28 @@ func Test${1:Function}(t *testing.T) {
}
endsnippet
# test table snippet
snippet tt
var tests = []struct {
name string
expected string
given string
}{
{"${1}", "${2}", "${3}",},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T){
actual := ${0:${VISUAL}}(tt.given)
if actual != tt.expected {
t.Errorf("$0(%s): expected %s, actual %s", tt.given, tt.expected, actual)
}
})
}
endsnippet
snippet hf "http.HandlerFunc" !b
func ${1:handler}(w http.ResponseWriter, r *http.Request) {
${0:fmt.Fprintf(w, "hello world")}

View file

@ -0,0 +1,17 @@
var tests = []struct {
name string
expected string
given string
}{
{"", "", "",},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T){
actual := {{++}}(tt.given)
if actual != tt.expected {
t.Errorf("{{+~\~1+}}(%s): expected %s, actual %s", tt.given, tt.expected, actual)
}
})
}

View file

@ -315,6 +315,25 @@ abbr func TestXYZ(t *testing.T) { ... }
func Test${1:Function}(t *testing.T) {
${0}
}
# test table snippet
snippet tt
abbr var test = {...}{...} for {t.Run(){...}}
var tests = []struct {
name string
expected string
given string
}{
{"${2}", "${3}", "${4}",},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T){
actual := ${1:Function}(tt.given)
if actual != tt.expected {
t.Errorf("given(%s): expected %s, actual %s", tt.given, tt.expected, actual)
}
})
}
# test server
snippet tsrv
abbr ts := httptest.NewServer(...)

View file

@ -24,18 +24,11 @@ if exists("*GoIndent")
finish
endif
" use shiftwidth function only if it's available
if exists('*shiftwidth')
func s:sw()
return shiftwidth()
endfunc
else
func s:sw()
return &sw
endfunc
endif
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! GoIndent(lnum)
function! GoIndent(lnum) abort
let prevlnum = prevnonblank(a:lnum-1)
if prevlnum == 0
" top of file
@ -49,19 +42,30 @@ function! GoIndent(lnum)
let ind = previ
for synid in synstack(a:lnum, 1)
if synIDattr(synid, 'name') == 'goRawString'
if prevl =~ '\%(\%(:\?=\)\|(\|,\)\s*`[^`]*$'
" previous line started a multi-line raw string
return 0
endif
" return -1 to keep the current indent.
return -1
endif
endfor
if prevl =~ '[({]\s*$'
" previous line opened a block
let ind += s:sw()
let ind += shiftwidth()
endif
if prevl =~# '^\s*\(case .*\|default\):$'
" previous line is part of a switch statement
let ind += s:sw()
let ind += shiftwidth()
endif
" TODO: handle if the previous line is a label.
if thisl =~ '^\s*[)}]'
" this line closed a block
let ind -= s:sw()
let ind -= shiftwidth()
endif
" Colons are tricky.
@ -69,10 +73,14 @@ function! GoIndent(lnum)
" We ignore trying to deal with jump labels because (a) they're rare, and
" (b) they're hard to disambiguate from a composite literal key.
if thisl =~# '^\s*\(case .*\|default\):$'
let ind -= s:sw()
let ind -= shiftwidth()
endif
return ind
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

Some files were not shown because too many files have changed in this diff Show more