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 target: auto
threshold: 1 threshold: 1
base: auto base: auto
patch: off
comment: false comment: false
ignore: ignore:
- "!autoload/go/*.vim$" - "!autoload/go/*.vim$"

View file

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

View file

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

View file

@ -9,14 +9,13 @@
### Configuration (**MUST** fill this out): ### 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 version (`go version`):
* Go environment (`go env`): * 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 language: go
go:
- 1.12.1
notifications: notifications:
email: false email: false
matrix: matrix:
@ -6,9 +8,16 @@ matrix:
- env: SCRIPT="test -c" VIM_VERSION=vim-7.4 - env: SCRIPT="test -c" VIM_VERSION=vim-7.4
- env: SCRIPT="test -c" VIM_VERSION=vim-8.0 - env: SCRIPT="test -c" VIM_VERSION=vim-8.0
- env: SCRIPT="test -c" VIM_VERSION=nvim - 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: install:
- ./scripts/install-vim $VIM_VERSION - ./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: script:
- ./scripts/$SCRIPT $VIM_VERSION - ./scripts/$SCRIPT $VIM_VERSION

View file

@ -1,11 +1,314 @@
## unplanned ## 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) ## 1.17 - (March 27, 2018)
FEATURES: FEATURES:
* **Debugger support!** Add integrated support for the * **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 `:GoInstallBinaries` to install `dlv`, and see `:help go-debug` to get
started. started.
[[GH-1390]](https://github.com/fatih/vim-go/pull/1390) [[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 && \ RUN apt-get update -y && \
apt-get install -y build-essential curl git libncurses5-dev python3-pip && \ 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 VIMS ?= vim-7.4 vim-8.0 nvim
all: install test lint all: install lint test
install: install:
@echo "==> Installing Vims: $(VIMS)" @echo "==> Installing Vims: $(VIMS)"

View file

@ -9,11 +9,11 @@
This plugin adds Go language support for Vim, with the following main features: 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 * 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`. * Quickly execute your current file(s) with `:GoRun`.
* Improved syntax highlighting and folding. * Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with `:GoDebugStart`. * 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. * `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with `:GoDef`. * Go to symbol/declaration with `:GoDef`.
* Look up documentation with `:GoDoc` or `:GoDocBrowser`. * 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 ## 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 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, recommended version to use. If you choose to use the master branch instead,
please do so with caution; it is a _development_ branch. please do so with caution; it is a _development_ branch.
vim-go follows the standard runtime path structure. Below are some helper lines vim-go follows the standard runtime path structure. Below are some helper lines
for popular package managers: for popular package managers:
@ -45,7 +48,9 @@ for popular package managers:
* [Pathogen](https://github.com/tpope/vim-pathogen) * [Pathogen](https://github.com/tpope/vim-pathogen)
* `git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go` * `git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go`
* [vim-plug](https://github.com/junegunn/vim-plug) * [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 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 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 = { let s:go_decls_var = {
\ 'init': 'ctrlp#decls#init()', \ 'init': 'ctrlp#decls#init()',
\ 'exit': 'ctrlp#decls#exit()', \ 'exit': 'ctrlp#decls#exit()',
@ -20,7 +24,7 @@ function! ctrlp#decls#init() abort
endfunction endfunction
function! ctrlp#decls#exit() abort function! ctrlp#decls#exit() abort
unlet! s:decls s:current_dir s:target unlet! s:decls s:target
endfunction endfunction
" The action to perform on the selected string " 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 cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd() let dir = getcwd()
try 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*\(.*\)|') let vals = matchlist(a:str, '|\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)|')
" i.e: main.go " i.e: main.go
@ -50,46 +50,41 @@ function! ctrlp#decls#accept(mode, str) abort
call ctrlp#acceptfile(a:mode, filepath) call ctrlp#acceptfile(a:mode, filepath)
call cursor(line, col) call cursor(line, col)
silent! norm! zvzz silent! norm! zvzz
finally
"jump back to old dir
execute cd . fnameescape(dir)
endtry endtry
endfunction endfunction
function! ctrlp#decls#enter() abort function! ctrlp#decls#enter() abort
let s:current_dir = fnameescape(expand('%:p:h'))
let s:decls = [] let s:decls = []
let bin_path = go#path#CheckBinPath('motion') let l:cmd = ['motion',
if empty(bin_path) \ '-format', 'vim',
return \ '-mode', 'decls',
endif \ '-include', go#config#DeclsIncludes(),
let command = printf("%s -format vim -mode decls", bin_path) \ ]
let command .= " -include ". get(g:, "go_decls_includes", "func,type")
call go#cmd#autowrite() call go#cmd#autowrite()
if s:mode == 0 if s:mode == 0
" current file mode " current file mode
let fname = expand("%:p") let l:fname = expand("%:p")
if exists('s:target') if exists('s:target')
let fname = s:target let l:fname = s:target
endif endif
let command .= printf(" -file %s", fname) let cmd += ['-file', l:fname]
else else
" all functions mode " all functions mode
let dir = expand("%:p:h") let l:dir = expand("%:p:h")
if exists('s:target') if exists('s:target')
let dir = s:target let l:dir = s:target
endif endif
let command .= printf(" -dir %s", dir) let cmd += ['-dir', l:dir]
endif endif
let out = go#util#System(command) let [l:out, l:err] = go#util#Exec(l:cmd)
if go#util#ShellError() != 0 if l:err
call go#util#EchoError(out) call go#util#EchoError(l:out)
return return
endif endif
@ -118,7 +113,7 @@ function! ctrlp#decls#enter() abort
call add(s:decls, printf("%s\t%s |%s:%s:%s|\t%s", call add(s:decls, printf("%s\t%s |%s:%s:%s|\t%s",
\ decl.ident . space, \ decl.ident . space,
\ decl.keyword, \ decl.keyword,
\ fnamemodify(decl.filename, ":t"), \ fnamemodify(decl.filename, ":p"),
\ decl.line, \ decl.line,
\ decl.col, \ decl.col,
\ decl.full, \ decl.full,
@ -152,4 +147,8 @@ function! ctrlp#decls#cmd(mode, ...) abort
return s:id return s:id
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 function! s:code(group, attr) abort
let code = synIDattr(synIDtrans(hlID(a:group)), a:attr, "cterm") let code = synIDattr(synIDtrans(hlID(a:group)), a:attr, "cterm")
if code =~ '^[0-9]\+$' if code =~ '^[0-9]\+$'
@ -58,35 +62,34 @@ function! s:source(mode,...) abort
let s:current_dir = expand('%:p:h') let s:current_dir = expand('%:p:h')
let ret_decls = [] let ret_decls = []
let bin_path = go#path#CheckBinPath('motion') let l:cmd = ['motion',
if empty(bin_path) \ '-format', 'vim',
return \ '-mode', 'decls',
endif \ '-include', go#config#DeclsIncludes(),
let command = printf("%s -format vim -mode decls", bin_path) \ ]
let command .= " -include ". get(g:, "go_decls_includes", "func,type")
call go#cmd#autowrite() call go#cmd#autowrite()
if a:mode == 0 if a:mode == 0
" current file mode " current file mode
let fname = expand("%:p") let l:fname = expand("%:p")
if a:0 && !empty(a:1) if a:0 && !empty(a:1)
let fname = a:1 let l:fname = a:1
endif endif
let command .= printf(" -file %s", shellescape(fname)) let cmd += ['-file', l:fname]
else else
" all functions mode " all functions mode
if a:0 && !empty(a:1) if a:0 && !empty(a:1)
let s:current_dir = a:1 let s:current_dir = a:1
endif endif
let command .= printf(" -dir %s", shellescape(s:current_dir)) let l:cmd += ['-dir', s:current_dir]
endif endif
let out = go#util#System(command) let [l:out, l:err] = go#util#Exec(l:cmd)
if go#util#ShellError() != 0 if l:err
call go#util#EchoError(out) call go#util#EchoError(l:out)
return return
endif endif
@ -147,4 +150,8 @@ function! fzf#decls#cmd(...) abort
\ })) \ }))
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

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

View file

@ -15,6 +15,10 @@
" "
" Flag to automatically call :Fmt when file is saved. " 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 let s:got_fmt_error = 0
" This is a trimmed-down version of the logic in fmt.vim. " 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) call writefile(go#util#GetLines(), l:tmpname)
" Run asmfmt. " Run asmfmt.
let path = go#path#CheckBinPath("asmfmt") let [l:out, l:err] = go#util#Exec(['asmfmt', '-w', l:tmpname])
if empty(path) if l:err
call go#util#EchoError(l:out)
return return
endif endif
let out = go#util#System(path . ' -w ' . l:tmpname)
" If there's no error, replace the current file with the output. " Remove undo point caused by BufWritePre.
if go#util#ShellError() == 0 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. " Replace the current file with the temp file; then reload the buffer.
let old_fileformat = &fileformat let old_fileformat = &fileformat
" save old file permissions
let original_fperm = getfperm(expand('%')) " save old file permissions
call rename(l:tmpname, expand('%')) let original_fperm = getfperm(expand('%'))
" restore old file permissions call rename(l:tmpname, expand('%'))
call setfperm(expand('%'), original_fperm)
silent edit! " restore old file permissions
let &fileformat = old_fileformat call setfperm(expand('%'), original_fperm)
let &syntax = &syntax silent edit!
endif let &fileformat = old_fileformat
let &syntax = &syntax
" Restore the cursor/window positions. " Restore the cursor/window positions.
call winrestview(l:curw) call winrestview(l:curw)
endfunction endfunction
function! go#asmfmt#ToggleAsmFmtAutoSave() abort function! go#asmfmt#ToggleAsmFmtAutoSave() abort
if get(g:, "go_asmfmt_autosave", 0) if go#config#AsmfmtAutosave()
let g:go_asmfmt_autosave = 1 call go#config#SetAsmfmtAutosave(1)
call go#util#EchoProgress("auto asmfmt enabled") call go#util#EchoProgress("auto asmfmt enabled")
return return
end end
let g:go_asmfmt_autosave = 0 call go#config#SetAsmfmtAutosave(0)
call go#util#EchoProgress("auto asmfmt disabled") call go#util#EchoProgress("auto asmfmt disabled")
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 function! go#cmd#autowrite() abort
if &autowrite == 1 || &autowriteall == 1 if &autowrite == 1 || &autowriteall == 1
silent! wall 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 endif
endfunction endfunction
@ -11,34 +26,21 @@ endfunction
function! go#cmd#Build(bang, ...) abort function! go#cmd#Build(bang, ...) abort
" Create our command arguments. go build discards any results when it " Create our command arguments. go build discards any results when it
" compiles multiple packages. So we pass the `errors` package just as an " compiles multiple packages. So we pass the `errors` package just as an
" placeholder with the current folder (indicated with '.'). We also pass -i " placeholder with the current folder (indicated with '.').
" that tries to install the dependencies, this has the side effect that it let l:args =
" caches the build results, so every other build is faster. \ ['build', '-tags', go#config#BuildTags()] +
let args =
\ ["build"] +
\ map(copy(a:000), "expand(v:val)") + \ map(copy(a:000), "expand(v:val)") +
\ [".", "errors"] \ [".", "errors"]
" Vim async. " Vim and Neovim async.
if go#util#has_job() if go#util#has_job()
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("building dispatched ...")
endif
call s:cmd_job({ call s:cmd_job({
\ 'cmd': ['go'] + args, \ 'cmd': ['go'] + args,
\ 'bang': a:bang, \ 'bang': a:bang,
\ 'for': 'GoBuild', \ '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 " Vim 7.4 without async
else else
let default_makeprg = &makeprg let default_makeprg = &makeprg
@ -59,6 +61,7 @@ function! go#cmd#Build(bang, ...) abort
redraw! redraw!
finally finally
execute cd . fnameescape(dir) execute cd . fnameescape(dir)
let &makeprg = default_makeprg
endtry endtry
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
@ -68,8 +71,6 @@ function! go#cmd#Build(bang, ...) abort
else else
call go#util#EchoSuccess("[build] SUCCESS") call go#util#EchoSuccess("[build] SUCCESS")
endif endif
let &makeprg = default_makeprg
endif endif
endfunction endfunction
@ -77,33 +78,44 @@ endfunction
" BuildTags sets or shows the current build tags used for tools " BuildTags sets or shows the current build tags used for tools
function! go#cmd#BuildTags(bang, ...) abort function! go#cmd#BuildTags(bang, ...) abort
if a:0 if a:0
if a:0 == 1 && a:1 == '""' let v = a:1
unlet g:go_build_tags 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") call go#util#EchoSuccess("build tags are cleared")
else else
let g:go_build_tags = a:1 call go#util#EchoSuccess("build tags are changed to: " . tags)
call go#util#EchoSuccess("build tags are changed to: ". a:1)
endif endif
return return
endif endif
if !exists('g:go_build_tags') let tags = go#config#BuildTags()
if empty(tags)
call go#util#EchoSuccess("build tags are not set") call go#util#EchoSuccess("build tags are not set")
else else
call go#util#EchoSuccess("current build tags: ". g:go_build_tags) call go#util#EchoSuccess("current build tags: " . tags)
endif endif
endfunction endfunction
" Run runs the current file (and their dependencies if any) in a new terminal. " Run runs the current file (and their dependencies if any) in a new terminal.
function! go#cmd#RunTerm(bang, mode, files) abort function! go#cmd#RunTerm(bang, mode, files) abort
if empty(a:files) let cmd = "go run "
let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) let tags = go#config#BuildTags()
else if len(tags) > 0
let cmd = "go run ". go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1) let cmd .= "-tags " . go#util#Shellescape(tags) . " "
endif 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 endfunction
" Run runs the current file (and their dependencies if any) and outputs it. " 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 " anything. Once this is implemented we're going to make :GoRun async
endif 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() 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 if v:shell_error
redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None
else else
@ -136,29 +159,36 @@ function! go#cmd#Run(bang, ...) abort
" :make expands '%' and '#' wildcards, so they must also be escaped " :make expands '%' and '#' wildcards, so they must also be escaped
let default_makeprg = &makeprg let default_makeprg = &makeprg
if a:0 == 0 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 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 endif
let l:listtype = go#list#Type("GoRun") let l:listtype = go#list#Type("GoRun")
if l:listtype == "locationlist" try
exe 'lmake!'
else
exe 'make!'
endif
let items = go#list#Get(l:listtype) " backup user's errorformat, will be restored once we are finished
let errors = go#tool#FilterValids(items) 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) let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang call go#list#Window(l:listtype, len(l:errors))
if !empty(l:errors) && !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
endif endif
let &makeprg = default_makeprg
endfunction endfunction
" Install installs the package by simple calling 'go install'. If any argument " 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) " expand all wildcards(i.e: '%' to the current file name)
let goargs = map(copy(a:000), "expand(v:val)") 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({ call s:cmd_job({
\ 'cmd': ['go', 'install'] + goargs, \ 'cmd': ['go', 'install', '-tags', go#config#BuildTags()] + goargs,
\ 'bang': a:bang, \ 'bang': a:bang,
\ 'for': 'GoInstall', \ 'for': 'GoInstall',
\ 'statustype': 'install'
\}) \})
return return
endif endif
@ -203,6 +230,7 @@ function! go#cmd#Install(bang, ...) abort
redraw! redraw!
finally finally
execute cd . fnameescape(dir) execute cd . fnameescape(dir)
let &makeprg = default_makeprg
endtry endtry
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
@ -212,8 +240,6 @@ function! go#cmd#Install(bang, ...) abort
else else
call go#util#EchoSuccess("installed to ". go#path#Default()) call go#util#EchoSuccess("installed to ". go#path#Default())
endif endif
let &makeprg = default_makeprg
endfunction endfunction
" Generate runs 'go generate' in similar fashion to go#cmd#Build() " 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") let l:listtype = go#list#Type("GoGenerate")
echon "vim-go: " | echohl Identifier | echon "generating ..."| echohl None echon "vim-go: " | echohl Identifier | echon "generating ..."| echohl None
if l:listtype == "locationlist"
silent! exe 'lmake!' try
else if l:listtype == "locationlist"
silent! exe 'make!' silent! exe 'lmake!'
endif else
redraw! silent! exe 'make!'
endif
finally
redraw!
let &makeprg = default_makeprg
endtry
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) 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 redraws! | echon "vim-go: " | echohl Function | echon "[generate] SUCCESS"| echohl None
endif 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 endfunction
" --------------------- " ---------------------
" | Vim job callbacks | " | Vim job callbacks |
" --------------------- " ---------------------
function s:cmd_job(args) abort function! s:cmd_job(args) abort
let status_dir = expand('%:p:h')
let started_at = reltime()
call go#statusline#Update(status_dir, {
\ 'desc': "current status",
\ 'type': a:args.cmd[1],
\ 'state': "started",
\})
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
function! s:complete(job, exit_status, data) closure abort call go#job#Spawn(a:args.cmd, a:args)
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)
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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() func! Test_GoBuildErrors()
try try
let l:filename = 'cmd/bad.go' let l:filename = 'cmd/bad.go'
@ -27,4 +31,8 @@ func! Test_GoBuildErrors()
endtry endtry
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 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) if empty(bin_path)
return [] return []
endif 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 = [bin_path]
let cmd = extend(cmd, ['-sock', socket_type]) let cmd = extend(cmd, ['-sock', socket_type])
let cmd = extend(cmd, ['-f', 'vim']) 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:cmd])
let cmd = extend(cmd, a:args) let cmd = extend(cmd, a:args)
@ -45,32 +68,7 @@ function! s:sync_gocode(cmd, args, input) abort
return l:result return l:result
endfunction 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 function! s:gocodeAutocomplete() abort
call s:gocodeEnableOptions()
" use the offset as is, because the cursor position is the position for " use the offset as is, because the cursor position is the position for
" which autocomplete candidates are needed. " which autocomplete candidates are needed.
return s:sync_gocode('autocomplete', return s:sync_gocode('autocomplete',
@ -84,60 +82,20 @@ function! go#complete#GetInfo() abort
return s:sync_info(0) return s:sync_info(0)
endfunction endfunction
function! go#complete#Info(auto) abort function! go#complete#Info(showstatus) abort
if go#util#has_job() if go#util#has_job(1)
return s:async_info(a:auto) return s:async_info(1, a:showstatus)
else else
return s:sync_info(a:auto) return s:sync_info(1)
endif endif
endfunction endfunction
function! s:async_info(auto) function! s:async_info(echo, showstatus)
if exists("s:async_info_job") let state = {'echo': a:echo}
call job_stop(s:async_info_job)
unlet s:async_info_job
endif
let state = { " explicitly bind complete to state so that within it, self will
\ 'exited': 0, " always refer to state. See :help Partial for more information.
\ 'exit_status': 0, let state.complete = function('s:complete', [], state)
\ '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
" add 1 to the offset, so that the position at the cursor will be included " add 1 to the offset, so that the position at the cursor will be included
" in gocode's search " in gocode's search
@ -149,23 +107,49 @@ function! s:async_info(auto)
\ "GOROOT": go#util#env("goroot") \ "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', let cmd = s:gocodeCommand('autocomplete',
\ [expand('%:p'), offset]) \ [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: " 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('%')} " s:gocodeFile()}, but unfortunately {in_io: 'buffer', in_buf: bufnr('%')}
" should work. " doesn't work.
let options = { call extend(opts, {
\ 'env': env, \ 'env': env,
\ 'in_io': 'file', \ 'in_io': 'file',
\ 'in_name': s:gocodeFile(), \ '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 endfunction
function! s:gocodeFile() function! s:gocodeFile()
@ -174,9 +158,7 @@ function! s:gocodeFile()
return file return file
endfunction endfunction
function! s:sync_info(auto) function! s:sync_info(echo)
" auto is true if we were called by g:go_auto_type_info's autocmd
" add 1 to the offset, so that the position at the cursor will be included " add 1 to the offset, so that the position at the cursor will be included
" in gocode's search " in gocode's search
let offset = go#util#OffsetCursor()+1 let offset = go#util#OffsetCursor()+1
@ -185,11 +167,11 @@ function! s:sync_info(auto)
\ [expand('%:p'), offset], \ [expand('%:p'), offset],
\ go#util#GetLines()) \ go#util#GetLines())
let result = s:info_filter(a:auto, result) let result = s:info_filter(a:echo, result)
call s:info_complete(a:auto, result) return s:info_complete(a:echo, result)
endfunction endfunction
function! s:info_filter(auto, result) abort function! s:info_filter(echo, result) abort
if empty(a:result) if empty(a:result)
return "" return ""
endif endif
@ -203,7 +185,7 @@ function! s:info_filter(auto, result) abort
if len(l:candidates) == 1 if len(l:candidates) == 1
" When gocode panics in vim mode, it returns " When gocode panics in vim mode, it returns
" [0, [{'word': 'PANIC', 'abbr': 'PANIC PANIC PANIC', 'info': 'PANIC PANIC PANIC'}]] " [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 "" return ""
endif endif
@ -216,17 +198,21 @@ function! s:info_filter(auto, result) abort
let wordMatch = substitute(wordMatch, "'", "''", "g") let wordMatch = substitute(wordMatch, "'", "''", "g")
let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'") let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'")
if len(l:filtered) != 1 if len(l:filtered) == 0
return "" return "no matches"
elseif len(l:filtered) > 1
return "ambiguous match"
endif endif
return l:filtered[0].info return l:filtered[0].info
endfunction endfunction
function! s:info_complete(auto, result) abort function! s:info_complete(echo, result) abort
if !empty(a:result) if a:echo
echo "vim-go: " | echohl Function | echon a:result | echohl None call go#util#ShowInfo(a:result)
endif endif
return a:result
endfunction endfunction
function! s:trim_bracket(val) abort function! s:trim_bracket(val) abort
@ -234,12 +220,22 @@ function! s:trim_bracket(val) abort
return a:val return a:val
endfunction endfunction
let s:completions = "" let s:completions = []
function! go#complete#Complete(findstart, base) abort
function! go#complete#GocodeComplete(findstart, base) abort
"findstart = 1 when we need to get the text length "findstart = 1 when we need to get the text length
if a:findstart == 1 if a:findstart == 1
execute "silent let s:completions = " . s:gocodeAutocomplete() let l:completions = []
return col('.') - s:completions[0] - 1 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 "findstart = 0 when we need to return the list of completions
else else
let s = getline(".")[col('.') - 1] let s = getline(".")[col('.') - 1]
@ -251,15 +247,49 @@ function! go#complete#Complete(findstart, base) abort
endif endif
endfunction 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 function! go#complete#ToggleAutoTypeInfo() abort
if get(g:, "go_auto_type_info", 0) if go#config#AutoTypeInfo()
let g:go_auto_type_info = 0 call go#config#SetAutoTypeInfo(0)
call go#util#EchoProgress("auto type info disabled") call go#util#EchoProgress("auto type info disabled")
return return
end end
let g:go_auto_type_info = 1 call go#config#SetAutoTypeInfo(1)
call go#util#EchoProgress("auto type info enabled") call go#util#EchoProgress("auto type info enabled")
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 let s:toggle = 0
" Buffer creates a new cover profile with 'go test -coverprofile' and changes " 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 s:toggle = 1
let l:tmpname = tempname() let l:tmpname = tempname()
if get(g:, 'go_echo_command_info', 1)
call go#util#EchoProgress("testing...")
endif
if go#util#has_job() if go#util#has_job()
call s:coverage_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]), \ 'complete': function('s:coverage_callback', [l:tmpname]),
\ 'bang': a:bang, \ 'bang': a:bang,
\ 'for': 'GoTest', \ 'for': 'GoTest',
\ 'statustype': 'coverage',
\ }) \ })
return return
endif endif
if go#config#EchoCommandInfo()
call go#util#EchoProgress("testing...")
endif
let args = [a:bang, 0, "-coverprofile", l:tmpname] let args = [a:bang, 0, "-coverprofile", l:tmpname]
if a:0 if a:0
call extend(args, a:000) call extend(args, a:000)
endif 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) 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 if go#util#ShellError() == 0
call go#coverage#overlay(l:tmpname) call go#coverage#overlay(l:tmpname)
endif endif
@ -96,7 +85,7 @@ function! go#coverage#Clear() abort
" remove the autocmd we defined " remove the autocmd we defined
augroup vim-go-coverage augroup vim-go-coverage
autocmd! autocmd! * <buffer>
augroup end augroup end
endfunction endfunction
@ -106,10 +95,11 @@ function! go#coverage#Browser(bang, ...) abort
let l:tmpname = tempname() let l:tmpname = tempname()
if go#util#has_job() if go#util#has_job()
call s:coverage_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]), \ 'complete': function('s:coverage_browser_callback', [l:tmpname]),
\ 'bang': a:bang, \ 'bang': a:bang,
\ 'for': 'GoTest', \ 'for': 'GoTest',
\ 'statustype': 'coverage',
\ }) \ })
return return
endif endif
@ -120,16 +110,9 @@ function! go#coverage#Browser(bang, ...) abort
endif endif
let id = call('go#test#Test', args) 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 if go#util#ShellError() == 0
let openHTML = 'go tool cover -html='.l:tmpname call go#util#ExecInDir(['go', 'tool', 'cover', '-html=' . l:tmpname])
call go#tool#ExecuteInDir(openHTML)
endif endif
call delete(l:tmpname) call delete(l:tmpname)
@ -259,7 +242,7 @@ function! go#coverage#overlay(file) abort
" clear the matches if we leave the buffer " clear the matches if we leave the buffer
augroup vim-go-coverage augroup vim-go-coverage
autocmd! autocmd! * <buffer>
autocmd BufWinLeave <buffer> call go#coverage#Clear() autocmd BufWinLeave <buffer> call go#coverage#Clear()
augroup end augroup end
@ -277,48 +260,17 @@ function s:coverage_job(args)
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
let status_dir = expand('%:p:h') let disabled_term = 0
let Complete = a:args.complete if go#config#TermEnabled()
function! s:complete(job, exit_status, data) closure let disabled_term = 1
let status = { call go#config#SetTermEnabled(0)
\ 'desc': 'last status', endif
\ 'type': "coverage",
\ 'state': "finished",
\ }
if a:exit_status call go#job#Spawn(a:args.cmd, a:args)
let status.state = "failed"
endif
call go#statusline#Update(status_dir, status) if disabled_term
return Complete(a:job, a:exit_status, a:data) call go#config#SetTermEnabled(1)
endfunction endif
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)
endfunction endfunction
" coverage_callback is called when the coverage execution is finished " 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) function! s:coverage_browser_callback(coverfile, job, exit_status, data)
if a:exit_status == 0 if a:exit_status == 0
let openHTML = 'go tool cover -html='.a:coverfile call go#util#ExecInDir(['go', 'tool', 'cover', '-html=' . a:coverfile])
call go#tool#ExecuteInDir(openHTML)
endif endif
call delete(a:coverfile) call delete(a:coverfile)
endfunction endfunction
" ----------------------- " restore Vi compatibility settings
" | Neovim job handlers | let &cpo = s:cpo_save
" ----------------------- unlet s:cpo_save
let s:coverage_handler_jobs = {}
let s:coverage_browser_handler_jobs = {}
function! s:coverage_handler(job, exit_status, data) abort
if !has_key(s:coverage_handler_jobs, a:job.id)
return
endif
let l:tmpname = s:coverage_handler_jobs[a:job.id]
if a:exit_status == 0
call go#coverage#overlay(l:tmpname)
endif
call delete(l:tmpname)
unlet s:coverage_handler_jobs[a:job.id]
endfunction
function! s:coverage_browser_handler(job, exit_status, data) abort
if !has_key(s:coverage_browser_handler_jobs, a:job.id)
return
endif
let l:tmpname = s:coverage_browser_handler_jobs[a:job.id]
if a:exit_status == 0
let openHTML = 'go tool cover -html='.l:tmpname
call go#tool#ExecuteInDir(openHTML)
endif
call delete(l:tmpname)
unlet s:coverage_browser_handler_jobs[a:job.id]
endfunction
" vim: sw=2 ts=2 et " 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 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') if !exists('s:state')
let s:state = { let s:state = {
\ 'rpcid': 1, \ 'rpcid': 1,
\ 'running': 0, \ 'running': 0,
\ 'breakpoint': {},
\ 'currentThread': {}, \ 'currentThread': {},
\ 'localVars': {}, \ 'localVars': {},
\ 'functionArgs': {}, \ 'functionArgs': {},
@ -25,7 +16,7 @@ if !exists('s:state')
\} \}
if go#util#HasDebug('debugger-state') if go#util#HasDebug('debugger-state')
let g:go_debug_diag = s:state call go#config#SetDebugDiag(s:state)
endif endif
endif endif
@ -37,14 +28,35 @@ function! s:groutineID() abort
return s:state['currentThread'].goroutineID return s:state['currentThread'].goroutineID
endfunction 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') if has_key(s:state, 'job')
call remove(s:state, 'job') call remove(s:state, 'job')
endif endif
call s:clearState()
if a:status > 0 if has_key(s:state, 'ready')
call go#util#EchoError(s:state['message']) call remove(s:state, 'ready')
endif endif
if has_key(s:state, 'ch')
call remove(s:state, 'ch')
endif
call s:clearState()
endfunction endfunction
function! s:logger(prefix, ch, msg) abort function! s:logger(prefix, ch, msg) abort
@ -71,57 +83,46 @@ endfunction
function! s:call_jsonrpc(method, ...) abort function! s:call_jsonrpc(method, ...) abort
if go#util#HasDebug('debugger-commands') if go#util#HasDebug('debugger-commands')
if !exists('g:go_debug_commands')
let g:go_debug_commands = []
endif
echom 'sending to dlv ' . a:method echom 'sending to dlv ' . a:method
endif endif
if len(a:000) > 0 && type(a:000[0]) == v:t_func let l:args = a:000
let Cb = a:000[0]
let args = a:000[1:]
else
let Cb = v:none
let args = a:000
endif
let s:state['rpcid'] += 1 let s:state['rpcid'] += 1
let req_json = json_encode({ let l:req_json = json_encode({
\ 'id': s:state['rpcid'], \ 'id': s:state['rpcid'],
\ 'method': a:method, \ 'method': a:method,
\ 'params': args, \ 'params': l:args,
\}) \})
try try
" Use callback let l:ch = s:state['ch']
if type(Cb) == v:t_func if has('nvim')
let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb}) call chansend(l:ch, l:req_json)
call ch_sendraw(s:ch, req_json) while len(s:state.data) == 0
sleep 50m
if go#util#HasDebug('debugger-commands') if get(s:state, 'ready', 0) == 0
let g:go_debug_commands = add(g:go_debug_commands, { return
\ 'request': req_json, endif
\ 'response': Cb, endwhile
\ }) let resp_json = s:state.data[0]
endif let s:state.data = s:state.data[1:]
return 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 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') if go#util#HasDebug('debugger-commands')
let g:go_debug_commands = add(g:go_debug_commands, { let g:go_debug_commands = add(go#config#DebugCommands(), {
\ 'request': req_json, \ 'request': l:req_json,
\ 'response': resp_json, \ 'response': l:resp_json,
\ }) \ })
endif endif
let obj = json_decode(resp_json) if type(l:resp_json) == v:t_dict && has_key(l:resp_json, 'error') && !empty(l:resp_json.error)
if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error) throw l:resp_json.error
throw obj.error
endif endif
return obj return l:resp_json
catch catch
throw substitute(v:exception, '^Vim', '', '') throw substitute(v:exception, '^Vim', '', '')
endtry endtry
@ -130,7 +131,7 @@ endfunction
" Update the location of the current breakpoint or line we're halted on based on " Update the location of the current breakpoint or line we're halted on based on
" response from dlv. " response from dlv.
function! s:update_breakpoint(res) abort function! s:update_breakpoint(res) abort
if type(a:res) ==# v:t_none if type(a:res) ==# type(v:null)
return return
endif endif
@ -227,26 +228,34 @@ function! s:clearState() abort
let s:state['localVars'] = {} let s:state['localVars'] = {}
let s:state['functionArgs'] = {} let s:state['functionArgs'] = {}
let s:state['message'] = [] let s:state['message'] = []
silent! sign unplace 9999 silent! sign unplace 9999
endfunction endfunction
function! s:stop() abort function! s:stop() abort
call s:clearState() let l:res = s:call_jsonrpc('RPCServer.Detach', {'kill': v:true})
if has_key(s:state, 'job') if has_key(s:state, 'job')
call job_stop(s:state['job']) call go#job#Wait(s:state['job'])
call remove(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 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 endfunction
function! go#debug#Stop() abort 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. " 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\\+")') for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")')
exe 'delcommand' k 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_VARIABLES__')) 'wincmd c'
silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c' silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c'
set noballooneval if has('balloon_eval')
set balloonexpr= let &ballooneval=s:ballooneval
let &balloonexpr=s:balloonexpr
endif
augroup vim-go-debug
autocmd!
augroup END
augroup! vim-go-debug
endfunction endfunction
function! s:goto_file() abort function! s:goto_file() abort
@ -393,22 +409,8 @@ function! s:expand_var() abort
endif endif
endfunction endfunction
function! s:start_cb(ch, json) abort function! s:start_cb() abort
let res = json_decode(a:json) let l:winid = win_getid()
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('%')
silent! only! silent! only!
let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
@ -416,8 +418,9 @@ function! s:start_cb(ch, json) abort
return return
endif endif
if exists('g:go_debug_windows["stack"]') && g:go_debug_windows['stack'] != '' let debugwindows = go#config#DebugWindows()
exe 'silent ' . g:go_debug_windows['stack'] if has_key(debugwindows, "stack") && debugwindows['stack'] != ''
exe 'silent ' . debugwindows['stack']
silent file `='__GODEBUG_STACKTRACE__'` silent file `='__GODEBUG_STACKTRACE__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugstacktrace setlocal filetype=godebugstacktrace
@ -425,16 +428,16 @@ function! s:start_cb(ch, json) abort
nmap <buffer> q <Plug>(go-debug-stop) nmap <buffer> q <Plug>(go-debug-stop)
endif endif
if exists('g:go_debug_windows["out"]') && g:go_debug_windows['out'] != '' if has_key(debugwindows, "out") && debugwindows['out'] != ''
exe 'silent ' . g:go_debug_windows['out'] exe 'silent ' . debugwindows['out']
silent file `='__GODEBUG_OUTPUT__'` silent file `='__GODEBUG_OUTPUT__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugoutput setlocal filetype=godebugoutput
nmap <buffer> q <Plug>(go-debug-stop) nmap <buffer> q <Plug>(go-debug-stop)
endif endif
if exists('g:go_debug_windows["vars"]') && g:go_debug_windows['vars'] != '' if has_key(debugwindows, "vars") && debugwindows['vars'] != ''
exe 'silent ' . g:go_debug_windows['vars'] exe 'silent ' . debugwindows['vars']
silent file `='__GODEBUG_VARIABLES__'` silent file `='__GODEBUG_VARIABLES__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugvariables 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-stop) :<C-u>call go#debug#Stop()<CR>
nnoremap <silent> <Plug>(go-debug-print) :<C-u>call go#debug#Print(expand('<cword>'))<CR> nnoremap <silent> <Plug>(go-debug-print) :<C-u>call go#debug#Print(expand('<cword>'))<CR>
nmap <F5> <Plug>(go-debug-continue) if has('balloon_eval')
nmap <F6> <Plug>(go-debug-print) let s:balloonexpr=&balloonexpr
nmap <F9> <Plug>(go-debug-breakpoint) let s:ballooneval=&ballooneval
nmap <F10> <Plug>(go-debug-next)
nmap <F11> <Plug>(go-debug-step)
set balloonexpr=go#debug#BalloonExpr() set balloonexpr=go#debug#BalloonExpr()
set ballooneval 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 endfunction
function! s:err_cb(ch, msg) abort 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] let s:state['message'] += [a:msg]
endfunction endfunction
function! s:out_cb(ch, msg) abort 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] let s:state['message'] += [a:msg]
" TODO: why do this in this callback? if stridx(a:msg, go#config#DebugAddress()) != -1
if stridx(a:msg, g:go_debug_address) != -1 if has('nvim')
call ch_setoptions(a:ch, { let s:state['data'] = []
\ 'out_cb': function('s:logger', ['OUT: ']), let l:state = {'databuf': ''}
\ 'err_cb': function('s:logger', ['ERR: ']),
\})
" Tell dlv about the breakpoints that the user added before delve started. " explicitly bind callback to state so that within it, self will
let l:breaks = copy(s:state.breakpoint) " always refer to state. See :help Partial for more information.
let s:state['breakpoint'] = {} let l:state.on_data = function('s:on_data', [], l:state)
for l:bt in values(l:breaks) let l:ch = sockconnect('tcp', go#config#DebugAddress(), {'on_data': l:state.on_data, 'state': l:state})
call go#debug#Breakpoint(bt.line) 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
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 endfor
call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb')) call s:start_cb()
endif endif
endfunction 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 " Start the debug mode. The first argument is the package name to compile and
" debug, anything else will be passed to the running program. " debug, anything else will be passed to the running program.
function! go#debug#Start(is_test, ...) abort function! go#debug#Start(is_test, ...) abort
if has('nvim') call go#cmd#autowrite()
call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(')
return
endif
if !go#util#has_job() 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 return
endif endif
" It's already running. " It's already running.
if has_key(s:state, 'job') && job_status(s:state['job']) == 'run' if has_key(s:state, 'job')
return return s:state['job']
endif endif
let s:start_args = a:000 let s:start_args = a:000
if go#util#HasDebug('debugger-state') if go#util#HasDebug('debugger-state')
let g:go_debug_diag = s:state call go#config#SetDebugDiag(s:state)
endif 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") let dlv = go#path#CheckBinPath("dlv")
if empty(dlv) if empty(dlv)
return return
@ -539,14 +591,27 @@ function! go#debug#Start(is_test, ...) abort
try try
if len(a:000) > 0 if len(a:000) > 0
let l:pkgname = a:1 let l:pkgname = a:1
" Expand .; otherwise this won't work from a tmp dir.
if l:pkgname[0] == '.' if l:pkgname[0] == '.'
let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:] let l:pkgname = go#package#FromPath(l:pkgname)
endif endif
else else
let l:pkgname = go#package#FromPath(getcwd()) let l:pkgname = go#package#FromPath(getcwd())
endif 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 = [] let l:args = []
if len(a:000) > 1 if len(a:000) > 1
let l:args = ['--'] + a:000[1:] let l:args = ['--'] + a:000[1:]
@ -555,29 +620,40 @@ function! go#debug#Start(is_test, ...) abort
let l:cmd = [ let l:cmd = [
\ dlv, \ dlv,
\ (a:is_test ? 'test' : 'debug'), \ (a:is_test ? 'test' : 'debug'),
\ l:pkgname,
\ '--output', tempname(), \ '--output', tempname(),
\ '--headless', \ '--headless',
\ '--api-version', '2', \ '--api-version', '2',
\ '--log', \ '--listen', go#config#DebugAddress(),
\ '--listen', g:go_debug_address,
\ '--accept-multiclient',
\] \]
if get(g:, 'go_build_tags', '') isnot '' let l:debugLogOutput = go#config#DebugLogOutput()
let l:cmd += ['--build-flags', '--tags=' . g:go_build_tags] 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 endif
let l:cmd += l:args let l:cmd += l:args
call go#util#EchoProgress('Starting GoDebug...')
let s:state['message'] = [] let s:state['message'] = []
let s:state['job'] = job_start(l:cmd, { let l:opts = {
\ 'out_cb': function('s:out_cb'), \ 'for': 'GoDebug',
\ 'err_cb': function('s:err_cb'), \ 'statustype': 'debug',
\ 'exit_cb': function('s:exit'), \ 'complete': function('s:complete'),
\ 'stoponexit': 'kill', \ }
\}) 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 catch
call go#util#EchoError(v:exception) call go#util#EchoError(v:exception)
endtry endtry
return s:state['job']
endfunction endfunction
" Translate a reflect kind constant to a human string. " Translate a reflect kind constant to a human string.
@ -670,13 +746,12 @@ endfunction
function! s:eval(arg) abort function! s:eval(arg) abort
try try
let res = s:call_jsonrpc('RPCServer.State') let l:res = s:call_jsonrpc('RPCServer.State')
let goroutineID = res.result.State.currentThread.goroutineID let l:res = s:call_jsonrpc('RPCServer.Eval', {
let res = s:call_jsonrpc('RPCServer.Eval', {
\ 'expr': a:arg, \ '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 catch
call go#util#EchoError(v:exception) call go#util#EchoError(v:exception)
return '' return ''
@ -726,12 +801,11 @@ endfunction
function! go#debug#Set(symbol, value) abort function! go#debug#Set(symbol, value) abort
try try
let res = s:call_jsonrpc('RPCServer.State') let l:res = s:call_jsonrpc('RPCServer.State')
let goroutineID = res.result.State.currentThread.goroutineID
call s:call_jsonrpc('RPCServer.Set', { call s:call_jsonrpc('RPCServer.Set', {
\ 'symbol': a:symbol, \ 'symbol': a:symbol,
\ 'value': a:value, \ 'value': a:value,
\ 'scope': {'GoroutineID': goroutineID} \ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID}
\ }) \ })
catch catch
call go#util#EchoError(v:exception) call go#util#EchoError(v:exception)
@ -742,27 +816,20 @@ endfunction
function! s:update_stacktrace() abort function! s:update_stacktrace() abort
try try
let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5}) let l:res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
call s:show_stacktrace(res) call s:show_stacktrace(l:res)
catch catch
call go#util#EchoError(v:exception) call go#util#EchoError(v:exception)
endtry endtry
endfunction endfunction
function! s:stack_cb(ch, json) abort function! s:stack_cb(res) abort
let s:stack_name = '' 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 return
endif endif
call s:update_breakpoint(res) call s:update_breakpoint(a:res)
call s:update_stacktrace() call s:update_stacktrace()
call s:update_variables() call s:update_variables()
endfunction endfunction
@ -780,7 +847,7 @@ function! go#debug#Stack(name) abort
endif endif
" Add a breakpoint to the main.Main if the user didn't define any. " 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 if go#debug#Breakpoint() isnot 0
let s:state.running = 0 let s:state.running = 0
return return
@ -793,36 +860,34 @@ function! go#debug#Stack(name) abort
call s:call_jsonrpc('RPCServer.CancelNext') call s:call_jsonrpc('RPCServer.CancelNext')
endif endif
let s:stack_name = l:name 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 catch
call go#util#EchoError(v:exception) call go#util#EchoError(v:exception)
endtry endtry
endfunction endfunction
function! go#debug#Restart() abort function! go#debug#Restart() abort
try call go#cmd#autowrite()
call job_stop(s:state['job'])
while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run' try
sleep 50m call s:stop()
endwhile
let l:breaks = s:state['breakpoint']
let s:state = { let s:state = {
\ 'rpcid': 1, \ 'rpcid': 1,
\ 'running': 0, \ 'running': 0,
\ 'breakpoint': {},
\ 'currentThread': {}, \ 'currentThread': {},
\ 'localVars': {}, \ 'localVars': {},
\ 'functionArgs': {}, \ 'functionArgs': {},
\ 'message': [], \ '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) call call('go#debug#Start', s:start_args)
catch catch
call go#util#EchoError(v:exception) call go#util#EchoError(v:exception)
@ -837,47 +902,45 @@ endfunction
" Toggle breakpoint. Returns 0 on success and 1 on failure. " Toggle breakpoint. Returns 0 on success and 1 on failure.
function! go#debug#Breakpoint(...) abort function! go#debug#Breakpoint(...) abort
let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!') let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
let l:linenr = line('.')
" Get line number from argument. " Get line number from argument.
if len(a:000) > 0 if len(a:000) > 0
let linenr = str2nr(a:1) let l:linenr = str2nr(a:1)
if linenr is 0 if l:linenr is 0
call go#util#EchoError('not a number: ' . a:1) call go#util#EchoError('not a number: ' . a:1)
return 0 return 0
endif endif
else if len(a:000) > 1
let linenr = line('.') let l:filename = a:2
endif
endif endif
try try
" Check if we already have a breakpoint for this line. " Check if we already have a breakpoint for this line.
let found = v:none let l:found = {}
for k in keys(s:state.breakpoint) for l:bt in s:list_breakpoints()
let bt = s:state.breakpoint[k] if l:bt.file is# l:filename && l:bt.line is# l:linenr
if bt.file == l:filename && bt.line == linenr let l:found = l:bt
let found = bt
break break
endif endif
endfor endfor
" Remove breakpoint. " Remove breakpoint.
if type(found) == v:t_dict if type(l:found) == v:t_dict && !empty(l:found)
call remove(s:state['breakpoint'], bt.id) exe 'sign unplace '. l:found.id .' file=' . l:found.file
exe 'sign unplace '. found.id .' file=' . found.file
if s:isActive() 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 endif
" Add breakpoint. " Add breakpoint.
else else
if s:isActive() if s:isActive()
let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}}) let l:res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': l:linenr}})
let bt = res.result.Breakpoint let l:bt = res.result.Breakpoint
exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file exe 'sign place '. l:bt.id .' line=' . l:bt.line . ' name=godebugbreakpoint file=' . l:bt.file
let s:state['breakpoint'][bt.id] = bt
else else
let id = len(s:state['breakpoint']) + 1 let l:id = len(s:list_breakpoints()) + 1
let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr} exe 'sign place ' . l:id . ' line=' . l:linenr . ' name=godebugbreakpoint file=' . l:filename
exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename
endif endif
endif endif
catch catch
@ -888,17 +951,43 @@ function! go#debug#Breakpoint(...) abort
return 0 return 0
endfunction endfunction
sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint function! s:list_breakpoints()
sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent " :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() let l:signs = []
hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black let l:file = ''
hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White for l:line in split(execute('sign place'), '\n')[1:]
endfun if l:line =~# '^Signs for '
augroup vim-go-breakpoint let l:file = l:line[10:-2]
autocmd! continue
autocmd ColorScheme * call s:hi() endif
augroup end
call s:hi() 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 " 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') " don't spam the user when Vim is started in Vi compatibility mode
let g:go_decls_mode = '' let s:cpo_save = &cpo
endif set cpo&vim
function! go#decls#Decls(mode, ...) abort 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)) 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) call call("fzf#decls#cmd", [a:mode] + a:000)
else else
if globpath(&rtp, 'plugin/ctrlp.vim') != "" if globpath(&rtp, 'plugin/ctrlp.vim') != ""
@ -18,4 +19,8 @@ function! go#decls#Decls(mode, ...) abort
end end
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 = []
let s:go_stack_level = 0 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?\\?/?') let fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" so guru right now is slow for some people. previously we were using " 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 " 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 " 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 " 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 bin_name == 'godef'
if &modified let l:cmd = ['godef',
" Write current unsaved buffer to a temp file and use the modified content \ '-f=' . l:fname,
let l:tmpname = tempname() \ '-o=' . go#util#OffsetCursor(),
call writefile(go#util#GetLines(), l:tmpname) \ '-t']
let fname = l:tmpname
endif
let bin_path = go#path#CheckBinPath("godef") if &modified
if empty(bin_path) let l:stdin_content = join(go#util#GetLines(), "\n")
return call add(l:cmd, "-i")
endif let [l:out, l:err] = go#util#ExecInDir(l:cmd, l:stdin_content)
let command = printf("%s -f=%s -o=%s -t", go#util#Shellescape(bin_path), else
\ go#util#Shellescape(fname), go#util#OffsetCursor()) let [l:out, l:err] = go#util#ExecInDir(l:cmd)
let out = go#util#System(command)
if exists("l:tmpname")
call delete(l:tmpname)
endif endif
elseif bin_name == 'guru' elseif bin_name == 'guru'
let bin_path = go#path#CheckBinPath("guru") let cmd = [go#path#CheckBinPath(bin_name)]
if empty(bin_path) let buildtags = go#config#BuildTags()
return if buildtags isnot ''
let cmd += ['-tags', buildtags]
endif endif
let cmd = [bin_path]
let stdin_content = "" let stdin_content = ""
if &modified 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 let stdin_content = fname . "\n" . strlen(content) . "\n" . content
call add(cmd, "-modified") call add(cmd, "-modified")
endif endif
if exists('g:go_build_tags') call extend(cmd, ["definition", fname . ':#' . go#util#OffsetCursor()])
let tags = get(g:, 'go_build_tags')
call extend(cmd, ["-tags", tags])
endif
let fname = fname.':#'.go#util#OffsetCursor()
call extend(cmd, ["definition", fname])
if go#util#has_job() if go#util#has_job()
let l:state = {}
let l:spawn_args = { let l:spawn_args = {
\ 'cmd': cmd, \ '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 if &modified
let l:spawn_args.input = stdin_content let l:spawn_args.input = stdin_content
endif endif
call go#util#EchoProgress("searching declaration ...") call s:def_job(spawn_args, l:state)
call s:def_job(spawn_args)
return return
endif endif
let command = join(cmd, " ")
if &modified if &modified
let out = go#util#System(command, stdin_content) let [l:out, l:err] = go#util#ExecInDir(l:cmd, l:stdin_content)
else else
let out = go#util#System(command) let [l:out, l:err] = go#util#ExecInDir(l:cmd)
endif 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 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 return
endif endif
if go#util#ShellError() != 0 if l:err
call go#util#EchoError(out) call go#util#EchoError(out)
return return
endif endif
@ -85,19 +88,26 @@ function! go#def#Jump(mode) abort
call go#def#jump_to_declaration(out, a:mode, bin_name) call go#def#jump_to_declaration(out, a:mode, bin_name)
endfunction 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 if a:exit_status != 0
return return
endif endif
call go#def#jump_to_declaration(a:data[0], a:mode, a:bin_name) 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 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 function! go#def#jump_to_declaration(out, mode, bin_name) abort
let final_out = a:out let final_out = a:out
if a:bin_name == "godef" 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. " This makes it compatible with guru output.
let final_out = join(split(a:out, '\n'), ':') let final_out = join(split(a:out, '\n'), ':')
endif endif
@ -110,10 +120,24 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
let parts = split(out, ':') let parts = split(out, ':')
endif 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 filename = parts[0]
let line = parts[1] if len(parts) > 1
let col = parts[2] let line = parts[1]
let ident = parts[3] 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 " Remove anything newer than the current position, just like basic
" vim tag support " vim tag support
@ -138,7 +162,7 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
if filename != fnamemodify(expand("%"), ':p:gs?\\?/?') if filename != fnamemodify(expand("%"), ':p:gs?\\?/?')
" jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded " 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 " 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 " jumpt to existing buffer if it exists
execute bufwinnr(filename) . 'wincmd w' execute bufwinnr(filename) . 'wincmd w'
else else
@ -291,14 +315,25 @@ function! go#def#Stack(...) abort
endif endif
endfunction endfunction
function s:def_job(args) abort function s:def_job(args, state) abort
let callbacks = go#job#Spawn(a:args) let l:start_options = go#job#Options(a:args)
let start_options = { let l:state = a:state
\ 'callback': callbacks.callback, function! s:exit_cb(next, job, exitval) dict
\ 'exit_cb': callbacks.exit_cb, call call(a:next, [a:job, a:exitval])
\ 'close_cb': callbacks.close_cb, 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 if &modified
let l:tmpname = tempname() let l:tmpname = tempname()
@ -307,7 +342,11 @@ function s:def_job(args) abort
let l:start_options.in_name = l:tmpname let l:start_options.in_name = l:tmpname
endif endif
call job_start(a:args.cmd, start_options) call go#job#Start(a:args.cmd, l:start_options)
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 func! Test_jump_to_declaration_guru() abort
try try
let l:filename = 'def/jump.go' let l:filename = 'def/jump.go'
@ -34,4 +38,37 @@ func! Test_jump_to_declaration_godef() abort
endtry endtry
endfunc 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 " vim: sw=2 ts=2 et

View file

@ -2,11 +2,11 @@
" Use of this source code is governed by a BSD-style " Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file. " 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 s:buf_nr = -1
let g:go_doc_command = ["godoc"]
endif
function! go#doc#OpenBrowser(...) abort function! go#doc#OpenBrowser(...) abort
" check if we have gogetdoc as it gives us more and accurate information. " 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 " non-json output of gogetdoc
let bin_path = go#path#CheckBinPath('gogetdoc') let bin_path = go#path#CheckBinPath('gogetdoc')
if !empty(bin_path) && exists('*json_decode') if !empty(bin_path) && exists('*json_decode')
let json_out = s:gogetdoc(1) let [l:json_out, l:err] = s:gogetdoc(1)
if go#util#ShellError() != 0 if l:err
call go#util#EchoError(json_out) call go#util#EchoError(json_out)
return return
endif endif
@ -29,15 +29,13 @@ function! go#doc#OpenBrowser(...) abort
let name = out["name"] let name = out["name"]
let decl = out["decl"] let decl = out["decl"]
let godoc_url = s:custom_godoc_url() let godoc_url = go#config#DocUrl()
let godoc_url .= "/" . import let godoc_url .= "/" . import
if decl !~ "^package" if decl !~ "^package"
let godoc_url .= "#" . name let godoc_url .= "#" . name
endif endif
echo godoc_url call go#util#OpenBrowser(godoc_url)
call go#tool#OpenBrowser(godoc_url)
return return
endif endif
@ -50,28 +48,22 @@ function! go#doc#OpenBrowser(...) abort
let exported_name = pkgs[1] let exported_name = pkgs[1]
" example url: https://godoc.org/github.com/fatih/set#Set " example url: https://godoc.org/github.com/fatih/set#Set
let godoc_url = s:custom_godoc_url() . "/" . pkg . "#" . exported_name let godoc_url = go#config#DocUrl() . "/" . pkg . "#" . exported_name
call go#tool#OpenBrowser(godoc_url) call go#util#OpenBrowser(godoc_url)
endfunction endfunction
function! go#doc#Open(newmode, mode, ...) abort function! go#doc#Open(newmode, mode, ...) abort
" With argument: run "godoc [arg]". " With argument: run "godoc [arg]".
if len(a:000) if len(a:000)
if empty(go#path#CheckBinPath(g:go_doc_command[0])) let [l:out, l:err] = go#util#Exec(['go', 'doc'] + a:000)
return else " Without argument: run gogetdoc on cursor position.
endif let [l:out, l:err] = s:gogetdoc(0)
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)
if out == -1 if out == -1
return return
endif endif
endif endif
if go#util#ShellError() != 0 if l:err
call go#util#EchoError(out) call go#util#EchoError(out)
return return
endif endif
@ -97,7 +89,7 @@ function! s:GodocView(newposition, position, content) abort
if !is_visible if !is_visible
if a:position == "split" if a:position == "split"
" cap window height to 20, but resize it for smaller contents " 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")) let content_height = len(split(a:content, "\n"))
if content_height > max_height if content_height > max_height
exe 'resize ' . max_height exe 'resize ' . max_height
@ -129,39 +121,29 @@ function! s:GodocView(newposition, position, content) abort
setlocal nomodifiable setlocal nomodifiable
sil normal! gg sil normal! gg
" close easily with <esc> or enter " close easily with enter
noremap <buffer> <silent> <CR> :<C-U>close<CR> noremap <buffer> <silent> <CR> :<C-U>close<CR>
noremap <buffer> <silent> <Esc> :<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 endfunction
function! s:gogetdoc(json) abort function! s:gogetdoc(json) abort
" check if we have 'gogetdoc' and use it automatically let l:cmd = [
let bin_path = go#path#CheckBinPath('gogetdoc') \ 'gogetdoc',
if empty(bin_path) \ '-tags', go#config#BuildTags(),
return -1 \ '-pos', expand("%:p:gs!\\!/!") . ':#' . go#util#OffsetCursor()]
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]
if a:json if a:json
let cmd += ["-json"] let l:cmd += ['-json']
endif endif
let command = join(cmd, " ")
if &modified if &modified
let command .= " -modified" let l:cmd += ['-modified']
let out = go#util#System(command, go#util#archive()) return go#util#Exec(l:cmd, go#util#archive())
else
let out = go#util#System(command)
endif endif
return out return go#util#Exec(l:cmd)
endfunction endfunction
" returns the package and exported name. exported name might be empty. " returns the package and exported name. exported name might be empty.
@ -206,18 +188,8 @@ function! s:godocWord(args) abort
return [pkg, exported_name] return [pkg, exported_name]
endfunction endfunction
function! s:custom_godoc_url() abort " restore Vi compatibility settings
let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org') let &cpo = s:cpo_save
if godoc_url isnot 'https://godoc.org' unlet s:cpo_save
" 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
" vim: sw=2 ts=2 et " 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 function! go#fillstruct#FillStruct() abort
let l:cmd = ['fillstruct', let l:cmd = ['fillstruct',
\ '-file', bufname(''), \ '-file', bufname(''),
\ '-offset', go#util#OffsetCursor(), \ '-offset', go#util#OffsetCursor(),
\ '-line', line('.')] \ '-line', line('.')]
" Needs: https://github.com/davidrjenni/reftools/pull/14
"\ '-tags', go#config#BuildTags()]
" Read from stdin if modified. " Read from stdin if modified.
if &modified if &modified
@ -59,4 +65,8 @@ function! go#fillstruct#FillStruct() abort
endtry endtry
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 func! Test_fillstruct() abort
try try
let l:tmp = gotest#write_file('a/a.go', [ let l:tmp = gotest#write_file('a/a.go', [
@ -87,4 +91,8 @@ func! Test_fillstruct_two_cursor() abort
endtry endtry
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " fmt.vim: Vim command to format Go files with gofmt (and gofmt compatible
" toorls, such as goimports). " toorls, such as goimports).
if !exists("g:go_fmt_command") " don't spam the user when Vim is started in Vi compatibility mode
let g:go_fmt_command = "gofmt" let s:cpo_save = &cpo
endif set cpo&vim
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
" we have those problems : " we have those problems :
" http://stackoverflow.com/questions/12741977/prevent-vim-from-updating-its-undo-tree " 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 " this and have VimL experience, please look at the function for
" improvements, patches are welcome :) " improvements, patches are welcome :)
function! go#fmt#Format(withGoimport) abort 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 " Using winsaveview to save/restore cursor state has the problem of
" closing folds on save: " closing folds on save:
" https://github.com/fatih/vim-go/issues/502 " https://github.com/fatih/vim-go/issues/502
@ -64,18 +52,18 @@ function! go#fmt#Format(withGoimport) abort
let l:tmpname = tr(l:tmpname, '\', '/') let l:tmpname = tr(l:tmpname, '\', '/')
endif endif
let bin_name = g:go_fmt_command let bin_name = go#config#FmtCommand()
if a:withGoimport == 1 if a:withGoimport == 1
let bin_name = "goimports" let bin_name = "goimports"
endif endif
let current_col = col('.') 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('$') 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('%')) 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) let errors = s:parse_errors(expand('%'), out)
call s:show_errors(errors) call s:show_errors(errors)
endif endif
@ -83,7 +71,7 @@ function! go#fmt#Format(withGoimport) abort
" We didn't use the temp file, so clean up " We didn't use the temp file, so clean up
call delete(l:tmpname) call delete(l:tmpname)
if g:go_fmt_experimental == 1 if go#config#FmtExperimental()
" restore our undo history " restore our undo history
silent! exe 'rundo ' . tmpundofile silent! exe 'rundo ' . tmpundofile
call delete(tmpundofile) call delete(tmpundofile)
@ -154,64 +142,27 @@ endfunction
" run runs the gofmt/goimport command for the given source file and returns " 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. " the output of the executed command. Target is the real file to be formatted.
function! go#fmt#run(bin_name, source, target) function! go#fmt#run(bin_name, source, target)
let cmd = s:fmt_cmd(a:bin_name, a:source, a:target) let l:cmd = s:fmt_cmd(a:bin_name, a:source, a:target)
if empty(cmd) if empty(l:cmd)
return return
endif endif
return go#util#Exec(l:cmd)
let command = join(cmd, " ")
" execute our command...
let out = go#util#System(command)
return out
endfunction endfunction
" fmt_cmd returns a dict that contains the command to execute gofmt (or " fmt_cmd returns the command to run as a list.
" goimports). args is dict with
function! s:fmt_cmd(bin_name, source, target) function! s:fmt_cmd(bin_name, source, target)
" check if the user has installed command binary. let l:cmd = [a:bin_name, '-w']
" 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")
" add the options for binary (if any). go_fmt_options was by default of type " 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 " string, however to allow customization it's now a dictionary of binary
" name mapping to options. " name mapping to options.
let opts = g:go_fmt_options let opts = go#config#FmtOptions()
if type(g:go_fmt_options) == type({}) if type(opts) == type({})
let opts = has_key(g:go_fmt_options, a:bin_name) ? g:go_fmt_options[a:bin_name] : "" let opts = has_key(opts, a:bin_name) ? opts[a:bin_name] : ""
endif endif
call extend(cmd, split(opts, " ")) call extend(cmd, split(opts, " "))
if a:bin_name is# 'goimports'
if a:bin_name == "goimports" call extend(cmd, ["-srcdir", a:target])
" 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
endif endif
call add(cmd, a:source) call add(cmd, a:source)
@ -254,14 +205,18 @@ function! s:show_errors(errors) abort
endfunction endfunction
function! go#fmt#ToggleFmtAutoSave() abort function! go#fmt#ToggleFmtAutoSave() abort
if get(g:, "go_fmt_autosave", 1) if go#config#FmtAutosave()
let g:go_fmt_autosave = 0 call go#config#SetFmtAutosave(0)
call go#util#EchoProgress("auto fmt disabled") call go#util#EchoProgress("auto fmt disabled")
return return
end end
let g:go_fmt_autosave = 1 call go#config#SetFmtAutosave(1)
call go#util#EchoProgress("auto fmt enabled") call go#util#EchoProgress("auto fmt enabled")
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 func! Test_run_fmt() abort
let actual_file = tempname() let actual_file = tempname()
call writefile(readfile("test-fixtures/fmt/hello.go"), actual_file) call writefile(readfile("test-fixtures/fmt/hello.go"), actual_file)
@ -46,4 +50,8 @@ func! Test_goimports() abort
call assert_equal(expected, actual) call assert_equal(expected, actual)
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -1,5 +1,9 @@
" guru.vim -- Vim integration for the Go guru. " 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 " guru_cmd returns a dict that contains the command to execute guru. args
" is dict with following options: " is dict with following options:
" mode : guru mode, such as 'implements' " mode : guru mode, such as 'implements'
@ -14,14 +18,9 @@ function! s:guru_cmd(args) range abort
let format = a:args.format let format = a:args.format
let needs_scope = a:args.needs_scope let needs_scope = a:args.needs_scope
let selected = a:args.selected let selected = a:args.selected
let postype = get(a:args, 'postype', 'cursor')
let result = {} 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 "return with a warning if the binary doesn't exist
let bin_path = go#path#CheckBinPath("guru") let bin_path = go#path#CheckBinPath("guru")
@ -30,9 +29,8 @@ function! s:guru_cmd(args) range abort
endif endif
" start constructing the command " start constructing the command
let cmd = [bin_path] let cmd = [bin_path, '-tags', go#config#BuildTags()]
let filename = fnamemodify(expand("%"), ':p:gs?\\?/?')
if &modified if &modified
let result.stdin_content = go#util#archive() let result.stdin_content = go#util#archive()
call add(cmd, "-modified") call add(cmd, "-modified")
@ -43,59 +41,41 @@ function! s:guru_cmd(args) range abort
call add(cmd, "-json") call add(cmd, "-json")
endif endif
" check for any tags let scopes = go#config#GuruScope()
if exists('g:go_build_tags') if empty(scopes)
let tags = get(g:, 'go_build_tags') " some modes require scope to be defined (such as callers). For these we
call extend(cmd, ["-tags", tags]) " choose a sensible setting, which is using the current file's package
let result.tags = tags if needs_scope
endif let pkg = go#package#ImportPath()
if pkg == -1
" some modes require scope to be defined (such as callers). For these we return {'err': "current directory is not inside of a valid GOPATH"}
" choose a sensible setting, which is using the current file's package endif
let scopes = [] let scopes = [pkg]
if needs_scope
let scopes = [pkg]
endif
" check for any user defined scope setting. users can define the scope,
" in package pattern form. examples:
" golang.org/x/tools/cmd/guru # a single package
" golang.org/x/tools/... # all packages beneath dir
" ... # the entire workspace.
if exists('g:go_guru_scope')
" check that the setting is of type list
if type(get(g:, 'go_guru_scope')) != type([])
return {'err' : "go_guru_scope should of type list"}
endif endif
let scopes = get(g:, 'go_guru_scope')
endif endif
" now add the scope to our command if there is any " Add the scope.
if !empty(scopes) if !empty(scopes)
" strip trailing slashes for each path in scoped. bug: " guru expect a comma-separated list of patterns.
" 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
let l:scope = join(scopes, ",") let l:scope = join(scopes, ",")
let result.scope = l:scope let result.scope = l:scope
call extend(cmd, ["-scope", l:scope]) call extend(cmd, ["-scope", l:scope])
endif endif
let pos = printf("#%s", go#util#OffsetCursor()) if postype == 'balloon'
if selected != -1 let pos = printf("#%s", go#util#Offset(v:beval_lnum, v:beval_col))
" means we have a range, get it else
let pos1 = go#util#Offset(line("'<"), col("'<")) let pos = printf("#%s", go#util#OffsetCursor())
let pos2 = go#util#Offset(line("'>"), col("'>")) if selected != -1
let pos = printf("#%s,#%s", pos1, pos2) " 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 endif
let filename .= ':'.pos let l:filename = fnamemodify(expand("%"), ':p:gs?\\?/?') . ':' . pos
call extend(cmd, [mode, filename]) call extend(cmd, [mode, l:filename])
let result.cmd = cmd let result.cmd = cmd
return result return result
@ -119,51 +99,22 @@ function! s:sync_guru(args) abort
endif endif
endif endif
" run, forrest run!!! " run, forrest run!!!
let command = join(result.cmd, " ") if has_key(l:result, 'stdin_content')
if has_key(result, 'stdin_content') let [l:out, l:err] = go#util#Exec(l:result.cmd, l:result.stdin_content)
let out = go#util#System(command, result.stdin_content)
else else
let out = go#util#System(command) let [l:out, l:err] = go#util#Exec(l:result.cmd)
endif endif
if has_key(a:args, 'custom_parse') 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 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 endif
return out return l:out
endfunc 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 " async_guru runs guru in async mode with the given arguments
function! s:async_guru(args) abort function! s:async_guru(args) abort
let result = s:guru_cmd(a:args) let result = s:guru_cmd(a:args)
@ -172,92 +123,51 @@ function! s:async_guru(args) abort
return return
endif 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 = { let state = {
\ 'status_dir': expand('%:p:h'),
\ 'statusline_type': printf("%s", a:args.mode),
\ 'mode': 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")) \ 'parse' : get(a:args, 'custom_parse', funcref("s:parse_guru_output"))
\ } \ }
function! s:callback(chan, msg) dict " explicitly bind complete to state so that within it, self will
call add(self.messages, a:msg) " always refer to state. See :help Partial for more information.
endfunction let state.complete = function('s:complete', [], state)
function! s:exit_cb(job, exitval) dict let opts = {
let self.exited = 1 \ 'statustype': get(a:args, 'statustype', a:args.mode),
\ 'for': '_',
\ 'errorformat': "%f:%l.%c-%[%^:]%#:\ %m,%f:%l:%c:\ %m",
\ 'complete': state.complete,
\ }
let status = { if has_key(a:args, 'disable_progress')
\ 'desc': 'last status', let opts.statustype = ''
\ 'type': self.statusline_type, endif
\ 'state': "finished",
\ }
if a:exitval let opts = go#job#Options(l:opts)
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)
\ }
if has_key(result, 'stdin_content') if has_key(result, 'stdin_content')
let l:tmpname = tempname() let l:tmpname = tempname()
call writefile(split(result.stdin_content, "\n"), l:tmpname, "b") call writefile(split(result.stdin_content, "\n"), l:tmpname, "b")
let l:start_options.in_io = "file" let l:opts.in_io = "file"
let l:start_options.in_name = l:tmpname let l:opts.in_name = l:tmpname
endif endif
call go#statusline#Update(state.status_dir, { call go#job#Start(result.cmd, opts)
\ 'desc': "current status",
\ 'type': state.statusline_type,
\ 'state': "analysing",
\})
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 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 " run_guru runs the given guru argument
function! s:run_guru(args) abort 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) let res = s:async_guru(a:args)
else else
let res = s:sync_guru(a:args) let res = s:sync_guru(a:args)
@ -278,6 +188,18 @@ function! go#guru#Implements(selected) abort
call s:run_guru(args) call s:run_guru(args)
endfunction 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 " Report the possible constants, global variables, and concrete types that may
" appear in a value of type error " appear in a value of type error
function! go#guru#Whicherrs(selected) abort function! go#guru#Whicherrs(selected) abort
@ -309,7 +231,7 @@ function! go#guru#Describe(selected) abort
call s:run_guru(args) call s:run_guru(args)
endfunction endfunction
function! go#guru#DescribeInfo() abort function! go#guru#DescribeInfo(showstatus) abort
" json_encode() and friends are introduced with this patch (7.4.1304) " json_encode() and friends are introduced with this patch (7.4.1304)
" vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ " vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
" nvim: https://github.com/neovim/neovim/pull/4131 " nvim: https://github.com/neovim/neovim/pull/4131
@ -318,103 +240,103 @@ function! go#guru#DescribeInfo() abort
return return
endif 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 = { let args = {
\ 'mode': 'describe', \ 'mode': 'describe',
\ 'format': 'json', \ 'format': 'json',
\ 'selected': -1, \ 'selected': -1,
\ 'needs_scope': 0, \ 'needs_scope': 0,
\ 'custom_parse': function('s:info'), \ 'custom_parse': function('s:info'),
\ 'disable_progress': 1, \ 'disable_progress': a:showstatus == 0,
\ } \ }
call s:run_guru(args) call s:run_guru(args)
endfunction 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 " Show possible targets of selected function call
function! go#guru#Callees(selected) abort function! go#guru#Callees(selected) abort
let args = { let args = {
@ -493,7 +415,7 @@ function! go#guru#Referrers(selected) abort
call s:run_guru(args) call s:run_guru(args)
endfunction 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 " 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 " it: http://ftp.vim.org/vim/patches/7.4/7.4.330
if !exists("*matchaddpos") if !exists("*matchaddpos")
@ -516,6 +438,9 @@ function! go#guru#SameIds() abort
\ 'needs_scope': 0, \ 'needs_scope': 0,
\ 'custom_parse': function('s:same_ids_highlight'), \ 'custom_parse': function('s:same_ids_highlight'),
\ } \ }
if !a:showstatus
let args.disable_progress = 1
endif
call s:run_guru(args) call s:run_guru(args)
endfunction 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. call go#guru#ClearSameIds() " run after calling guru to reduce flicker.
if a:output[0] !=# '{' if a:output[0] !=# '{'
if !get(g:, 'go_auto_sameids', 0) if !go#config#AutoSameids()
call go#util#EchoError(a:output) call go#util#EchoError(a:output)
endif endif
return return
endif endif
let result = json_decode(a:output) 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") call go#util#EchoError("malformed output from guru")
return return
endif endif
if !has_key(result, 'sameids') 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") call go#util#EchoError("no same_ids founds for the given identifier")
endif endif
return 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)]]) call matchaddpos('goSameId', [[str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]])
endfor 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 " re-apply SameIds at the current cursor position at the time the buffer
" is redisplayed: e.g. :edit, :GoRename, etc. " is redisplayed: e.g. :edit, :GoRename, etc.
augroup vim-go-sameids augroup vim-go-sameids
autocmd! autocmd! * <buffer>
autocmd BufWinEnter <buffer> nested call go#guru#SameIds() autocmd BufWinEnter <buffer> nested call go#guru#SameIds(0)
augroup end augroup end
endif endif
endfunction endfunction
@ -592,7 +517,7 @@ function! go#guru#ClearSameIds() abort
" remove the autocmds we defined " remove the autocmds we defined
augroup vim-go-sameids augroup vim-go-sameids
autocmd! autocmd! * <buffer>
augroup end augroup end
return 0 return 0
@ -600,20 +525,20 @@ endfunction
function! go#guru#ToggleSameIds() abort function! go#guru#ToggleSameIds() abort
if go#guru#ClearSameIds() != 0 if go#guru#ClearSameIds() != 0
call go#guru#SameIds() call go#guru#SameIds(1)
endif endif
endfunction endfunction
function! go#guru#AutoToogleSameIds() abort function! go#guru#AutoToggleSameIds() abort
if get(g:, "go_auto_sameids", 0) if go#config#AutoSameids()
call go#util#EchoProgress("sameids auto highlighting disabled") call go#util#EchoProgress("sameids auto highlighting disabled")
call go#guru#ClearSameIds() call go#guru#ClearSameIds()
let g:go_auto_sameids = 0 call go#config#SetAutoSameids(0)
return return
endif endif
call go#util#EchoSuccess("sameids auto highlighting enabled") call go#util#EchoSuccess("sameids auto highlighting enabled")
let g:go_auto_sameids = 1 call go#config#SetAutoSameids(1)
endfunction endfunction
@ -648,22 +573,165 @@ endfun
function! go#guru#Scope(...) abort function! go#guru#Scope(...) abort
if a:0 if a:0
let scope = a:000
if a:0 == 1 && a:1 == '""' 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") call go#util#EchoSuccess("guru scope is cleared")
else else
let g:go_guru_scope = a:000
call go#util#EchoSuccess("guru scope changed to: ". join(a:000, ",")) call go#util#EchoSuccess("guru scope changed to: ". join(a:000, ","))
endif endif
return return
endif endif
if !exists('g:go_guru_scope') let scope = go#config#GuruScope()
if empty(scope)
call go#util#EchoError("guru scope is not set") call go#util#EchoError("guru scope is not set")
else else
call go#util#EchoSuccess("current guru scope: ". join(g:go_guru_scope, ",")) call go#util#EchoSuccess("current guru scope: ". join(scope, ","))
endif endif
endfunction 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 " 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 function! go#impl#Impl(...) abort
let recv = "" let recv = ""
let iface = "" let iface = ""
@ -101,13 +105,29 @@ function! s:root_dirs() abort
return dirs return dirs
endfunction endfunction
function! s:go_packages(dirs) abort function! s:go_packages(dirs, arglead) abort
let pkgs = [] let pkgs = []
for d in a:dirs for dir in a:dirs
let pkg_root = expand(d . '/pkg/' . go#util#osarch()) " this may expand to multiple lines
call extend(pkgs, split(globpath(pkg_root, '**/*.a', 1), "\n")) 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 endfor
return map(pkgs, "fnamemodify(v:val, ':t:r')")
return pkgs
endfunction endfunction
function! s:interface_list(pkg) abort function! s:interface_list(pkg) abort
@ -124,17 +144,32 @@ endfunction
" Complete package and interface for {interface} " Complete package and interface for {interface}
function! go#impl#Complete(arglead, cmdline, cursorpos) abort function! go#impl#Complete(arglead, cmdline, cursorpos) abort
let words = split(a:cmdline, '\s\+', 1) let words = split(a:cmdline, '\s\+', 1)
if words[-1] ==# '' if words[-1] ==# ''
return s:uniq(sort(s:go_packages(s:root_dirs()))) " if no words are given, just start completing the first package we found
elseif words[-1] =~# '^\h\w*$' return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead)))
return s:uniq(sort(filter(s:go_packages(s:root_dirs()), 'stridx(v:val, words[-1]) == 0'))) elseif words[-1] =~# '^\(\h\w.*\.\%(\h\w*\)\=$\)\@!\S*$'
elseif words[-1] =~# '^\h\w*\.\%(\h\w*\)\=$' " start matching go packages. It's negate match of the below match
let [pkg, interface] = split(words[-1], '\.', 1) return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead)))
echomsg pkg 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]'))) return s:uniq(sort(filter(s:interface_list(pkg), 'v:val =~? words[-1]')))
else else
return [] return []
endif endif
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 func! Test_impl() abort
try try
let l:tmp = gotest#write_file('a/a.go', [ let l:tmp = gotest#write_file('a/a.go', [
@ -35,3 +39,9 @@ func! Test_impl_get() abort
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
endfunc 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 " 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 function! go#import#SwitchImport(enabled, localname, path, bang) abort
let view = winsaveview() let view = winsaveview()
let path = substitute(a:path, '^\s*\(.\{-}\)\s*$', '\1', '') let path = substitute(a:path, '^\s*\(.\{-}\)\s*$', '\1', '')
@ -27,8 +32,8 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
endif endif
if a:bang == "!" if a:bang == "!"
let out = go#util#System("go get -u -v ".shellescape(path)) let [l:out, l:err] = go#util#Exec(['go', 'get', '-u', '-v', path])
if go#util#ShellError() != 0 if err != 0
call s:Error("Can't find import: " . path . ":" . out) call s:Error("Can't find import: " . path . ":" . out)
endif endif
endif endif
@ -65,6 +70,9 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
let packageline = line let packageline = line
let appendline = line let appendline = line
elseif linestr =~# '^import\s\+(\+)'
let appendline = line
let appendstr = qlocalpath
elseif linestr =~# '^import\s\+(' elseif linestr =~# '^import\s\+('
let appendstr = qlocalpath let appendstr = qlocalpath
let indentstr = 1 let indentstr = 1
@ -161,8 +169,16 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
let linesdelta += 3 let linesdelta += 3
let appendstr = qlocalpath let appendstr = qlocalpath
let indentstr = 1 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 endif
call append(appendline, appendstr)
execute appendline + 1 execute appendline + 1
if indentstr if indentstr
execute 'normal! >>' execute 'normal! >>'
@ -209,5 +225,8 @@ function! s:Error(s) abort
echohl Error | echo a:s | echohl None echohl Error | echo a:s | echohl None
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " 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 " 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. " logic.
" "
" args is a dictionary with the these keys: " args is a dictionary with the these keys:
" 'cmd':
" The value to pass to job_start().
" 'bang': " 'bang':
" Set to 0 to jump to the first error in the error list. " Set to 0 to jump to the first error in the error list.
" Defaults to 0. " Defaults to 0.
" 'statustype':
" The status type to use when updating the status.
" See statusline.vim.
" 'for': " 'for':
" The g:go_list_type_command key to use to get the error list type to use. " 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' " Defaults to '_job'
" 'errorformat':
" The errorformat string to use when parsing errors. Defaults to
" &errorformat.
" See :help 'errorformat'.
" 'complete': " 'complete':
" A function to call after the job exits and the channel is closed. The " 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 " function will be passed three arguments: the job, its exit code, and the
" list of messages received from the channel. The default value will " list of messages received from the channel. The default is a no-op. A
" process the messages and manage the error list after the job exits and " custom value can modify the messages before they are processed by the
" the channel is closed. " 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: " The return value is a dictionary with these keys:
" 'callback': " 'callback':
@ -30,22 +52,30 @@
" 'close_cb': " 'close_cb':
" A function suitable to be passed as a job close_cb handler. See " A function suitable to be passed as a job close_cb handler. See
" job-close_cb. " 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 cbs = {}
let state = { let state = {
\ 'winnr': winnr(), \ 'winid': win_getid(winnr()),
\ 'dir': getcwd(), \ 'dir': getcwd(),
\ 'jobdir': fnameescape(expand("%:p:h")), \ 'jobdir': fnameescape(expand("%:p:h")),
\ 'messages': [], \ 'messages': [],
\ 'args': a:args.cmd,
\ 'bang': 0, \ 'bang': 0,
\ 'for': "_job", \ 'for': "_job",
\ 'exited': 0, \ 'exited': 0,
\ 'exit_status': 0, \ 'exit_status': 0,
\ 'closed': 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') if has_key(a:args, 'bang')
let state.bang = a:args.bang let state.bang = a:args.bang
endif endif
@ -54,90 +84,139 @@ function go#job#Spawn(args)
let state.for = a:args.for let state.for = a:args.for
endif 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) 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 endfunction
if has_key(a:args, 'complete') if has_key(a:args, 'complete')
let state.complete = a:args.complete let state.custom_complete = a:args.complete
endif endif
function! s:callback(chan, msg) dict " explicitly bind _start to state so that within it, self will
call add(self.messages, a:msg) " always refer to state. See :help Partial for more information.
endfunction "
" _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 " explicitly bind callback to state so that within it, self will
" always refer to state. See :help Partial for more information. " always refer to state. See :help Partial for more information.
let cbs.callback = function('s:callback', [], state) 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 " explicitly bind exit_cb to state so that within it, self will always refer
" to state. See :help Partial for more information. " to state. See :help Partial for more information.
let cbs.exit_cb = function('s:exit_cb', [], state) 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 " explicitly bind close_cb to state so that within it, self will
" always refer to state. See :help Partial for more information. " always refer to state. See :help Partial for more information.
let cbs.close_cb = function('s:close_cb', [], state) let cbs.close_cb = function('s:close_cb', [], state)
function state.show_errors(job, exit_status, data) 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) let l:listtype = go#list#Type(self.for)
if a:exit_status == 0 if a:exit_status == 0
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
return return
endif endif
let l:listtype = go#list#Type(self.for) let l:listtype = go#list#Type(self.for)
if len(a:data) == 0 if len(a:data) == 0
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
return return
endif endif
let out = join(self.messages, "\n") let out = join(self.messages, "\n")
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
try try
" parse the errors relative to self.jobdir " 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) call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for)
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
finally finally
execute cd . fnameescape(self.dir) execute l:cd fnameescape(self.dir)
endtry endtry
if empty(errors) if empty(errors)
" failed to parse errors, output the original content " 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 return
endif 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)) 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) call go#list#JumpToFirst(l:listtype)
endif endif
endif endif
@ -146,4 +225,311 @@ function go#job#Spawn(args)
return cbs return cbs
endfunction 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 " 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() " don't spam the user when Vim is started in Vi compatibility mode
let bin_path = go#path#CheckBinPath("keyify") let s:cpo_save = &cpo
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') 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 return
endif endif
silent! let result = json_decode(l:out)
" 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)
" We want to output the error message in case the result isn't a JSON " We want to output the error message in case the result isn't a JSON
if type(result) != type({}) if type(result) != type({})
call go#util#EchoError(s:chomp(output)) call go#util#EchoError(s:chomp(l:out))
return return
endif endif
@ -53,4 +57,8 @@ function! s:chomp(string)
return substitute(a:string, '\n\+$', '', '') return substitute(a:string, '\n\+$', '', '')
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -1,62 +1,34 @@
if !exists("g:go_metalinter_command") " don't spam the user when Vim is started in Vi compatibility mode
let g:go_metalinter_command = "" let s:cpo_save = &cpo
endif set cpo&vim
if !exists("g:go_metalinter_autosave_enabled") function! go#lint#Gometa(bang, autosave, ...) abort
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
if a:0 == 0 if a:0 == 0
let goargs = [expand('%:p:h')] let goargs = [expand('%:p:h')]
else else
let goargs = a:000 let goargs = a:000
endif endif
let bin_path = go#path#CheckBinPath("gometalinter") let l:metalinter = go#config#MetalinterCommand()
if empty(bin_path)
return
endif
let cmd = [bin_path] if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint'
let cmd += ["--disable-all"] let cmd = s:metalintercmd(l:metalinter)
if empty(cmd)
return
endif
if a:autosave || empty(g:go_metalinter_command)
" linters " 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 for linter in linters
let cmd += ["--enable=".linter] let cmd += ["--enable=".linter]
endfor endfor
for linter in g:go_metalinter_disabled for linter in go#config#MetalinterDisabled()
let cmd += ["--disable=".linter] let cmd += ["--disable=".linter]
endfor 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 else
" the user wants something else, let us use it. " the user wants something else, let us use it.
let cmd += split(g:go_metalinter_command, " ") let cmd = split(go#config#MetalinterCommand(), " ")
endif endif
if a:autosave if a:autosave
@ -64,37 +36,44 @@ function! go#lint#Gometa(autosave, ...) abort
" will be cleared " will be cleared
redraw redraw
" Include only messages for the active buffer for autosave. if l:metalinter == "gometalinter"
let cmd += [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))] " Include only messages for the active buffer for autosave.
endif let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))]
if go#util#has_job()
" gometalinter has a default deadline of 5 seconds. let include = [printf('--include=^%s:.*$', expand('%:p:t'))]
" endif
" For async mode (s:lint_job), we want to override the default deadline only let cmd += include
" if we have a deadline configured. elseif l:metalinter == "golangci-lint"
" let goargs[0] = expand('%:p')
" 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]
endif endif
let cmd += goargs
call s:lint_job({'cmd': cmd}, a:autosave)
return
endif endif
" We're calling gometalinter synchronously. " Call metalinter asynchronously.
let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")] let deadline = go#config#MetalinterDeadline()
if deadline != ''
let cmd += ["--deadline=" . deadline]
endif
let cmd += goargs 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) let [l:out, l:err] = go#util#Exec(cmd)
if a:autosave if a:autosave
@ -107,48 +86,46 @@ function! go#lint#Gometa(autosave, ...) abort
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None
else else
" GoMetaLinter can output one of the two, so we look for both: let l:winid = win_getid(winnr())
" <file>:<line>:[<column>]: <message> (<linter>)
" <file>:<line>:: <message> (<linter>)
" This can be defined by the following errorformat:
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m"
" Parse and populate our location list " Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter') call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !a:autosave if a:autosave || a:bang
call go#list#JumpToFirst(l:listtype) call win_gotoid(l:winid)
return
endif endif
call go#list#JumpToFirst(l:listtype)
endif endif
endfunction endfunction
" Golint calls 'golint' on the current directory. Any warnings are populated in " Golint calls 'golint' on the current directory. Any warnings are populated in
" the location list " the location list
function! go#lint#Golint(...) abort function! go#lint#Golint(bang, ...) 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)
if a:0 == 0 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 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 endif
if empty(out) if empty(l:out)
echon "vim-go: " | echohl Function | echon "[lint] PASS" | echohl None call go#util#EchoSuccess('[lint] PASS')
return return
endif endif
let l:winid = win_getid(winnr())
let l:listtype = go#list#Type("GoLint") let l:listtype = go#list#Type("GoLint")
call go#list#Parse(l:listtype, out, "GoLint") call go#list#Parse(l:listtype, l:out, "GoLint")
let errors = go#list#Get(l:listtype) let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) 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) call go#list#JumpToFirst(l:listtype)
endfunction endfunction
@ -156,202 +133,152 @@ endfunction
" the location list " the location list
function! go#lint#Vet(bang, ...) abort function! go#lint#Vet(bang, ...) abort
call go#cmd#autowrite() 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 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 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 endif
let l:listtype = go#list#Type("GoVet") let l:listtype = go#list#Type("GoVet")
if go#util#ShellError() != 0 if l:err != 0
let errorformat="%-Gexit status %\\d%\\+," . &errorformat let l:winid = win_getid(winnr())
let errorformat = "%-Gexit status %\\d%\\+," . &errorformat
call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet") call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet")
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
else
call win_gotoid(l:winid)
endif endif
echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None call go#util#EchoSuccess('[vet] PASS')
endif endif
endfunction endfunction
" ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in " ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in
" the location list " the location list
function! go#lint#Errcheck(...) abort function! go#lint#Errcheck(bang, ...) abort
if a:0 == 0 if a:0 == 0
let import_path = go#package#ImportPath() let l:import_path = go#package#ImportPath()
if import_path == -1 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 return
endif endif
else else
let import_path = go#util#Shelljoin(a:000) let l:import_path = join(a:000, ' ')
endif endif
let bin_path = go#path#CheckBinPath(g:go_errcheck_bin) call go#util#EchoProgress('[errcheck] analysing ...')
if empty(bin_path)
return
endif
echon "vim-go: " | echohl Identifier | echon "errcheck analysing ..." | echohl None
redraw redraw
let command = go#util#Shellescape(bin_path) . ' -abspath ' . import_path let [l:out, l:err] = go#util#Exec([go#config#ErrcheckBin(), '-abspath', l:import_path])
let out = go#tool#ExecuteInDir(command)
let l:listtype = go#list#Type("GoErrCheck") 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" let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m"
" Parse and populate our location list " Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck') call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck')
let errors = go#list#Get(l:listtype) let l:errors = go#list#Get(l:listtype)
if empty(errors) if empty(l:errors)
echohl Error | echomsg "GoErrCheck returned error" | echohl None call go#util#EchoError(l:out)
echo out
return return
endif endif
if !empty(errors) if !empty(errors)
echohl Error | echomsg "GoErrCheck found errors" | echohl None
call go#list#Populate(l:listtype, errors, 'Errcheck') call go#list#Populate(l:listtype, errors, 'Errcheck')
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) if !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
else
call win_gotoid(l:winid)
endif endif
endif endif
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None call go#util#EchoSuccess('[errcheck] PASS')
endif endif
endfunction endfunction
function! go#lint#ToggleMetaLinterAutoSave() abort function! go#lint#ToggleMetaLinterAutoSave() abort
if get(g:, "go_metalinter_autosave", 0) if go#config#MetalinterAutosave()
let g:go_metalinter_autosave = 0 call go#config#SetMetalinterAutosave(0)
call go#util#EchoProgress("auto metalinter disabled") call go#util#EchoProgress("auto metalinter disabled")
return return
end end
let g:go_metalinter_autosave = 1 call go#config#SetMetalinterAutosave(1)
call go#util#EchoProgress("auto metalinter enabled") call go#util#EchoProgress("auto metalinter enabled")
endfunction endfunction
function! s:lint_job(args, autosave) function! s:lint_job(args, bang, autosave)
let state = { let l:opts = {
\ 'status_dir': expand('%:p:h'), \ 'statustype': a:args.statustype,
\ 'started_at': reltime(), \ 'errorformat': a:args.errformat,
\ 'messages': [], \ 'for': "GoMetaLinter",
\ 'exited': 0, \ 'bang': a:bang,
\ 'closed': 0, \ }
\ 'exit_status': 0,
\ 'winnr': winnr(),
\ 'autosave': a:autosave
\ }
call go#statusline#Update(state.status_dir, { if a:autosave
\ 'desc': "current status", let l:opts.for = "GoMetaLinterAutoSave"
\ 'type': "gometalinter", endif
\ 'state': "analysing",
\})
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
if a:autosave call go#job#Spawn(a:args.cmd, l:opts)
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
endfunction 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 " 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 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' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go' silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [ try
\ {'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)'} 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 " clear the quickfix lists
call setqflist([], 'r') call setqflist([], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will let g:go_metalinter_enabled = ['golint']
" 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 orig_go_metalinter_enabled = g:go_metalinter_enabled call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
let g:go_metalinter_enabled = ['golint']
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() 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) call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_enabled = orig_go_metalinter_enabled finally
unlet g:go_metalinter_enabled
endtry
endfunc endfunc
func! Test_GometaWithDisabled() abort 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' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go' silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [ try
\ {'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)'} 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 " clear the quickfix lists
call setqflist([], 'r') call setqflist([], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will let g:go_metalinter_disabled = ['vet']
" 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 orig_go_metalinter_disabled = g:go_metalinter_disabled call go#lint#Gometa(0, 0, $GOPATH . '/src/foo')
let g:go_metalinter_disabled = ['vet']
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() 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) call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_disabled = orig_go_metalinter_disabled finally
unlet g:go_metalinter_disabled
endtry
endfunc endfunc
func! Test_GometaAutoSave() abort 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' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go' silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
let expected = [ try
\ {'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 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 " clear the location lists
call setloclist(l:winnr, [], 'r') call setloclist(l:winnr, [], 'r')
" call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will let g:go_metalinter_autosave_enabled = ['golint']
" 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 orig_go_metalinter_autosave_enabled = g:go_metalinter_autosave_enabled call go#lint#Gometa(0, 1)
let g:go_metalinter_autosave_enabled = ['golint']
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) 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) call gotest#assert_quickfix(actual, expected)
let g:go_metalinter_autosave_enabled = orig_go_metalinter_autosave_enabled finally
unlet g:go_metalinter_autosave_enabled
endtry
endfunc endfunc
func! Test_Vet() func! Test_Vet() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/vet/vet.go' silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
compiler go compiler go
let expected = [ 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() let winnr = winnr()
@ -128,4 +145,8 @@ func! Test_Vet()
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(actual, expected)
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -1,10 +1,6 @@
if !exists("g:go_list_type") " don't spam the user when Vim is started in Vi compatibility mode
let g:go_list_type = "" let s:cpo_save = &cpo
endif set cpo&vim
if !exists("g:go_list_type_commands")
let g:go_list_type_commands = {}
endif
" Window opens the list with the given height up to 10 lines maximum. " Window opens the list with the given height up to 10 lines maximum.
" Otherwise g:go_loclist_height is used. " Otherwise g:go_loclist_height is used.
@ -22,7 +18,7 @@ function! go#list#Window(listtype, ...) abort
return return
endif endif
let height = get(g:, "go_list_height", 0) let height = go#config#ListHeight()
if height == 0 if height == 0
" prevent creating a large location height for a large set of numbers " prevent creating a large location height for a large set of numbers
if a:1 > 10 if a:1 > 10
@ -113,7 +109,7 @@ endfunction
" Close closes the location list " Close closes the location list
function! go#list#Close(listtype) abort function! go#list#Close(listtype) abort
let autoclose_window = get(g:, 'go_list_autoclose', 1) let autoclose_window = go#config#ListAutoclose()
if !autoclose_window if !autoclose_window
return return
endif endif
@ -126,13 +122,12 @@ function! go#list#Close(listtype) abort
endfunction endfunction
function! s:listtype(listtype) abort function! s:listtype(listtype) abort
if g:go_list_type == "locationlist" let listtype = go#config#ListType()
return "locationlist" if empty(listtype)
elseif g:go_list_type == "quickfix" return a:listtype
return "quickfix"
endif endif
return a:listtype return listtype
endfunction endfunction
" s:default_list_type_commands is the defaults that will be used for each of " 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. " in g:go_list_type_commands.
let s:default_list_type_commands = { let s:default_list_type_commands = {
\ "GoBuild": "quickfix", \ "GoBuild": "quickfix",
\ "GoDebug": "quickfix",
\ "GoErrCheck": "quickfix", \ "GoErrCheck": "quickfix",
\ "GoFmt": "locationlist", \ "GoFmt": "locationlist",
\ "GoGenerate": "quickfix", \ "GoGenerate": "quickfix",
@ -150,6 +146,7 @@ let s:default_list_type_commands = {
\ "GoLint": "quickfix", \ "GoLint": "quickfix",
\ "GoMetaLinter": "quickfix", \ "GoMetaLinter": "quickfix",
\ "GoMetaLinterAutoSave": "locationlist", \ "GoMetaLinterAutoSave": "locationlist",
\ "GoModFmt": "locationlist",
\ "GoModifyTags": "locationlist", \ "GoModifyTags": "locationlist",
\ "GoRename": "quickfix", \ "GoRename": "quickfix",
\ "GoRun": "quickfix", \ "GoRun": "quickfix",
@ -169,7 +166,11 @@ function! go#list#Type(for) abort
let l:listtype = "quickfix" let l:listtype = "quickfix"
endif endif
return get(g:go_list_type_commands, a:for, l:listtype) return get(go#config#ListTypeCommands(), a:for, l:listtype)
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " This file provides a utility function that performs auto-completion of
" package names, for use by other commands. " 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:goos = $GOOS
let s:goarch = $GOARCH let s:goarch = $GOARCH
@ -28,7 +32,7 @@ if len(s:goarch) == 0
endif endif
endif endif
function! go#package#Paths() abort function! s:paths() abort
let dirs = [] let dirs = []
if !exists("s:goroot") if !exists("s:goroot")
@ -54,63 +58,119 @@ function! go#package#Paths() abort
return dirs return dirs
endfunction 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 = {} 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 function! go#package#ImportPath() abort
let dir = expand("%:p:h") let dir = expand("%:p:h")
if has_key(s:import_paths, dir) if has_key(s:import_paths, dir)
return s:import_paths[dir] return s:import_paths[dir]
endif endif
let out = go#tool#ExecuteInDir("go list") let [l:out, l:err] = go#util#ExecInDir(['go', 'list'])
if go#util#ShellError() != 0 if l:err != 0
return -1 return -1
endif 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. " go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH.
" Check it and retun an error if that is the case " Check it and retun an error if that is the case
if import_path[0] ==# '_' if l:importpath[0] ==# '_'
return -1 return -1
endif endif
let s:import_paths[dir] = import_path let s:import_paths[dir] = l:importpath
return import_path return l:importpath
endfunction endfunction
" FromPath returns the import path of arg.
function! go#package#FromPath(arg) abort function! go#package#FromPath(arg) abort
let path = fnamemodify(resolve(a:arg), ':p') let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let dirs = go#package#Paths() let l:dir = getcwd()
for dir in dirs let l:path = a:arg
if len(dir) && match(path, dir) == 0 if !isdirectory(l:path)
let workspace = dir let l:path = fnamemodify(l:path, ':h')
break endif
endif
endfor
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 return -1
endif endif
let path = substitute(path, '/*$', '', '') let l:importpath = split(l:out, '\n')[0]
let workspace = substitute(workspace . '/src/', '/+', '', '')
if isdirectory(path) " go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH.
return substitute(path, workspace, '', '') " Check it and retun an error if that is the case
else if l:importpath[0] ==# '_'
return substitute(substitute(path, workspace, '', ''), return -1
\ '/' . fnamemodify(path, ':t'), '', '')
endif endif
return l:importpath
endfunction endfunction
function! go#package#CompleteMembers(package, member) abort function! go#package#CompleteMembers(package, member) abort
silent! let content = go#util#System('godoc ' . a:package) let [l:content, l:err] = go#util#Exec(['go', 'doc', a:package])
if go#util#ShellError() || !len(content) if l:err || !len(content)
return [] return []
endif endif
let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'") let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'")
try try
let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*' 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]) return go#package#CompleteMembers(words[1], words[2])
endif endif
let dirs = go#package#Paths() let dirs = s:paths()
let module = s:module()
if len(dirs) == 0 if len(dirs) == 0 && empty(module)
" should not happen " should not happen
return [] return []
endif endif
let ret = {} let vendordirs = s:vendordirs()
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')
" without this the result can have duplicates in form of let ret = {}
" 'encoding/json' and '/encoding/json/' for dir in dirs
let i = go#util#StripPathSep(i) " 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 if !empty(module) && dir ==# module.dir
endfor if stridx(a:ArgLead, module.path) == 0
endfor 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 endfor
return sort(keys(ret)) endfor
return sort(keys(ret))
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " 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 " 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 " 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. " BinPath returns the binary path of installed go tools.
function! go#path#BinPath() abort 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 " check if our global custom path is set, if not check if $GOBIN is set so
" we can use it, otherwise use default GOPATH " we can use it, otherwise use default GOPATH
if exists("g:go_bin_path") if $GOBIN != ""
let bin_path = g:go_bin_path
elseif $GOBIN != ""
let bin_path = $GOBIN let bin_path = $GOBIN
else else
let go_paths = split(go#path#Default(), go#util#PathListSep()) let go_paths = split(go#path#Default(), go#util#PathListSep())
@ -141,11 +146,13 @@ function! go#path#BinPath() abort
endfunction endfunction
" CheckBinPath checks whether the given binary exists or not and returns the " 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 function! go#path#CheckBinPath(binpath) abort
" remove whitespaces if user applied something like 'goimports ' " remove whitespaces if user applied something like 'goimports '
let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '') let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '')
" save off original path
" save original path
let old_path = $PATH let old_path = $PATH
" check if we have an appropriate bin_path " check if we have an appropriate bin_path
@ -153,7 +160,12 @@ function! go#path#CheckBinPath(binpath) abort
if !empty(go_bin_path) if !empty(go_bin_path)
" append our GOBIN and GOPATH paths and be sure they can be found there... " append our GOBIN and GOPATH paths and be sure they can be found there...
" let us search in our GOBIN and GOPATH paths " 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 endif
" if it's in PATH just return it " if it's in PATH just return it
@ -193,4 +205,44 @@ function! s:CygwinPath(path)
return substitute(a:path, '\\', '/', "g") return substitute(a:path, '\\', '/', "g")
endfunction 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 " vim: sw=2 ts=2 et

View file

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

View file

@ -1,26 +1,14 @@
if !exists("g:go_gorename_bin") " don't spam the user when Vim is started in Vi compatibility mode
let g:go_gorename_bin = "gorename" let s:cpo_save = &cpo
endif set cpo&vim
" 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()
function! go#rename#Rename(bang, ...) abort function! go#rename#Rename(bang, ...) abort
call s:default()
let to_identifier = "" let to_identifier = ""
if a:0 == 0 if a:0 == 0
let ask = printf("vim-go: rename '%s' to: ", expand("<cword>")) let ask = printf("vim-go: rename '%s' to: ", expand("<cword>"))
if g:go_gorename_prefill != '' let prefill = go#config#GorenamePrefill()
let to_identifier = input(ask, eval(g:go_gorename_prefill)) if prefill != ''
let to_identifier = input(ask, eval(prefill))
else else
let to_identifier = input(ask) let to_identifier = input(ask)
endif endif
@ -33,7 +21,7 @@ function! go#rename#Rename(bang, ...) abort
endif endif
" return with a warning if the bin doesn't exist " 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) if empty(bin_path)
return return
endif endif
@ -41,22 +29,9 @@ function! go#rename#Rename(bang, ...) abort
let fname = expand('%:p') let fname = expand('%:p')
let pos = go#util#OffsetCursor() let pos = go#util#OffsetCursor()
let offset = printf('%s:#%d', fname, pos) let offset = printf('%s:#%d', fname, pos)
let cmd = [bin_path, "-offset", offset, "-to", to_identifier, '-tags', go#config#BuildTags()]
" 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
if go#util#has_job() if go#util#has_job()
call go#util#EchoProgress(printf("renaming to '%s' ...", to_identifier))
call s:rename_job({ call s:rename_job({
\ 'cmd': cmd, \ 'cmd': cmd,
\ 'bang': a:bang, \ 'bang': a:bang,
@ -64,71 +39,42 @@ function! go#rename#Rename(bang, ...) abort
return return
endif endif
let command = join(cmd, " ") let [l:out, l:err] = go#util#ExecInDir(l:cmd)
let out = go#tool#ExecuteInDir(command) call s:parse_errors(l:err, a:bang, split(l:out, '\n'))
let splitted = split(out, '\n')
call s:parse_errors(go#util#ShellError(), a:bang, splitted)
endfunction endfunction
function s:rename_job(args) function s:rename_job(args)
let state = { let l:job_opts = {
\ 'exited': 0, \ 'bang': a:args.bang,
\ 'closed': 0, \ 'for': 'GoRename',
\ 'exitval': 0, \ 'statustype': 'gorename',
\ '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),
\ } \ }
call go#statusline#Update(state.status_dir, { " autowrite is not enabled for jobs
\ 'desc': "current status", call go#cmd#autowrite()
\ 'type': "gorename", let l:cbs = go#job#Options(l:job_opts)
\ 'state': "started",
\})
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 endfunction
function s:parse_errors(exit_val, bang, out) 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") let l:listtype = go#list#Type("GoRename")
if a:exit_val != 0 if a:exit_val != 0
call go#util#EchoError("FAILED") let errors = go#util#ParseErrors(a:out)
let errors = go#tool#ParseErrors(a:out)
call go#list#Populate(l:listtype, errors, 'Rename') call go#list#Populate(l:listtype, errors, 'Rename')
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang if !empty(errors) && !a:bang
@ -163,9 +108,6 @@ function s:parse_errors(exit_val, bang, out)
call go#util#EchoSuccess(a:out[0]) call go#util#EchoSuccess(a:out[0])
" refresh the buffer so we can see the new content " 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" silent execute ":e"
endfunction endfunction
@ -178,4 +120,8 @@ function! go#rename#Complete(lead, cmdline, cursor)
\ 'strpart(v:val, 0, len(a:lead)) == a:lead') \ 'strpart(v:val, 0, len(a:lead)) == a:lead')
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " Statusline
"""""""""""""""""""""""""""""""" """"""""""""""""""""""""""""""""
@ -26,7 +30,7 @@ function! go#statusline#Show() abort
" lazy initialiation of the cleaner " lazy initialiation of the cleaner
if !s:timer_id if !s:timer_id
" clean every 60 seconds all statuses " 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}) let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1})
endif endif
@ -54,11 +58,11 @@ function! go#statusline#Show() abort
" only update highlight if status has changed. " only update highlight if status has changed.
if status_text != s:last_status if status_text != s:last_status
if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass" 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" 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" 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
endif endif
@ -109,4 +113,8 @@ function! go#statusline#Clear(timer_id) abort
exe 'let &ro = &ro' exe 'let &ro = &ro'
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " mapped to :GoAddTags
function! go#tags#Add(start, end, count, ...) abort function! go#tags#Add(start, end, count, ...) abort
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') 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 let args["modified"] = 1
endif endif
let result = s:create_cmd(args) let l:result = s:create_cmd(args)
if has_key(result, 'err') if has_key(result, 'err')
call go#util#EchoError(result.err) call go#util#EchoError(result.err)
return -1 return -1
endif endif
let command = join(result.cmd, " ")
if &modified if &modified
let filename = expand("%:p:gs!\\!/!") let filename = expand("%:p:gs!\\!/!")
let content = join(go#util#GetLines(), "\n") let content = join(go#util#GetLines(), "\n")
let in = filename . "\n" . strlen(content) . "\n" . content 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 else
let out = go#util#System(command) let [l:out, l:err] = go#util#Exec(l:result.cmd)
endif endif
if go#util#ShellError() != 0 if l:err != 0
call go#util#EchoError(out) call go#util#EchoError(out)
return return
endif endif
@ -115,19 +117,18 @@ func s:create_cmd(args) abort
if empty(bin_path) if empty(bin_path)
return {'err': "gomodifytags does not exist"} return {'err': "gomodifytags does not exist"}
endif endif
let bin_path = go#util#Shellescape(bin_path)
let l:start = a:args.start let l:start = a:args.start
let l:end = a:args.end let l:end = a:args.end
let l:offset = a:args.offset let l:offset = a:args.offset
let l:mode = a:args.mode let l:mode = a:args.mode
let l:cmd_args = a:args.cmd_args 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 " start constructing the command
let cmd = [bin_path] let cmd = [bin_path]
call extend(cmd, ["-format", "json"]) 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]) call extend(cmd, ["-transform", l:modifytags_transform])
if has_key(a:args, "modified") if has_key(a:args, "modified")
@ -162,17 +163,17 @@ func s:create_cmd(args) abort
endfor endfor
endif 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) if !empty(l:options)
call extend(cmd, ["-add-options", join(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 endif
elseif l:mode == "remove" elseif l:mode == "remove"
if empty(l:cmd_args) if empty(l:cmd_args)
@ -211,4 +212,8 @@ func s:create_cmd(args) abort
return {'cmd': cmd} return {'cmd': cmd}
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 try
let l:tmp = gotest#load_fixture('tags/add_all_input.go') let l:tmp = gotest#load_fixture('tags/add_all_input.go')
silent call go#tags#run(0, 0, 40, "add", bufname(''), 1) silent call go#tags#run(0, 0, 40, "add", bufname(''), 1)
@ -9,6 +13,28 @@ func! Test_add_tags() abort
endfunc 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 func! Test_remove_tags() abort
try try
let l:tmp = gotest#load_fixture('tags/remove_all_input.go') let l:tmp = gotest#load_fixture('tags/remove_all_input.go')
@ -19,4 +45,8 @@ func! Test_remove_tags() abort
endtry endtry
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim:ts=2:sts=2:sw=2:et " 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>") let s:current_file = expand("<sfile>")
function! go#template#create() abort 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 l:root_dir = fnamemodify(s:current_file, ':h:h:h')
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let l:package_name = go#tool#PackageName()
let dir = getcwd()
let l:package_name = -1
if isdirectory(expand('%:p:h')) " if we can't figure out any package name (i.e. no Go files in the directory)
execute cd . fnameescape(expand('%:p:h')) " from the directory create the template or use the directory as the name.
let l:package_name = go#tool#PackageName() if l:package_name == -1
endif if l:go_template_use_pkg == 1
let l:path = fnamemodify(expand('%:p:h'), ':t')
" if we can't figure out any package name(no Go files or non Go package let l:content = printf("package %s", l:path)
" files) from the directory create the template or use the cwd call append(0, l:content)
" 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")
else 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 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 else
let l:content = printf("package %s", l:package_name) let l:content = printf("package %s", l:package_name)
call append(0, l:content) call append(0, l:content)
endif endif
$delete _ " 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.
execute cd . fnameescape(dir) if getline('$') is ''
$delete _
endif
endfunction endfunction
function! go#template#ToggleAutoCreate() abort function! go#template#ToggleAutoCreate() abort
if get(g:, "go_template_autocreate", 1) if go#config#TemplateAutocreate()
let g:go_template_autocreate = 0 call go#config#SetTemplateAutocreate(0)
call go#util#EchoProgress("auto template create disabled") call go#util#EchoProgress("auto template create disabled")
return return
end end
let g:go_template_autocreate = 1 call go#config#SetTemplateAutocreate(1)
call go#util#EchoProgress("auto template create enabled") call go#util#EchoProgress("auto template create enabled")
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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") " don't spam the user when Vim is started in Vi compatibility mode
let g:go_term_mode = 'vsplit' let s:cpo_save = &cpo
endif set cpo&vim
" new creates a new terminal with the given command. Mode is set based on the " 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 " global variable g:go_term_mode, which is by default set to :vsplit
function! go#term#new(bang, cmd) abort function! go#term#new(bang, cmd, errorformat) abort
return go#term#newmode(a:bang, a:cmd, g:go_term_mode) return go#term#newmode(a:bang, a:cmd, a:errorformat, go#config#TermMode())
endfunction endfunction
" new creates a new terminal with the given command and window mode. " go#term#newmode creates a new terminal with the given command and window mode.
function! go#term#newmode(bang, cmd, mode) abort function! go#term#newmode(bang, cmd, errorformat, mode) abort
let mode = a:mode let l:mode = a:mode
if empty(mode) if empty(l:mode)
let mode = g:go_term_mode let l:mode = go#config#TermMode()
endif endif
let state = { let l:state = {
\ 'cmd': a:cmd, \ 'cmd': a:cmd,
\ 'bang' : a:bang, \ 'bang' : a:bang,
\ 'winid': win_getid(winnr()), \ 'winid': win_getid(winnr()),
\ 'stdout': [] \ 'stdout': [],
\ 'stdout_buf': '',
\ 'errorformat': a:errorformat,
\ } \ }
" execute go build in the files directory " execute go build in the files directory
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let dir = getcwd() 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 filetype=goterm
setlocal bufhidden=delete 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 " 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 " on_stdout. See https://github.com/neovim/neovim/issues/2836
let job = { let l:job = {
\ 'on_stdout': function('s:on_stdout', [], state), \ 'on_stdout': function('s:on_stdout', [], state),
\ 'on_exit' : function('s:on_exit', [], state), \ 'on_exit' : function('s:on_exit', [], state),
\ } \ }
let state.id = termopen(a:cmd, job) let l:state.id = termopen(a:cmd, l:job)
let state.termwinid = win_getid(winnr()) let l:state.termwinid = win_getid(winnr())
execute cd . fnameescape(dir) execute l:cd . fnameescape(l:dir)
" resize new term if needed. " resize new term if needed.
let height = get(g:, 'go_term_height', winheight(0)) let l:height = go#config#TermHeight()
let width = get(g:, 'go_term_width', winwidth(0)) let l:width = go#config#TermWidth()
" Adjust the window width or height depending on whether it's a vertical or " Adjust the window width or height depending on whether it's a vertical or
" horizontal split. " horizontal split.
if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew" if l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew"
exe 'vertical resize ' . width exe 'vertical resize ' . l:width
elseif mode =~ "split" || mode =~ "new" elseif mode =~ "split" || mode =~ "new"
exe 'resize ' . height exe 'resize ' . l:height
endif endif
" we also need to resize the pty, so there you go... " 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 endfunction
function! s:on_stdout(job_id, data, event) dict abort 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 endfunction
function! s:on_exit(job_id, exit_status, event) dict abort 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") let l:listtype = go#list#Type("_term")
" usually there is always output so never branch into this clause if a:exit_status == 0
if empty(self.stdout) call go#list#Clean(l:listtype)
call s:cleanlist(self.winid, l:listtype) call win_gotoid(l:winid)
return return
endif endif
let errors = go#tool#ParseErrors(self.stdout) call win_gotoid(self.winid)
let errors = go#tool#FilterValids(errors)
if !empty(errors) let l:title = self.cmd
" close terminal; we don't need it anymore if type(l:title) == v:t_list
call win_gotoid(self.termwinid) let l:title = join(self.cmd)
close 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#ParseFormat(l:listtype, self.errorformat, self.stdout, l:title)
call go#list#Window(l:listtype, len(errors)) let l:errors = go#list#Get(l:listtype)
if !self.bang call go#list#Window(l:listtype, len(l:errors))
call go#list#JumpToFirst(l:listtype)
endif
if empty(l:errors)
call go#util#EchoError( '[' . l:title . '] ' . "FAIL")
call win_gotoid(l:winid)
return return
endif 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 endfunction
function! s:cleanlist(winid, listtype) abort " restore Vi compatibility settings
" There are no errors. Clean and close the list. Jump to the window to which let &cpo = s:cpo_save
" the location list is attached, close the list, and then jump back to the unlet s:cpo_save
" current window.
let winid = win_getid(winnr())
call win_gotoid(a:winid)
call go#list#Clean(a:listtype)
call win_gotoid(l:winid)
endfunction
" vim: sw=2 ts=2 et " 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() func! Test_GoTermNewMode()
if !has('nvim') if !has('nvim')
return return
@ -13,7 +17,7 @@ func! Test_GoTermNewMode()
let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set nosplitright set nosplitright
call go#term#newmode(0, cmd, '') call go#term#new(0, cmd, &errorformat)
let actual = expand('%:p') let actual = expand('%:p')
call assert_equal(actual, l:expected) call assert_equal(actual, l:expected)
@ -37,7 +41,7 @@ func! Test_GoTermNewMode_SplitRight()
let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set splitright set splitright
call go#term#newmode(0, cmd, '') call go#term#new(0, cmd, &errorformat)
let actual = expand('%:p') let actual = expand('%:p')
call assert_equal(actual, l:expected) call assert_equal(actual, l:expected)
@ -47,4 +51,8 @@ func! Test_GoTermNewMode_SplitRight()
endtry endtry
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 " 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 " 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. " test files). Any other argument is appended to the final `go test` command.
function! go#test#Test(bang, compile, ...) abort 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. " don't run the test, only compile it. Useful to capture and fix errors.
if a:compile if a:compile
@ -10,11 +14,6 @@ function! go#test#Test(bang, compile, ...) abort
call extend(args, ["-c", "-o", testfile]) call extend(args, ["-c", "-o", testfile])
endif endif
if exists('g:go_build_tags')
let tags = get(g:, 'go_build_tags')
call extend(args, ["-tags", tags])
endif
if a:0 if a:0
let goargs = a:000 let goargs = a:000
@ -24,18 +23,35 @@ function! go#test#Test(bang, compile, ...) abort
let goargs = map(copy(a:000), "expand(v:val)") let goargs = map(copy(a:000), "expand(v:val)")
endif endif
if !(has('nvim') || go#util#has_job())
let goargs = go#util#Shelllist(goargs, 1)
endif
call extend(args, goargs, 1) call extend(args, goargs, 1)
else else
" only add this if no custom flags are passed " 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)) call add(args, printf("-timeout=%s", timeout))
endif 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 if a:compile
call go#util#EchoProgress("compiling tests ...") call go#util#EchoProgress("compiling tests ...")
else else
@ -43,35 +59,12 @@ function! go#test#Test(bang, compile, ...) abort
endif endif
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() call go#cmd#autowrite()
redraw redraw
let command = "go " . join(args, ' ') let l:cmd = ['go'] + l:args
let out = go#tool#ExecuteInDir(command)
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
" TODO(bc): When the output is JSON, the JSON should be run through a " 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. " 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() let dir = getcwd()
execute cd fnameescape(expand("%:p:h")) execute cd fnameescape(expand("%:p:h"))
if go#util#ShellError() != 0 if l:err != 0
call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), command) 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) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang if empty(errors)
call go#list#JumpToFirst(l:listtype)
elseif empty(errors)
" failed to parse errors, output the original content " failed to parse errors, output the original content
call go#util#EchoError(out) call go#util#EchoError(out)
elseif a:bang
call win_gotoid(l:winid)
else
call go#list#JumpToFirst(l:listtype)
endif endif
call go#util#EchoError("[test] FAIL")
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
@ -130,156 +125,22 @@ function! go#test#Func(bang, ...) abort
call extend(args, a:000) call extend(args, a:000)
else else
" only add this if no custom flags are passed " 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)) call add(args, printf("-timeout=%s", timeout))
endif endif
call call('go#test#Test', args) call call('go#test#Test', args)
endfunction endfunction
function! s:test_job(args) abort function! s:test_job(cmd, args) abort
let status = {
\ 'desc': 'current status',
\ 'type': "test",
\ 'state': "started",
\ }
if a:args.compile_test
let status.state = "compiling"
endif
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
call go#cmd#autowrite() call go#cmd#autowrite()
let state = { call go#job#Spawn(a:cmd, a:args)
\ '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)
endfunction endfunction
" show_errors parses the given list of lines of a 'go test' output and returns let s:efm = ""
" a quickfix compatible list of errors. It's intended to be used only for go let s:go_test_show_name = 0
" 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
function! s:errorformat() abort function! s:errorformat() abort
" NOTE(arslan): once we get JSON output everything will be easier :). " 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. " https://github.com/golang/go/issues/2981.
let goroot = go#util#goroot() 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 if s:efm != "" && s:go_test_show_name == show_name
return s:efm return s:efm
endif endif
@ -297,15 +158,12 @@ function! s:errorformat() abort
" each level of test indents the test output 4 spaces. Capturing groups " 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. \(\)) cannot be used in an errorformat, but non-capturing groups can
" (e.g. \%(\)). " (e.g. \%(\)).
let indent = '%\\%( %\\)%#' let indent = '%\\%( %\\)'
" match compiler errors
let format = "%f:%l:%c: %m"
" ignore `go test -v` output for starting tests " ignore `go test -v` output for starting tests
let format .= ",%-G=== RUN %.%#" let format = "%-G=== RUN %.%#"
" ignore `go test -v` output for passing tests " ignore `go test -v` output for passing tests
let format .= ",%-G" . indent . "--- PASS: %.%#" let format .= ",%-G" . indent . "%#--- PASS: %.%#"
" Match failure lines. " Match failure lines.
" "
@ -315,24 +173,25 @@ function! s:errorformat() abort
" e.g.: " e.g.:
" '--- FAIL: TestSomething (0.00s)' " '--- FAIL: TestSomething (0.00s)'
if show_name if show_name
let format .= ",%G" . indent . "--- FAIL: %m (%.%#)" let format .= ",%G" . indent . "%#--- FAIL: %m (%.%#)"
else else
let format .= ",%-G" . indent . "--- FAIL: %.%#" let format .= ",%-G" . indent . "%#--- FAIL: %.%#"
endif endif
" Go 1.10 test output {{{1
" Matches test output lines. " Matches test output lines.
" "
" All test output lines start with the test indentation and a tab, followed " 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 " by the filename, a colon, the line number, another colon, a space, and the
" message. e.g.: " message. e.g.:
" '\ttime_test.go:30: Likely problem: the time zone files have not been installed.' " '\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 " also match lines that don't have a message (i.e. the message begins with a
" newline or is the empty string): " newline or is the empty string):
" e.g.: " e.g.:
" t.Errorf("\ngot %v; want %v", actual, expected) " t.Errorf("\ngot %v; want %v", actual, expected)
" t.Error("") " 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 " 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, " 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 " 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 " get concatenated in the quickfix list, which is not what users typically
" want when writing a newline into their test output. " 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. " set the format for panics.
@ -375,7 +244,7 @@ function! s:errorformat() abort
" e.g.: " e.g.:
" '\t/usr/local/go/src/time.go:1313 +0x5d' " '\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. " panicaddress sets the filename and line number.
let panicaddress = "%\\t%f:%l +0x%[0-9A-Fa-f]%\\+" let panicaddress = "%\\t%f:%l +0x%[0-9A-Fa-f]%\\+"
let readyaddress = "%\\t%\\f%\\+:%\\d%\\+ +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. " the running goroutine's stack.
let format .= ",%Z" . panicaddress 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. " 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 " This is to catch those lines that come after the top most non-standard
" library line in stack traces. " library line in stack traces.
@ -409,12 +283,26 @@ function! s:errorformat() abort
let format .= ",%-Cexit status %[0-9]%\\+" let format .= ",%-Cexit status %[0-9]%\\+"
"let format .= ",exit 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 " or not, because these lines sometimes come before and sometimes after
" panic stacktraces. " panic stacktraces.
let format .= ",%-CFAIL%\\t%.%#" let format .= ",%-CFAIL%\\t%.%#"
"let format .= ",FAIL%\\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. " Match and ignore everything else in multi-line messages.
let format .= ",%-C%.%#" let format .= ",%-C%.%#"
" Match and ignore everything else not in a multi-line message: " Match and ignore everything else not in a multi-line message:
@ -425,4 +313,8 @@ function! s:errorformat() abort
return s:efm return s:efm
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 func! Test_GoTest() abort
let expected = [ let expected = [
\ {'lnum': 12, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'log message'}, \ {'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 func! Test_GoTestShowName() abort
let expected = [ let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'}, \ {'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': 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 let g:go_test_show_name=1
call s:test('showname/showname_test.go', expected) 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 endfunc
func! s:test(file, expected, ...) abort 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' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test'
silent exe 'e ' . $GOPATH . '/src/' . a:file silent exe 'e ' . $GOPATH . '/src/' . a:file
@ -94,7 +105,7 @@ func! s:test(file, expected, ...) abort
endif endif
" run the tests " run the tests
call call(function('go#test#Test'), args) silent call call(function('go#test#Test'), args)
let actual = getqflist() let actual = getqflist()
let start = reltime() 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') return substitute(a:str, '[0-9]\+\(\.[0-9]\+\)\?s', '0.000s', 'g')
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -1,48 +1,132 @@
if !exists("g:go_textobj_enabled") " don't spam the user when Vim is started in Vi compatibility mode
let g:go_textobj_enabled = 1 let s:cpo_save = &cpo
endif set cpo&vim
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
" ( ) motions " ( ) motions
" { } motions " { } motions
" s for sentence " s for sentence
" p for parapgrah " p for paragraph
" < > " < >
" t for tag " t for tag
" Select a function in visual mode. function! go#textobj#Comment(mode) abort
function! go#textobj#Function(mode) abort let l:fname = expand('%:p')
let offset = go#util#OffsetCursor()
let fname = shellescape(expand("%:p")) try
if &modified if &modified
" Write current unsaved buffer to a temp file and use the modified content let l:tmpname = tempname()
let l:tmpname = tempname() call writefile(go#util#GetLines(), l:tmpname)
call writefile(go#util#GetLines(), l:tmpname) let l:fname = l:tmpname
let fname = l:tmpname endif
endif
let bin_path = go#path#CheckBinPath('motion') let l:cmd = ['motion',
if empty(bin_path) \ '-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 return
endif endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) let l:info = l:result.comment
let command .= " -mode enclosing" call cursor(l:info.startLine, l:info.startCol)
if g:go_textobj_include_function_doc " Adjust cursor to exclude start comment markers. Try to be a little bit
let command .= " -parse-comments" " 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 endif
let out = go#util#System(command) normal! v
if go#util#ShellError() != 0
" 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) call go#util#EchoError(out)
return return
endif endif
@ -63,9 +147,9 @@ function! go#textobj#Function(mode) abort
if a:mode == 'a' if a:mode == 'a'
" anonymous functions doesn't have associated doc. Also check if the user " anonymous functions doesn't have associated doc. Also check if the user
" want's to include doc comments for function declarations " 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) 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 " one liner anonymous functions
if info.lbrace.line == info.rbrace.line if info.lbrace.line == info.rbrace.line
" jump to first nonblack char, to get the correct column " jump to first nonblack char, to get the correct column
@ -101,36 +185,28 @@ endfunction
" Get the location of the previous or next function. " Get the location of the previous or next function.
function! go#textobj#FunctionLocation(direction, cnt) abort function! go#textobj#FunctionLocation(direction, cnt) abort
let offset = go#util#OffsetCursor() let l:fname = expand("%:p")
let fname = shellescape(expand("%:p"))
if &modified if &modified
" Write current unsaved buffer to a temp file and use the modified content " Write current unsaved buffer to a temp file and use the modified content
let l:tmpname = tempname() let l:tmpname = tempname()
call writefile(go#util#GetLines(), l:tmpname) call writefile(go#util#GetLines(), l:tmpname)
let fname = l:tmpname let l:fname = l:tmpname
endif endif
let bin_path = go#path#CheckBinPath('motion') let l:cmd = ['motion',
if empty(bin_path) \ '-format', 'vim',
return \ '-file', l:fname,
\ '-offset', go#util#OffsetCursor(),
\ '-shift', a:cnt,
\ '-mode', a:direction,
\ ]
if go#config#TextobjIncludeFunctionDoc()
let l:cmd += ['-parse-comments']
endif endif
let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) let [l:out, l:err] = go#util#Exec(l:cmd)
let command .= ' -shift ' . a:cnt if l:err
if a:direction == 'next'
let command .= ' -mode next'
else " 'prev'
let command .= ' -mode prev'
endif
if g:go_textobj_include_function_doc
let command .= " -parse-comments"
endif
let out = go#util#System(command)
if go#util#ShellError() != 0
call go#util#EchoError(out) call go#util#EchoError(out)
return return
endif endif
@ -190,7 +266,7 @@ function! go#textobj#FunctionJump(mode, direction) abort
endif endif
if a:mode == 'v' && a:direction == 'prev' 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) keepjumps call cursor(info.doc.line, 1)
else else
keepjumps call cursor(info.func.line, 1) 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) keepjumps call cursor(info.func.line, 1)
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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". " From "go list -h".
function! go#tool#ValidFiles(...) function! go#tool#ValidFiles(...)
let l:list = ["GoFiles", "CgoFiles", "IgnoredGoFiles", "CFiles", "CXXFiles", let l:list = ["GoFiles", "CgoFiles", "IgnoredGoFiles", "CFiles", "CXXFiles",
@ -36,8 +40,8 @@ function! go#tool#Files(...) abort
endif endif
endfor endfor
let out = go#tool#ExecuteInDir('go list -f ' . shellescape(combined)) let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:combined])
return split(out, '\n') return split(l:out, '\n')
endfunction endfunction
function! go#tool#Deps() abort function! go#tool#Deps() abort
@ -46,9 +50,8 @@ function! go#tool#Deps() abort
else else
let format = "{{range $f := .Deps}}{{$f}}\n{{end}}" let format = "{{range $f := .Deps}}{{$f}}\n{{end}}"
endif endif
let command = 'go list -f '.shellescape(format) let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format])
let out = go#tool#ExecuteInDir(command) return split(l:out, '\n')
return split(out, '\n')
endfunction endfunction
function! go#tool#Imports() abort function! go#tool#Imports() abort
@ -58,188 +61,79 @@ function! go#tool#Imports() abort
else else
let format = "{{range $f := .Imports}}{{$f}}{{printf \"\\n\"}}{{end}}" let format = "{{range $f := .Imports}}{{$f}}{{printf \"\\n\"}}{{end}}"
endif endif
let command = 'go list -f '.shellescape(format) let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format])
let out = go#tool#ExecuteInDir(command) if l:err != 0
if go#util#ShellError() != 0
echo out echo out
return imports return imports
endif endif
for package_path in split(out, '\n') for package_path in split(out, '\n')
let cmd = "go list -f '{{.Name}}' " . shellescape(package_path) let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}', l:package_path])
let package_name = substitute(go#tool#ExecuteInDir(cmd), '\n$', '', '') if l:err != 0
echo out
return imports
endif
let package_name = substitute(l:out, '\n$', '', '')
let imports[package_name] = package_path let imports[package_name] = package_path
endfor endfor
return imports return imports
endfunction endfunction
function! go#tool#Info(auto) abort function! go#tool#Info(showstatus) abort
let l:mode = get(g:, 'go_info_mode', 'gocode') let l:mode = go#config#InfoMode()
if l:mode == 'gocode' if l:mode == 'gocode'
call go#complete#Info(a:auto) call go#complete#Info(a:showstatus)
elseif l:mode == 'guru' 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 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 endif
endfunction endfunction
function! go#tool#PackageName() abort function! go#tool#PackageName() abort
let command = "go list -f \"{{.Name}}\"" let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}'])
let out = go#tool#ExecuteInDir(command) if l:err != 0
if go#util#ShellError() != 0
return -1 return -1
endif endif
return split(out, '\n')[0] return split(out, '\n')[0]
endfunction 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 " Exists checks whether the given importpath exists or not. It returns 0 if
" the importpath exists under GOPATH. " the importpath exists under GOPATH.
function! go#tool#Exists(importpath) abort function! go#tool#Exists(importpath) abort
let command = "go list ". a:importpath let [l:out, l:err] = go#util#ExecInDir(['go', 'list', a:importpath])
let out = go#tool#ExecuteInDir(command) if l:err != 0
if go#util#ShellError() != 0
return -1 return -1
endif endif
return 0 return 0
endfunction endfunction
" following two functions are from: https://github.com/mattn/gist-vim function! go#tool#DescribeBalloon()
" thanks @mattn let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p')
function! s:get_browser_command() abort call go#lsp#Hover(l:fname, v:beval_lnum, v:beval_col, funcref('s:balloon', []))
let go_play_browser_command = get(g:, 'go_play_browser_command', '') return ''
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
endfunction endfunction
function! go#tool#OpenBrowser(url) abort function! s:balloon(msg)
let cmd = s:get_browser_command() let l:msg = a:msg
if len(cmd) == 0 if has('balloon_eval')
redraw if has('balloon_multiline')
echohl WarningMsg let l:msg = join(a:msg, "\n")
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
else else
let cmd = substitute(cmd, '%URL%', '\=shellescape(a:url)', 'g') let l:msg = substitute(join(map(deepcopy(a:msg), 'substitute(v:val, "\t", "", "")'), '; '), '{;', '{', '')
call go#util#System(cmd)
endif endif
endif
call balloon_show(l:msg)
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 func! Test_ExecuteInDir() abort
let l:tmp = gotest#write_file('a/a.go', ['package a']) let l:tmp = gotest#write_file('a/a.go', ['package a'])
try try
let l:out = go#tool#ExecuteInDir("pwd") let l:out = go#util#ExecInDir(['pwd'])
call assert_equal(l:tmp . "/src/a\n", l:out) call assert_equal([l:tmp . "/src/a\n", 0], l:out)
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
@ -13,11 +17,15 @@ func! Test_ExecuteInDir_nodir() abort
exe ':e ' . l:tmp . '/new-dir/a' exe ':e ' . l:tmp . '/new-dir/a'
try try
let l:out = go#tool#ExecuteInDir("pwd") let l:out = go#util#ExecInDir(['pwd'])
call assert_equal('', l:out) call assert_equal(['', 1], l:out)
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 let s:buf_nr = -1
"OpenWindow opens a new scratch window and put's the content into the window "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 norm! zz
endfunction endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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. " PathSep returns the appropriate OS specific path separator.
function! go#util#PathSep() abort function! go#util#PathSep() abort
if go#util#IsWin() if go#util#IsWin()
@ -48,7 +52,7 @@ function! go#util#IsMac() abort
return has('mac') || return has('mac') ||
\ has('macunix') || \ has('macunix') ||
\ has('gui_macvim') || \ has('gui_macvim') ||
\ go#util#System('uname') =~? '^darwin' \ go#util#Exec(['uname'])[0] =~? '^darwin'
endfunction endfunction
" Checks if using: " Checks if using:
@ -59,7 +63,20 @@ function! go#util#IsUsingCygwinShell()
return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*' return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*'
endfunction 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 " 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. " of the latest is 8.0.0087 which is required for a stable async API.
return has('job') && has("patch-8.0.0087") 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 " goarch returns 'go env GOARCH'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goarch")' " be used. Instead use 'go#util#env("goarch")'
function! go#util#goarch() abort 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 endfunction
" goos returns 'go env GOOS'. This is an internal function and shouldn't " goos returns 'go env GOOS'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goos")' " be used. Instead use 'go#util#env("goos")'
function! go#util#goos() abort 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 endfunction
" goroot returns 'go env GOROOT'. This is an internal function and shouldn't " goroot returns 'go env GOROOT'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("goroot")' " be used. Instead use 'go#util#env("goroot")'
function! go#util#goroot() abort 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 endfunction
" gopath returns 'go env GOPATH'. This is an internal function and shouldn't " gopath returns 'go env GOPATH'. This is an internal function and shouldn't
" be used. Instead use 'go#util#env("gopath")' " be used. Instead use 'go#util#env("gopath")'
function! go#util#gopath() abort 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 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 function! go#util#osarch() abort
return go#util#env("goos") . '_' . go#util#env("goarch") return go#util#env("goos") . '_' . go#util#env("goarch")
endfunction endfunction
@ -124,12 +148,13 @@ endfunction
" so that we always use a standard POSIX-compatible Bourne shell (and not e.g. " so that we always use a standard POSIX-compatible Bourne shell (and not e.g.
" csh, fish, etc.) See #988 and #1276. " csh, fish, etc.) See #988 and #1276.
function! s:system(cmd, ...) abort function! s:system(cmd, ...) abort
" Preserve original shell and shellredir values " Preserve original shell, shellredir and shellcmdflag values
let l:shell = &shell let l:shell = &shell
let l:shellredir = &shellredir let l:shellredir = &shellredir
let l:shellcmdflag = &shellcmdflag
if !go#util#IsWin() && executable('/bin/sh') 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 endif
try try
@ -138,6 +163,7 @@ function! s:system(cmd, ...) abort
" Restore original values " Restore original values
let &shell = l:shell let &shell = l:shell
let &shellredir = l:shellredir let &shellredir = l:shellredir
let &shellcmdflag = l:shellcmdflag
endtry endtry
endfunction endfunction
@ -153,16 +179,47 @@ endfunction
function! go#util#Exec(cmd, ...) abort function! go#util#Exec(cmd, ...) abort
if len(a:cmd) == 0 if len(a:cmd) == 0
call go#util#EchoError("go#util#Exec() called with empty a:cmd") call go#util#EchoError("go#util#Exec() called with empty a:cmd")
return return ['', 1]
endif 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. " 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) if empty(l:bin)
return ["", 1] return ['', 1]
endif 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()] return [l:out, go#util#ShellError()]
endfunction endfunction
@ -259,7 +316,7 @@ endfunction
" snippetcase converts the given word to given preferred snippet setting type " snippetcase converts the given word to given preferred snippet setting type
" case. " case.
function! go#util#snippetcase(word) abort 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" if l:snippet_case == "snakecase"
return go#util#snakecase(a:word) return go#util#snakecase(a:word)
elseif l:snippet_case == "camelcase" elseif l:snippet_case == "camelcase"
@ -397,7 +454,73 @@ endfunction
" Report if the user enabled a debug flag in g:go_debug. " Report if the user enabled a debug flag in g:go_debug.
function! go#util#HasDebug(flag) function! go#util#HasDebug(flag)
return index(get(g:, 'go_debug', []), a:flag) >= 0 return index(go#config#Debug(), a:flag) >= 0
endfunction 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 " 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. " 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, " 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 endwhile
endfunc endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -28,7 +28,7 @@ function! s:source.gather_candidates(args, context) abort
return [] return []
endif 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:command = printf('%s -format vim -mode decls -include %s -%s %s', l:bin_path, l:include, l:mode, shellescape(l:path))
let l:candidates = [] let l:candidates = []
try try

View file

@ -9,6 +9,10 @@ if exists("g:current_compiler")
endif endif
let g:current_compiler = "go" 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 if exists(":CompilerSet") != 2
command -nargs=* CompilerSet setlocal <args> command -nargs=* CompilerSet setlocal <args>
endif endif
@ -38,4 +42,8 @@ CompilerSet errorformat+=%-G%.%# " All lines not matching a
let &cpo = s:save_cpo let &cpo = s:save_cpo
unlet 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 " 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. tools developed by the Go community to provide a seamless Vim experience.
* Compile your package with |:GoBuild|, install it with |:GoInstall| or * 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|. * Quickly execute your current file(s) with |:GoRun|.
* Improved syntax highlighting and folding. * Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with |:GoDebugStart|. * 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. * `gofmt` or `goimports` on save keeps the cursor position and undo history.
* Go to symbol/declaration with |:GoDef|. * Go to symbol/declaration with |:GoDef|.
* Look up documentation with |:GoDoc| or |:GoDocBrowser|. * 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* 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 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 the recommended version to use. If you choose to use the master branch
instead, please do so with caution; it is a _development_ 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 > * https://github.com/gmarik/vundle >
Plugin 'fatih/vim-go' Plugin 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
< <
* Manual (not recommended) > * Manual (not recommended) >
Copy all of the files into your `~/.vim` directory Copy all of the files into your `~/.vim` directory
< <
You will also need to install all the necessary binaries. vim-go makes it easy 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 to install all of them by providing a command, |:GoInstallBinaries|, to
`go get` all the required binaries. The binaries will be installed to $GOBIN `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 https://github.com/Shougo/neocomplete.vim
* Real-time completion (Neovim and Vim 8): * Real-time completion (Neovim and Vim 8):
https://github.com/Shougo/deoplete.nvim and https://github.com/Shougo/deoplete.nvim
https://github.com/zchee/deoplete-go
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: * Display source code navigation in a sidebar:
https://github.com/majutsushi/tagbar 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/SirVer/ultisnips or
https://github.com/joereynolds/vim-minisnip 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|: * Interactive |:GoDecls| and |:GoDeclsDir|:
https://github.com/ctrlpvim/ctrlp.vim or https://github.com/ctrlpvim/ctrlp.vim or
https://github.com/junegunn/fzf.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* COMMANDS *go-commands*
*:GoReportGitHubIssue*
:GoReportGitHubIssue
GoReportGitHubIssue opens the default browser and starts a new bug report
with useful system information.
*:GoPath* *:GoPath*
:GoPath [path] :GoPath [path]
@ -187,11 +197,13 @@ COMMANDS *go-commands*
displayed and the buffer will be untouched. displayed and the buffer will be untouched.
*:GoLint* *:GoLint*
:GoLint [packages] :GoLint! [packages]
Run golint for the directory under your current file, or for the given Run golint for the directory under your current file, or for the given
packages. packages.
If [!] is not given the first error is jumped to.
*:GoDoc* *:GoDoc*
:GoDoc [word] :GoDoc [word]
@ -249,7 +261,7 @@ CTRL-]
g<C-LeftMouse> g<C-LeftMouse>
<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>, default the CTRL-] shortcut, the mapping `gd` and <C-LeftMouse>,
g<LeftMouse> are enabled to invoke :GoDef for the identifier under the g<LeftMouse> are enabled to invoke :GoDef for the identifier under the
cursor. See |'g:go_def_mapping_enabled'| to disable them. No explicit 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 list of file locations you have visited with :GoDef that is retained to
help you navigate software. 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*
:GoDefStack [number] :GoDefStack [number]
@ -455,7 +475,7 @@ CTRL-t
If [!] is not given the first error is jumped to. If [!] is not given the first error is jumped to.
*:GoErrCheck* *:GoErrCheck*
:GoErrCheck [options] :GoErrCheck! [options]
Check for unchecked errors in you current package. Errors are populated in Check for unchecked errors in you current package. Errors are populated in
the quickfix window. the quickfix window.
@ -463,6 +483,8 @@ CTRL-t
You may optionally pass any valid errcheck flags/options. See You may optionally pass any valid errcheck flags/options. See
`errcheck -h` for a full list. `errcheck -h` for a full list.
If [!] is not given the first error is jumped to.
*:GoFiles* *:GoFiles*
:GoFiles [source_files] :GoFiles [source_files]
@ -629,7 +651,7 @@ CTRL-t
disabled it clears and stops automatic highlighting. disabled it clears and stops automatic highlighting.
*:GoMetaLinter* *:GoMetaLinter*
:GoMetaLinter [path] :GoMetaLinter! [path]
Calls the underlying `gometalinter` tool and displays all warnings and Calls the underlying `gometalinter` tool and displays all warnings and
errors in the |quickfix| window. By default the following linters are 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 use the variable |'g:go_metalinter_command'|. To override the maximum
linters execution time use |'g:go_metalinter_deadline'| variable. linters execution time use |'g:go_metalinter_deadline'| variable.
If [!] is not given the first error is jumped to.
*:GoBuildTags* *:GoBuildTags*
:GoBuildTags [tags] :GoBuildTags [tags]
Changes the build tags for various commands. If you have any file that 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 uses a custom build tag, such as `// +build integration` , this command
be used to pass it to all tools that accepts tags, such as guru, gorename, can be used to pass it to all tools that accepts tags, such as guru,
etc.. gorename, etc.
The build tags is cleared (unset) if `""` is given. If no arguments is The build tags is cleared (unset) if `""` is given. If no arguments are
given it prints the current custom build tags. given it prints the current build tags.
*:AsmFmt* *:AsmFmt*
:AsmFmt :AsmFmt
@ -676,6 +700,12 @@ CTRL-t
\| command! -bang AS call go#alternate#Switch(<bang>0, 'split') \| command! -bang AS call go#alternate#Switch(<bang>0, 'split')
augroup END augroup END
< <
*:GoPointsTo*
:GoPointsTo
Show all variables to which the pointer under the cursor may point to.
*:GoWhicherrs* *:GoWhicherrs*
:GoWhicherrs :GoWhicherrs
@ -762,7 +792,7 @@ CTRL-t
*:GoRemoveTags* *:GoRemoveTags*
:[range]GoRemoveTags [key],[option] [key1],[option1] ... :[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 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 called outside a struct definition or if the file is not correctly
formatted formatted
@ -789,6 +819,11 @@ CTRL-t
Toggles |'g:go_fmt_autosave'|. Toggles |'g:go_fmt_autosave'|.
*:GoModFmtAutoSaveToggle*
:GoModFmtAutoSaveToggle
Toggles |'g:go_mod_fmt_autosave'|.
*:GoAsmFmtAutoSaveToggle* *:GoAsmFmtAutoSaveToggle*
: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* MAPPINGS *go-mappings*
@ -1030,6 +1093,10 @@ Show send/receive corresponding to selected channel op
Show all refs to entity denoted by selected identifier 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)* *(go-metalinter)*
Calls `go-metalinter` for the current directory 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 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* 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 excluding the function definition and the closing bracket. This
text-object also supports literal functions 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: vim-go also defines the following text motion objects:
@ -1090,10 +1171,10 @@ FUNCTIONS *go-functions*
*go#statusline#Show()* *go#statusline#Show()*
Shows the status of a job running asynchronously. Can be used to plug into the 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 statusline. It works to show the status per package instead of per file.
file. Assume you have three files open, all belonging to the same package, if Assume you have three files open, all belonging to the same package, if the
the package build (`:GoBuild`) is successful, all statusline's will show package build (`:GoBuild`) is successful, all statuslines will show `success`,
`success`, if you it fails all file's statusline will show `failed`. if it fails all windows' statuslines will show `failed`.
To avoid always showing old status information, the status information is To avoid always showing old status information, the status information is
cleaned for each package after `60` seconds. This can be changed with the 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 Returns the description of the identifer under the cursor. Can be used to plug
into the statusline. 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* 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 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 `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 `gopls` or `guru` as they cover more cases and are more accurate. Current
are: `[gocode, guru]` > valid options are: `[gocode, guru, gopls]` >
let g:go_info_mode = 'gocode' 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 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 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. > it's causing problems on some Vim versions. By default it's disabled. >
let g:go_fmt_experimental = 0 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'* *'g:go_doc_keywordprg_enabled'*
Use this option to run `godoc` on words under the cursor with |K|; this will 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 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 `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' 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 let g:go_def_reuse_buffer = 0
< <
*'g:go_doc_command'* *'g:go_bin_path'*
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'*
Use this option to change default path for vim-go tools when using Use this option to change default path for vim-go tools when using
|:GoInstallBinaries| and |:GoUpdateBinaries|. If not set `$GOBIN` or |:GoInstallBinaries| and |:GoUpdateBinaries|. If not set `$GOBIN` or
`$GOPATH/bin` is used. > `$GOPATH/bin` is used. >
let g:go_bin_path = "" 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'* *'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'* *'g:go_build_tags'*
These options that will be automatically passed to the `-tags` option of Space-separated list of build tags passed to the `-tags` flag of tools that
various tools, such as `guru`, `gorename`, etc... This is a permanent support it.
setting. A more useful way is to use |:GoBuildTags| to dynamically change or There is also the |:GoBuildTags| convenience command to change or remove build
remove build tags. By default it's not set. tags.
> >
let g:go_build_tags = '' let g:go_build_tags = ''
< <
@ -1407,11 +1516,12 @@ it's empty
< <
*'g:go_metalinter_command'* *'g:go_metalinter_command'*
Overrides the command to be executed when |:GoMetaLinter| is called. This is Overrides the command to be executed when |:GoMetaLinter| is called. By
an advanced settings and is for users who want to have a complete control default it's `gometalinter`. `golangci-lint` is also supported. It can also be
over how `gometalinter` should be executed. By default it's empty. 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'* *'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 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 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. is not present in the dictionary, |'g:go_list_type'| will be used instead.
Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint", Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoModFmt", "GoInstall",
"GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for both "GoLint", "GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for
:GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". Supported both :GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest".
values for each command are "quickfix" and "locationlist". Supported values for each command are "quickfix" and "locationlist".
> >
let g:go_list_type_commands = {} let g:go_list_type_commands = {}
< <
@ -1522,14 +1632,6 @@ same.
let g:go_gorename_prefill = 'expand("<cword>") =~# "^[A-Z]"' . let g:go_gorename_prefill = 'expand("<cword>") =~# "^[A-Z]"' .
\ '? go#util#pascalcase(expand("<cword>"))' . \ '? go#util#pascalcase(expand("<cword>"))' .
\ ': go#util#camelcase(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'* *'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. to an autocompletion proposals. By default it is enabled.
> >
let g:go_gocode_propose_builtins = 1 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'* *'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: Currently accepted values:
shell-commands Echo all shell commands that vim-go runs.
debugger-state Expose debugger state in 'g:go_debug_diag'. debugger-state Expose debugger state in 'g:go_debug_diag'.
debugger-commands Echo communication between vim-go and `dlv`; requests and debugger-commands Echo communication between vim-go and `dlv`; requests and
responses are recorded in `g:go_debug_commands`. responses are recorded in `g:go_debug_commands`.
lsp Record lsp requests and responses in g:go_lsp_log.
> >
let g:go_debug = [] let g:go_debug = []
< <
@ -1750,13 +1862,13 @@ Highlight function and method declarations.
> >
let g:go_highlight_functions = 0 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 Highlight the variable names in parameters (including named return parameters)
declarations. Setting this implies the functionality from in function declarations. Setting this implies the functionality from
|'g:go_highlight_functions'|. |'g:go_highlight_functions'|.
> >
let g:go_highlight_function_arguments = 0 let g:go_highlight_function_parameters = 0
< <
*'g:go_highlight_function_calls'* *'g:go_highlight_function_calls'*
@ -1828,6 +1940,13 @@ filetype.
The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the
`gotexttmpl` is never automatically set and needs to be set manually. `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* DEBUGGER *go-debug*
@ -1847,10 +1966,10 @@ features:
* Toggle breakpoint. * Toggle breakpoint.
* Stack operation continue/next/step out. * Stack operation continue/next/step out.
This feature requires Vim 8.0.0087 or newer with the |+job| feature. Neovim This feature requires either Vim 8.0.0087 or newer with the |+job| feature or
does _not_ work (yet). Neovim. This features also requires Delve 1.0.0 or newer, and it is
This requires Delve 1.0.0 or newer, and it is recommended to use Go 1.10 or recommended to use Go 1.10 or newer, as its new caching will speed up
newer, as its new caching will speed up recompiles. recompiles.
*go-debug-intro* *go-debug-intro*
GETTING STARTED WITH THE DEBUGGER~ 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' 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* 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 a reasonable amount of performance `guru` limits this analysis to a selected
list of packages. This is known as the "guru scope". 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` 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 the scope will be set to `github.com/user/pkg/a`, but you probably want
`github.com/user/pkg` `github.com/user/pkg`
@ -2093,11 +2230,22 @@ Also see |:GoGuruScope| and |'g:go_guru_scope'|.
Vim becomes slow while editing Go files~ Vim becomes slow while editing Go files~
This is usually caused by `g:go_highlight_*` options. Try disabling them if The most common cause for this is using an older version of Vim that doesn't
you've enabled some of them. 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~ 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 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. 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 vim-go might not work as well in Neovim as it does in Vim. I'm happy to accept
requests or very detailed bug reports. If you're interested to improve the pull requests or very detailed bug reports. If you're interested to improve
state of Neovim in vim-go you're always welcome! the state of Neovim in vim-go you're always welcome!
Run `:GoRun` in a new tab, horizontal split or vertical split terminal 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" 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* DEVELOPMENT *go-development*
@ -2215,14 +2375,14 @@ You can install and test all Vim versions by running `make`.
DONATION *go-donation* DONATION *go-donation*
People have asked for this for a long time, now you can be a fully supporter 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 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 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 features. It's completely optional and is just a direct way to support
vim-go's ongoing development. Thanks! 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 " vint: -ProhibitAutocmdWithNoGroup
" We take care to preserve the user's fileencodings and fileformats, " don't spam the user when Vim is started in Vi compatibility mode
" because those settings are global (not buffer local), yet we want let s:cpo_save = &cpo
" to override them for loading Go files, which are defined to be UTF-8. set cpo&vim
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
" Note: should not use augroup in ftdetect (see :help ftdetect) " Note: should not use augroup in ftdetect (see :help ftdetect)
au BufNewFile *.go setfiletype go | setlocal fileencoding=utf-8 fileformat=unix au BufRead,BufNewFile *.go setfiletype go
au BufRead *.go call s:gofiletype_pre("go") au BufRead,BufNewFile *.s setfiletype asm
au BufReadPost *.go call s:gofiletype_post() au BufRead,BufNewFile *.tmpl setfiletype gohtmltmpl
au BufNewFile *.s setfiletype asm | setlocal fileencoding=utf-8 fileformat=unix " remove the autocommands for modsim3, and lprolog files so that their
au BufRead *.s call s:gofiletype_pre("asm") " highlight groups, syntax, etc. will not be loaded. *.MOD is included, so
au BufReadPost *.s call s:gofiletype_post() " 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 " vim: sw=2 ts=2 et

View file

@ -5,7 +5,12 @@ if exists("b:did_ftplugin")
endif endif
let b:did_ftplugin = 1 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 setlocal formatoptions-=t
@ -16,4 +21,17 @@ setlocal noexpandtab
command! -nargs=0 AsmFmt call go#asmfmt#Format() 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 " vim: sw=2 ts=2 et

View file

@ -9,7 +9,12 @@ if exists("b:did_ftplugin")
endif endif
let b:did_ftplugin = 1 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 setlocal formatoptions-=t
@ -20,8 +25,11 @@ setlocal noexpandtab
compiler go compiler go
" Set gocode completion " Set autocompletion
setlocal omnifunc=go#complete#Complete setlocal omnifunc=go#complete#Complete
if !go#util#has_job()
setlocal omnifunc=go#complete#GocodeComplete
endif
if get(g:, "go_doc_keywordprg_enabled", 1) if get(g:, "go_doc_keywordprg_enabled", 1)
" keywordprg doesn't allow to use vim commands, override it " 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-]> :GoDef<cr>
nnoremap <buffer> <silent> <C-LeftMouse> <LeftMouse>:GoDef<cr> nnoremap <buffer> <silent> <C-LeftMouse> <LeftMouse>:GoDef<cr>
nnoremap <buffer> <silent> g<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-]> :<C-u>call go#def#Jump("split", 0)<CR>
nnoremap <buffer> <silent> <C-w>] :<C-u>call go#def#Jump("split")<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> nnoremap <buffer> <silent> <C-t> :<C-U>call go#def#StackPop(v:count1)<cr>
endif endif
if get(g:, "go_textobj_enabled", 1) if get(g:, "go_textobj_enabled", 1)
onoremap <buffer> <silent> af :<c-u>call go#textobj#Function('a')<cr> 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> 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> 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 " 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', 'next')<cr>
nnoremap <buffer> <silent> [[ :<c-u>call go#textobj#FunctionJump('n', 'prev')<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> xnoremap <buffer> <silent> [[ :<c-u>call go#textobj#FunctionJump('v', 'prev')<cr>
endif 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) let &l:updatetime= get(g:, "go_updatetime", 800)
endif endif
" NOTE(arslan): experimental, disabled by default, doesn't work well. No " Autocommands
" documentation as well. If anyone feels adventurous, enable the following and " ============================================================================
" try to search for Go identifiers ;)
" "
" if get(g:, "go_sameid_search_enabled", 0) augroup vim-go-buffer
" autocmd FileType go nnoremap <buffer> <silent> * :<c-u>call Sameids_search(0)<CR> autocmd! * <buffer>
" 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
" " mode 0: next 1: prev " The file is registered (textDocument/DidOpen) with gopls in in
" function! Sameids_repeat(mode) " plugin/go.vim on the FileType event.
" let matches = getmatches() " TODO(bc): handle all the other events that may be of interest to gopls,
" if empty(matches) " too (e.g. BufFilePost , CursorHold , CursorHoldI, FileReadPost,
" return " StdinReadPre, BufWritePost, TextChange, TextChangedI)
" endif if go#util#has_job()
" let cur_offset = go#util#OffsetCursor() 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 autocmd CursorHold <buffer> call go#auto#auto_type_info()
" if a:mode autocmd CursorHold <buffer> call go#auto#auto_sameids()
" call reverse(matches)
" endif
" for m in matches " Echo the identifier information when completion is done. Useful to see
" if !has_key(m, "group") " the signature of a function, etc...
" return if exists('##CompleteDone')
" endif autocmd CompleteDone <buffer> call go#auto#echo_go_info()
endif
" if m.group != "goSameId" autocmd BufWritePre <buffer> call go#auto#fmt_autosave()
" return autocmd BufWritePost <buffer> call go#auto#metalinter_autosave()
" endif
" 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 autocmd BufEnter <buffer>
" call cursor(m.pos1[0], m.pos1[1]) \ if go#config#AutodetectGopath() && !exists('b:old_gopath')
" return \| let b:old_gopath = exists('$GOPATH') ? $GOPATH : -1
" elseif !a:mode && cur_offset < offset \| let $GOPATH = go#path#Detect()
" call cursor(m.pos1[0], m.pos1[1]) \| endif
" return autocmd BufLeave <buffer>
" endif \ if exists('b:old_gopath')
" endfor \| 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 " restore Vi compatibility settings
" let initial_match = matches[0] let &cpo = s:cpo_save
" if !has_key(initial_match, "group") unlet s:cpo_save
" 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
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -4,6 +4,7 @@ command! -nargs=? -complete=customlist,go#rename#Complete GoRename call go#renam
" -- guru " -- guru
command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope(<f-args>) command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope(<f-args>)
command! -range=% GoImplements call go#guru#Implements(<count>) 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=% GoWhicherrs call go#guru#Whicherrs(<count>)
command! -range=% GoCallees call go#guru#Callees(<count>) command! -range=% GoCallees call go#guru#Callees(<count>)
command! -range=% GoDescribe call go#guru#Describe(<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=% GoChannelPeers call go#guru#ChannelPeers(<count>)
command! -range=% GoReferrers call go#guru#Referrers(<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 GoSameIdsClear call go#guru#ClearSameIds()
command! -range=0 GoSameIdsToggle call go#guru#ToggleSameIds() 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 " -- tags
command! -nargs=* -range GoAddTags call go#tags#Add(<line1>, <line2>, <count>, <f-args>) 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>) command! -nargs=* -range GoRemoveTags call go#tags#Remove(<line1>, <line2>, <count>, <f-args>)
" -- mod
command! -nargs=0 -range GoModFmt call go#mod#Format()
" -- tool " -- tool
command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files(<f-args>) command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files(<f-args>)
command! -nargs=0 GoDeps echo go#tool#Deps() 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() command! -nargs=0 GoAutoTypeInfoToggle call go#complete#ToggleAutoTypeInfo()
" -- cmd " -- 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>) command! -nargs=0 -range=% GoPlay call go#play#Share(<count>, <line1>, <line2>)
" -- def " -- 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=? GoDefPop :call go#def#StackPop(<f-args>)
command! -nargs=? GoDefStack :call go#def#Stack(<f-args>) command! -nargs=? GoDefStack :call go#def#Stack(<f-args>)
command! -nargs=? GoDefStackClear :call go#def#StackClear(<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>') command! -nargs=* -bang -complete=customlist,go#package#Complete GoImportAs call go#import#SwitchImport(1, <f-args>, '<bang>')
" -- linters " -- 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=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=* -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 " -- alternate
command! -bang GoAlternate call go#alternate#Switch(<bang>0, '') 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>) command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
endif 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 " 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-files) :<C-u>call go#tool#Files()<CR>
nnoremap <silent> <Plug>(go-deps) :<C-u>call go#tool#Deps()<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-import) :<C-u>call go#import#SwitchImport(1, '', expand('<cword>'), '')<CR>
nnoremap <silent> <Plug>(go-imports) :<C-u>call go#fmt#Format(1)<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> 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-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-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-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-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-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-decls) :<C-u>call go#decls#Decls(0, '')<CR>
nnoremap <silent> <Plug>(go-def-vertical) :<C-u>call go#def#Jump("vsplit")<CR> nnoremap <silent> <Plug>(go-decls-dir) :<C-u>call go#decls#Decls(1, '')<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-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-pop) :<C-u>call go#def#StackPop()<CR>
nnoremap <silent> <Plug>(go-def-stack) :<C-u>call go#def#Stack()<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-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-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-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()<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-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-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-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-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 " 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") if exists("g:go_loaded_gosnippets")
finish finish
endif endif
@ -47,7 +51,7 @@ function! s:GoMinisnip() abort
endfunction endfunction
let s:engine = get(g:, 'go_snippet_engine', 'automatic') let s:engine = go#config#SnippetEngine()
if s:engine is? "automatic" if s:engine is? "automatic"
if get(g:, 'did_plugin_ultisnips') is 1 if get(g:, 'did_plugin_ultisnips') is 1
call s:GoUltiSnips() call s:GoUltiSnips()
@ -64,4 +68,8 @@ elseif s:engine is? "minisnip"
call s:GoMinisnip() call s:GoMinisnip()
endif endif
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -9,6 +9,10 @@ elseif globpath(&rtp, 'plugin/tagbar.vim') == ""
finish finish
endif 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") if !exists("g:go_gotags_bin")
let g:go_gotags_bin = "gotags" let g:go_gotags_bin = "gotags"
endif endif
@ -54,4 +58,8 @@ endfunction
call s:SetTagbar() call s:SetTagbar()
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et " 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 # error multiple return
snippet errn, "Error return with two return values" !b snippet errn, "Error return with two return values" !b
if err != nil { if err != nil {
return ${1:nil}, err return ${1:nil}, ${2:err}
} }
${0} ${0}
endsnippet endsnippet
@ -363,6 +363,28 @@ func Test${1:Function}(t *testing.T) {
} }
endsnippet 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 snippet hf "http.HandlerFunc" !b
func ${1:handler}(w http.ResponseWriter, r *http.Request) { func ${1:handler}(w http.ResponseWriter, r *http.Request) {
${0:fmt.Fprintf(w, "hello world")} ${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) { func Test${1:Function}(t *testing.T) {
${0} ${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 # test server
snippet tsrv snippet tsrv
abbr ts := httptest.NewServer(...) abbr ts := httptest.NewServer(...)

View file

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

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