diff --git a/pack/acp/start/vim-go/.codecov.yml b/pack/acp/start/vim-go/.codecov.yml index e9062d5..9a8b1bb 100644 --- a/pack/acp/start/vim-go/.codecov.yml +++ b/pack/acp/start/vim-go/.codecov.yml @@ -6,6 +6,7 @@ coverage: target: auto threshold: 1 base: auto + patch: off comment: false ignore: - "!autoload/go/*.vim$" diff --git a/pack/acp/start/vim-go/.coveragerc b/pack/acp/start/vim-go/.coveragerc index 9f46ed7..8cddf91 100644 --- a/pack/acp/start/vim-go/.coveragerc +++ b/pack/acp/start/vim-go/.coveragerc @@ -1,3 +1,3 @@ [run] plugins = covimerage -data_file = .coverage.covimerage +data_file = .coverage_covimerage diff --git a/pack/acp/start/vim-go/.dockerignore b/pack/acp/start/vim-go/.dockerignore index f12dac2..286d089 100644 --- a/pack/acp/start/vim-go/.dockerignore +++ b/pack/acp/start/vim-go/.dockerignore @@ -1,2 +1,6 @@ .local/ +.config/ +.cache/ +.dlv/ .git/ +.viminfo diff --git a/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md b/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md index dd376ee..f227239 100644 --- a/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md +++ b/pack/acp/start/vim-go/.github/ISSUE_TEMPLATE.md @@ -9,14 +9,13 @@ ### Configuration (**MUST** fill this out): -* Vim version (first two lines from `:version`): +* vim-go version: + +* `vimrc` you used to reproduce (use a *minimal* vimrc with other plugins disabled; do not link to a 2,000 line vimrc): + +* Vim version (first three lines from `:version`): * Go version (`go version`): * Go environment (`go env`): -* vim-go version: - -* `vimrc` you used to reproduce (use a *minimal* vimrc with other plugins disabled; do not link to a 2,000 line vimrc): - - diff --git a/pack/acp/start/vim-go/.travis.yml b/pack/acp/start/vim-go/.travis.yml index fe50e85..9d7abd6 100644 --- a/pack/acp/start/vim-go/.travis.yml +++ b/pack/acp/start/vim-go/.travis.yml @@ -1,4 +1,6 @@ language: go +go: + - 1.12.1 notifications: email: false matrix: @@ -6,9 +8,16 @@ matrix: - env: SCRIPT="test -c" VIM_VERSION=vim-7.4 - env: SCRIPT="test -c" VIM_VERSION=vim-8.0 - env: SCRIPT="test -c" VIM_VERSION=nvim - - env: SCRIPT=lint VIM_VERSION=vim-8.0 + - env: ENV=vimlint SCRIPT=lint VIM_VERSION=vim-8.0 + language: python + python: 3.6 install: - ./scripts/install-vim $VIM_VERSION - - pip install --user vim-vint covimerage codecov + - | + if [ "$ENV" = "vimlint" ]; then + pip install vim-vint covimerage codecov pathlib + else + pip install --user vim-vint covimerage codecov pathlib + fi script: - ./scripts/$SCRIPT $VIM_VERSION diff --git a/pack/acp/start/vim-go/CHANGELOG.md b/pack/acp/start/vim-go/CHANGELOG.md index ac5d8d1..09a3a6d 100644 --- a/pack/acp/start/vim-go/CHANGELOG.md +++ b/pack/acp/start/vim-go/CHANGELOG.md @@ -1,11 +1,314 @@ ## unplanned +## 1.20 - (April 22, 2019) + +FEATURES: +* ***gopls support!*** + * use gopls for autocompletion by default in Vim8 and Neovim. + * use gopls for `:GoDef` by setting `g:go_def_mode='gopls'`. + * use gopls for `:GoInfo` by setting `g:go_info_mode='gopls'`. +* Add support for golangci-lint. + * set `g:go_metalinter_command='golangci-lint'` to use golangci-lint instead + of gometalinter. +* New `:GoDefType` command to jump to a type definition from an instance of the + type. + +BACKWARDS INCOMPATABILITIES: +* `g:go_highlight_function_arguments` is renamed to `g:go_highlight_function_parameters` + [[GH-2117]](https://github.com/fatih/vim-go/pull/2117) + +IMPROVEMENTS: +* Disable `g:go_gocode_propose_source` by default. + [[GH-2050]](https://github.com/fatih/vim-go/pull/2050) +* Don't spam users when Vim is run with vi compatibility. + [[GH-2055]](https://github.com/fatih/vim-go/pull/2055) +* Add bang support to lint commands to allow them to be run without jumping to + errors. + [[GH-2056]](https://github.com/fatih/vim-go/pull/2056) +* Use `go doc` for `:GoDoc` instead of `godoc`. + [[GH-2070]](https://github.com/fatih/vim-go/pull/2070) +* Detach from and shutdown dlv correctly. + [[GH-2075]](https://github.com/fatih/vim-go/pull/2075) +* Do not require `'autowrite'` or `'autowriteall'` to be set when using + autocompletion in module mode. + [[GH-2091]](https://github.com/fatih/vim-go/pull/2091) +* Fix use of `g:go_metalinter_command` _and_ apply it even when autosaving. + [[GH-2101]](https://github.com/fatih/vim-go/pull/2101) +* Report errors in quickfix when Delve fails to start (e.g. compiler errors). + [[GH-2111]](https://github.com/fatih/vim-go/pull/2111) +* Support `'undo_ftplugin'`, make most autocmds buffer-local, and only do the + bare minimum based on file names alone. + [[GH-2108]](https://github.com/fatih/vim-go/pull/2108) +* Write a message when `:GoInfo` can't display any results when `g:go_info_mode='gocode'`. + [[GH-2122]](https://github.com/fatih/vim-go/pull/2122) +* Highlight fields followed by an operator when `g:go_highlight_fields` is set. + [[GH-1907]](https://github.com/fatih/vim-go/pull/1907) +* Skip autosave actions when the buffer is not a readable file. + [[GH-2143]](https://github.com/fatih/vim-go/pull/2143) +* Run `godef` from the current buffer's directory to make sure it works with modules. + [[GH-2150]](https://github.com/fatih/vim-go/pull/2150) +* Add a function, `go#tool#DescribeBalloon`, to show information in a balloon + with `'balloonexpr'`. (Vim8 only). + [[GH-1975]](https://github.com/fatih/vim-go/pull/1975) +* Add initial support for `gopls`. + [[GH-2163]](https://github.com/fatih/vim-go/pull/2163). +* Add `:GoDefType` to jump to the type definition of the identifier under the + cursor. + [[GH-2165]](https://github.com/fatih/vim-go/pull/2165) +* Notify gopls about changes. + [[GH-2171]](https://github.com/fatih/vim-go/pull/2171) +* Respect `g:go_jump_to_error` when running `gometalinter` automatically on + save. [[GH-2176]](https://github.com/fatih/vim-go/pull/2176) +* Use gopls for code completion by default in Vim8 and Neovim. + [[GH-2172]](https://github.com/fatih/vim-go/pull/2172) +* Add support for golangci-lint. + [[GH-2182]](https://github.com/fatih/vim-go/pull/2182) +* Show hover balloon using gopls instead of gocode. + [[GH-2202]](https://github.com/fatih/vim-go/pull/2202) +* Add a new option, `g:go_debug_log_output`, to control logging with the + debugger. + [[GH-2203]](https://github.com/fatih/vim-go/pull/2203) +* Do not jump to quickfix or location list window when bang is used for async + jobs or linting. + [[GH-2205]](https://github.com/fatih/vim-go/pull/2205) +* Tab complete package names for commands from vendor directories and in + modules. + [[GH-2213]](https://github.com/fatih/vim-go/pull/2213) +* Add support for `gopls` to `g:go_info_mode`. + [[GH-2224]](https://github.com/fatih/vim-go/pull/2224) + +BUG FIXES: +* Fix opening of non-existent file from `:GoDeclsDir` when the current + directory is not the directory containing the current buffer. + [[GH-2048]](https://github.com/fatih/vim-go/pull/2048) +* Fix jumping to an identifier with godef from a modified buffer. + [[GH-2054]](https://github.com/fatih/vim-go/pull/2054) +* Fix errors when `g:go_debug` contains `debugger-commands`. + [[GH-2075]](https://github.com/fatih/vim-go/pull/2075) +* Fix errors from `:GoDebugStop` in Neovim. + [[GH-2075]](https://github.com/fatih/vim-go/pull/2075) +* Fix `:GoSameIdsToggle`. + [[GH-2086]](https://github.com/fatih/vim-go/pull/2086) +* Do not set fileencoding or fileformat options or populate from template when + the buffer is not modifiable. + [[GH-2097]](https://github.com/fatih/vim-go/pull/2097) +* Do not clear buffer-local autocmds of other buffers. + [[GH-2109]](https://github.com/fatih/vim-go/pull/2109) +* Highlight return parameter types when g:go_highlight_function_arguments is + set. [[GH-2116]](https://github.com/fatih/vim-go/pull/2116) +* Fix lockup in Neovim when trying to run `:GoDebugTest` when there are no + tests. [[GH-2125]](https://github.com/fatih/vim-go/pull/2125) +* Keep track of breakpoints correctly when buffer is edited after breakpoints + are set. + [[GH-2126]](https://github.com/fatih/vim-go/pull/2126) +* Fix race conditions in `:GoDebugStop`. + [[GH-2127]](https://github.com/fatih/vim-go/pull/2127) +* Fix jumping to module or package using godef. + [[GH-2141]](https://github.com/fatih/vim-go/pull/2141) +* Fix errors caused by redefining functions within functions. + [[GH-2189]](https://github.com/fatih/vim-go/pull/2189) +* Highlight pre-release and metadata in versions in go.mod. + [[GH-2192]](https://github.com/fatih/vim-go/pull/2192) +* Handle runtime panics from `:GoRun` when using Neovim's terminal. + [[GH-2209]](https://github.com/fatih/vim-go/pull/2209) +* Fix adding tag option when a tag is added. + [[GH-2227]](https://github.com/fatih/vim-go/pull/2227) + +## 1.19 - (November 4, 2018) + +FEATURES: + +* **go.mod file support!** This is the first feature for upcoming Go modules + support. The followings are added: + * Syntax highlighting for the `go.mod` file. + * A new `gomod` filetype is set if a `go.mod` file has been opened and starts + with the line `module ` + * New **:GoModFmt** command that formats the `go.mod` file + * Auto format on save feature for `:GoModFmt`, enabled automatically. Can be + toggled of with the setting `g:go_mod_fmt_autosave` or with the command: + `GoModFmtAutoSaveToggle` + [[GH-1931]](https://github.com/fatih/vim-go/pull/1931) + +IMPROVEMENTS: +* Unify async job handling for Vim8 and Neovim. + [[GH-1864]](https://github.com/fatih/vim-go/pull/1864) +* Document Vim and Neovim requirements in README.md and help file. + [[GH-1889]](https://github.com/fatih/vim-go/pull/1889) +* Highlight `context.Context` when `g:go_highlight_extra_types` is set. + [[GH-1903]](https://github.com/fatih/vim-go/pull/1903) +* Run gometalinter asynchronously in Neovim. + [[GH-1901]](https://github.com/fatih/vim-go/pull/1901) +* Run gorename asynchronously in Vim8 and Neovim. + [[GH-1894]](https://github.com/fatih/vim-go/pull/1894) +* Install keyify from its canonical import path. + [[GH-1924]](https://github.com/fatih/vim-go/pull/1924) +* Update the tested version of Neovim to v0.3.1. + [[GH-1923]](https://github.com/fatih/vim-go/pull/1923) +* Run autocompletion asynchronously in Vim8 and Neovim. + [[GH-1926]](https://github.com/fatih/vim-go/pull/1926) +* Show statusline update when running `:GoInfo` with `g:go_info_mode='gocode'`. + [[GH-1937]](https://github.com/fatih/vim-go/pull/1937) +* Do not update statusline when highlighting sameids or showing type info via + an autocmd. + [[GH-1937]](https://github.com/fatih/vim-go/pull/1937) +* Do not indent within a raw string literal. + [[GH-1858]](https://github.com/fatih/vim-go/pull/1858) +* Highlight Go's predeclared function identifiers (the functions in `builtins`) + using keyword groups and highlight them using the `Identifiers` group. + [[GH-1939]](https://github.com/fatih/vim-go/pull/1939) +* Add a new FAQ entry to instruct users how to modify the vim-go highlight + groups. + [[GH-1939]](https://github.com/fatih/vim-go/pull/1939) +* Improve use of statusline and progress messages. + [[GH-1948]](https://github.com/fatih/vim-go/pull/1948) +* Add `tt` snippet to create a table test boilerplate (see + https://github.com/golang/go/wiki/TableDrivenTests for more information on + how to use a table driven test). + [[GH-1956]](https://github.com/fatih/vim-go/pull/1956) +* Add `(go-decls)` and `(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 `(go-iferr)` plug key to + create a custom mapping. This command generates an `if err != nil { return ... }` + automatically which infer the type of return values and the numbers. + For example: + + ``` + func doSomething() (string, error) { + f, err := os.Open("file") + } + ``` + + Becomes: + + ``` + func doSomething() (string, error) { + f, err := os.Open("file") + if err != nil { + return "", err + } + } + ``` + +* Two new text objects has been added: + * `ic` (inner comment) selects the content of the comment, excluding the start/end markers (i.e: `//`, `/*`) + * `ac` (a comment) selects the content of the whole commment block, including markers + To use this new feature, make sure you use use the latest version of + [motion](https://github.com/fatih/motion). You can update the tool from Vim + via `:GoUpdateBinaries` + [[GH-1779]](https://github.com/fatih/vim-go/pull/1779) +* Add `:GoPointsTo` to show all variables to which the pointer under the cursor + may point to. + [[GH-1751]](https://github.com/fatih/vim-go/pull/1751) +* Add `:GoReportGitHubIssue` to initialize a new GitHub issue with as much data + that our template requests as possible. + [[GH-1738]](https://github.com/fatih/vim-go/pull/1738) + +IMPROVEMENTS: + +* Add build tags (with `g:go_build_tags`) to all commands that support it. + [[GH-1705]](https://github.com/fatih/vim-go/pull/1705) +* Some command which operate on files (rather than Vim buffers) will now show a + warning if there are unsaved buffers, similar to Vim's `:make`. + [[GH-1754]](https://github.com/fatih/vim-go/pull/1754) +* Don't return an error from `:GoGuru` functions when the import path is + unknown and scope is unneeded. + [[GH-1826]](https://github.com/fatih/vim-go/pull/1826) +* Performance improvements for the `go.vim` syntax file. + [[GH-1799]](https://github.com/fatih/vim-go/pull/1799) +* Allow `GoDebugBreakpoint` and `GoDebugCurrent` highlight groups to be + overridden by user configuration. + [[GH-1850]](https://github.com/vim-go/pull/1850) +* Strip trailing carriage returns from quickfix errors that are parsed + manually. [[GH-1861]](https://github.com/fatih/vim-go/pull/1861). +* Cleanup title of terminal window. + [[GH-1861]](https://github.com/fatih/vim-go/pull/1861). +* Add `:GoImpl` is able to complete interfaces by their full import path in + addition to the current package name (i.e: `:GoImpl t *T github.com/BurntSushi/toml.Unmarshaller` + is now possible) + [[GH-1884]](https://github.com/fatih/vim-go/pull/1884) + +BUG FIXES: + +* Update the correct window's location list after a long running async job + completes, even when the user changes their window layout while the job is + running. + [[GH-1734]](https://github.com/fatih/vim-go/pull/1734) +* Apply debugger mappings only for Go buffers, and not all buffers. + [[GH-1696]](https://github.com/fatih/vim-go/pull/1696) +* The `gohtmltmpl` filetype will now highlight `{{ .. }}` syntax HTML attributes + and some other locations. + [[GH-1790]](https://github.com/fatih/vim-go/pull/1790) +* Use the correct logging flag argument for delve. + [[GH-1809]](https://github.com/fatih/vim-go/pull/1809) +* Fix gocode option string values that would cause gocode settings not to set + correctly + [[GH-1818]](https://github.com/fatih/vim-go/pull/1818) +* Fix Neovim handling of guru output. + [[GH-1846]](https://github.com/fatih/vim-go/pull/1846) +* Execute commands correctly when they are in $GOBIN but not $PATH. + [[GH-1866]](https://github.com/fatih/vim-go/pull/1866) +* Open files correctly with ctrlp. + [[GH-1878]](https://github.com/fatih/vim-go/pull/1878) +* Fix checking guru binary path + [[GH-1886]](https://github.com/fatih/vim-go/pull/1886) +* Add build tags to `:GoDef` if only it's present + [[GH-1882]](https://github.com/fatih/vim-go/pull/1882) + ## 1.17 - (March 27, 2018) FEATURES: * **Debugger support!** Add integrated support for the - [`delve`](https://github.com/derekparker/delve) debugger. Use + [`delve`](https://github.com/go-delve/delve) debugger. Use `:GoInstallBinaries` to install `dlv`, and see `:help go-debug` to get started. [[GH-1390]](https://github.com/fatih/vim-go/pull/1390) diff --git a/pack/acp/start/vim-go/Dockerfile b/pack/acp/start/vim-go/Dockerfile index e7ff629..7f03ba7 100644 --- a/pack/acp/start/vim-go/Dockerfile +++ b/pack/acp/start/vim-go/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.9.2 +FROM golang:1.12.1 RUN apt-get update -y && \ apt-get install -y build-essential curl git libncurses5-dev python3-pip && \ diff --git a/pack/acp/start/vim-go/Makefile b/pack/acp/start/vim-go/Makefile index 54978e8..717eb0e 100644 --- a/pack/acp/start/vim-go/Makefile +++ b/pack/acp/start/vim-go/Makefile @@ -1,6 +1,6 @@ VIMS ?= vim-7.4 vim-8.0 nvim -all: install test lint +all: install lint test install: @echo "==> Installing Vims: $(VIMS)" diff --git a/pack/acp/start/vim-go/README.md b/pack/acp/start/vim-go/README.md index d211376..c47c8f3 100644 --- a/pack/acp/start/vim-go/README.md +++ b/pack/acp/start/vim-go/README.md @@ -9,11 +9,11 @@ This plugin adds Go language support for Vim, with the following main features: * Compile your package with `:GoBuild`, install it with `:GoInstall` or test it - with `:GoTest`. Run a single tests with `:GoTestFunc`). + with `:GoTest`. Run a single test with `:GoTestFunc`). * Quickly execute your current file(s) with `:GoRun`. * Improved syntax highlighting and folding. * Debug programs with integrated `delve` support with `:GoDebugStart`. -* Completion support via `gocode`. +* Completion support via `gocode` and `gopls`. * `gofmt` or `goimports` on save keeps the cursor position and undo history. * Go to symbol/declaration with `:GoDef`. * Look up documentation with `:GoDoc` or `:GoDocBrowser`. @@ -33,10 +33,13 @@ This plugin adds Go language support for Vim, with the following main features: ## Install +vim-go requires at least Vim 7.4.2009 or Neovim 0.3.1. + The [**latest stable release**](https://github.com/fatih/vim-go/releases/latest) is the recommended version to use. If you choose to use the master branch instead, please do so with caution; it is a _development_ branch. + vim-go follows the standard runtime path structure. Below are some helper lines for popular package managers: @@ -45,7 +48,9 @@ for popular package managers: * [Pathogen](https://github.com/tpope/vim-pathogen) * `git clone https://github.com/fatih/vim-go.git ~/.vim/bundle/vim-go` * [vim-plug](https://github.com/junegunn/vim-plug) - * `Plug 'fatih/vim-go'` + * `Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }` +* [Vundle](https://github.com/VundleVim/Vundle.vim) + * `Plugin 'fatih/vim-go'` You will also need to install all the necessary binaries. vim-go makes it easy to install all of them by providing a command, `:GoInstallBinaries`, which will diff --git a/pack/acp/start/vim-go/autoload/ctrlp/decls.vim b/pack/acp/start/vim-go/autoload/ctrlp/decls.vim index 00ecb89..5d65299 100644 --- a/pack/acp/start/vim-go/autoload/ctrlp/decls.vim +++ b/pack/acp/start/vim-go/autoload/ctrlp/decls.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + let s:go_decls_var = { \ 'init': 'ctrlp#decls#init()', \ 'exit': 'ctrlp#decls#exit()', @@ -20,7 +24,7 @@ function! ctrlp#decls#init() abort endfunction function! ctrlp#decls#exit() abort - unlet! s:decls s:current_dir s:target + unlet! s:decls s:target endfunction " The action to perform on the selected string @@ -32,10 +36,6 @@ function! ctrlp#decls#accept(mode, str) abort let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let dir = getcwd() try - " we jump to the file directory so we can get the fullpath via fnamemodify - " below - execute cd . s:current_dir - let vals = matchlist(a:str, '|\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)|') " i.e: main.go @@ -50,46 +50,41 @@ function! ctrlp#decls#accept(mode, str) abort call ctrlp#acceptfile(a:mode, filepath) call cursor(line, col) silent! norm! zvzz - finally - "jump back to old dir - execute cd . fnameescape(dir) endtry endfunction function! ctrlp#decls#enter() abort - let s:current_dir = fnameescape(expand('%:p:h')) let s:decls = [] - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return - endif - let command = printf("%s -format vim -mode decls", bin_path) - let command .= " -include ". get(g:, "go_decls_includes", "func,type") + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-mode', 'decls', + \ '-include', go#config#DeclsIncludes(), + \ ] call go#cmd#autowrite() if s:mode == 0 " current file mode - let fname = expand("%:p") + let l:fname = expand("%:p") if exists('s:target') - let fname = s:target + let l:fname = s:target endif - let command .= printf(" -file %s", fname) + let cmd += ['-file', l:fname] else " all functions mode - let dir = expand("%:p:h") + let l:dir = expand("%:p:h") if exists('s:target') - let dir = s:target + let l:dir = s:target endif - let command .= printf(" -dir %s", dir) + let cmd += ['-dir', l:dir] endif - let out = go#util#System(command) - if go#util#ShellError() != 0 - call go#util#EchoError(out) + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError(l:out) return endif @@ -118,7 +113,7 @@ function! ctrlp#decls#enter() abort call add(s:decls, printf("%s\t%s |%s:%s:%s|\t%s", \ decl.ident . space, \ decl.keyword, - \ fnamemodify(decl.filename, ":t"), + \ fnamemodify(decl.filename, ":p"), \ decl.line, \ decl.col, \ decl.full, @@ -152,4 +147,8 @@ function! ctrlp#decls#cmd(mode, ...) abort return s:id endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/fzf/decls.vim b/pack/acp/start/vim-go/autoload/fzf/decls.vim index 255320e..20e4f04 100644 --- a/pack/acp/start/vim-go/autoload/fzf/decls.vim +++ b/pack/acp/start/vim-go/autoload/fzf/decls.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + function! s:code(group, attr) abort let code = synIDattr(synIDtrans(hlID(a:group)), a:attr, "cterm") if code =~ '^[0-9]\+$' @@ -58,35 +62,34 @@ function! s:source(mode,...) abort let s:current_dir = expand('%:p:h') let ret_decls = [] - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return - endif - let command = printf("%s -format vim -mode decls", bin_path) - let command .= " -include ". get(g:, "go_decls_includes", "func,type") + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-mode', 'decls', + \ '-include', go#config#DeclsIncludes(), + \ ] call go#cmd#autowrite() if a:mode == 0 " current file mode - let fname = expand("%:p") + let l:fname = expand("%:p") if a:0 && !empty(a:1) - let fname = a:1 + let l:fname = a:1 endif - let command .= printf(" -file %s", shellescape(fname)) + let cmd += ['-file', l:fname] else " all functions mode if a:0 && !empty(a:1) let s:current_dir = a:1 endif - let command .= printf(" -dir %s", shellescape(s:current_dir)) + let l:cmd += ['-dir', s:current_dir] endif - let out = go#util#System(command) - if go#util#ShellError() != 0 - call go#util#EchoError(out) + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError(l:out) return endif @@ -147,4 +150,8 @@ function! fzf#decls#cmd(...) abort \ })) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/alternate.vim b/pack/acp/start/vim-go/autoload/go/alternate.vim index f2cb210..a067190 100644 --- a/pack/acp/start/vim-go/autoload/go/alternate.vim +++ b/pack/acp/start/vim-go/autoload/go/alternate.vim @@ -1,7 +1,6 @@ -" By default use edit (current buffer view) to switch -if !exists("g:go_alternate_mode") - let g:go_alternate_mode = "edit" -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim " Test alternates between the implementation of code and the test code. function! go#alternate#Switch(bang, cmd) abort @@ -23,10 +22,14 @@ function! go#alternate#Switch(bang, cmd) abort call go#util#EchoError("couldn't find ".alt_file) return elseif empty(a:cmd) - execute ":" . g:go_alternate_mode . " " . alt_file + execute ":" . go#config#AlternateMode() . " " . alt_file else execute ":" . a:cmd . " " . alt_file endif endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/asmfmt.vim b/pack/acp/start/vim-go/autoload/go/asmfmt.vim index cc8acd3..9aa5f47 100644 --- a/pack/acp/start/vim-go/autoload/go/asmfmt.vim +++ b/pack/acp/start/vim-go/autoload/go/asmfmt.vim @@ -15,6 +15,10 @@ " " Flag to automatically call :Fmt when file is saved. +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + let s:got_fmt_error = 0 " This is a trimmed-down version of the logic in fmt.vim. @@ -28,42 +32,45 @@ function! go#asmfmt#Format() abort call writefile(go#util#GetLines(), l:tmpname) " Run asmfmt. - let path = go#path#CheckBinPath("asmfmt") - if empty(path) + let [l:out, l:err] = go#util#Exec(['asmfmt', '-w', l:tmpname]) + if l:err + call go#util#EchoError(l:out) return endif - let out = go#util#System(path . ' -w ' . l:tmpname) - " If there's no error, replace the current file with the output. - if go#util#ShellError() == 0 - " Remove undo point caused by BufWritePre. - try | silent undojoin | catch | endtry + " Remove undo point caused by BufWritePre. + try | silent undojoin | catch | endtry - " Replace the current file with the temp file; then reload the buffer. - let old_fileformat = &fileformat - " save old file permissions - let original_fperm = getfperm(expand('%')) - call rename(l:tmpname, expand('%')) - " restore old file permissions - call setfperm(expand('%'), original_fperm) - silent edit! - let &fileformat = old_fileformat - let &syntax = &syntax - endif + " Replace the current file with the temp file; then reload the buffer. + let old_fileformat = &fileformat + + " save old file permissions + let original_fperm = getfperm(expand('%')) + call rename(l:tmpname, expand('%')) + + " restore old file permissions + call setfperm(expand('%'), original_fperm) + silent edit! + let &fileformat = old_fileformat + let &syntax = &syntax " Restore the cursor/window positions. call winrestview(l:curw) endfunction function! go#asmfmt#ToggleAsmFmtAutoSave() abort - if get(g:, "go_asmfmt_autosave", 0) - let g:go_asmfmt_autosave = 1 + if go#config#AsmfmtAutosave() + call go#config#SetAsmfmtAutosave(1) call go#util#EchoProgress("auto asmfmt enabled") return end - let g:go_asmfmt_autosave = 0 + call go#config#SetAsmfmtAutosave(0) call go#util#EchoProgress("auto asmfmt disabled") endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/auto.vim b/pack/acp/start/vim-go/autoload/go/auto.vim new file mode 100644 index 0000000..1174858 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/auto.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/cmd.vim b/pack/acp/start/vim-go/autoload/go/cmd.vim index 569572d..fdf2299 100644 --- a/pack/acp/start/vim-go/autoload/go/cmd.vim +++ b/pack/acp/start/vim-go/autoload/go/cmd.vim @@ -1,6 +1,21 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + function! go#cmd#autowrite() abort if &autowrite == 1 || &autowriteall == 1 silent! wall + else + for l:nr in range(0, bufnr('$')) + if buflisted(l:nr) && getbufvar(l:nr, '&modified') + " Sleep one second to make sure people see the message. Otherwise it is + " often immediacy overwritten by the async messages (which also don't + " invoke the "hit ENTER" prompt). + call go#util#EchoWarning('[No write since last change]') + sleep 1 + return + endif + endfor endif endfunction @@ -11,34 +26,21 @@ endfunction function! go#cmd#Build(bang, ...) abort " Create our command arguments. go build discards any results when it " compiles multiple packages. So we pass the `errors` package just as an - " placeholder with the current folder (indicated with '.'). We also pass -i - " that tries to install the dependencies, this has the side effect that it - " caches the build results, so every other build is faster. - let args = - \ ["build"] + + " placeholder with the current folder (indicated with '.'). + let l:args = + \ ['build', '-tags', go#config#BuildTags()] + \ map(copy(a:000), "expand(v:val)") + \ [".", "errors"] - " Vim async. + " Vim and Neovim async. if go#util#has_job() - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoProgress("building dispatched ...") - endif - call s:cmd_job({ \ 'cmd': ['go'] + args, \ 'bang': a:bang, \ 'for': 'GoBuild', + \ 'statustype': 'build' \}) - " Nvim async. - elseif has('nvim') - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoProgress("building dispatched ...") - endif - - call go#jobcontrol#Spawn(a:bang, "build", "GoBuild", args) - " Vim 7.4 without async else let default_makeprg = &makeprg @@ -59,6 +61,7 @@ function! go#cmd#Build(bang, ...) abort redraw! finally execute cd . fnameescape(dir) + let &makeprg = default_makeprg endtry let errors = go#list#Get(l:listtype) @@ -68,8 +71,6 @@ function! go#cmd#Build(bang, ...) abort else call go#util#EchoSuccess("[build] SUCCESS") endif - - let &makeprg = default_makeprg endif endfunction @@ -77,33 +78,44 @@ endfunction " BuildTags sets or shows the current build tags used for tools function! go#cmd#BuildTags(bang, ...) abort if a:0 - if a:0 == 1 && a:1 == '""' - unlet g:go_build_tags + let v = a:1 + if v == '""' || v == "''" + let v = "" + endif + call go#config#SetBuildTags(v) + let tags = go#config#BuildTags() + if empty(tags) call go#util#EchoSuccess("build tags are cleared") else - let g:go_build_tags = a:1 - call go#util#EchoSuccess("build tags are changed to: ". a:1) + call go#util#EchoSuccess("build tags are changed to: " . tags) endif return endif - if !exists('g:go_build_tags') + let tags = go#config#BuildTags() + if empty(tags) call go#util#EchoSuccess("build tags are not set") else - call go#util#EchoSuccess("current build tags: ". g:go_build_tags) + call go#util#EchoSuccess("current build tags: " . tags) endif endfunction " Run runs the current file (and their dependencies if any) in a new terminal. function! go#cmd#RunTerm(bang, mode, files) abort - if empty(a:files) - let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) - else - let cmd = "go run ". go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1) + let cmd = "go run " + let tags = go#config#BuildTags() + if len(tags) > 0 + let cmd .= "-tags " . go#util#Shellescape(tags) . " " endif - call go#term#newmode(a:bang, cmd, a:mode) + + if empty(a:files) + let cmd .= go#util#Shelljoin(go#tool#Files()) + else + let cmd .= go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1) + endif + call go#term#newmode(a:bang, cmd, s:runerrorformat(), a:mode) endfunction " Run runs the current file (and their dependencies if any) and outputs it. @@ -122,8 +134,19 @@ function! go#cmd#Run(bang, ...) abort " anything. Once this is implemented we're going to make :GoRun async endif + let cmd = "go run " + let tags = go#config#BuildTags() + if len(tags) > 0 + let cmd .= "-tags " . go#util#Shellescape(tags) . " " + endif + if go#util#IsWin() - exec '!go run ' . go#util#Shelljoin(go#tool#Files()) + if a:0 == 0 + exec '!' . cmd . go#util#Shelljoin(go#tool#Files(), 1) + else + exec '!' . cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) + endif + if v:shell_error redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None else @@ -136,29 +159,36 @@ function! go#cmd#Run(bang, ...) abort " :make expands '%' and '#' wildcards, so they must also be escaped let default_makeprg = &makeprg if a:0 == 0 - let &makeprg = 'go run ' . go#util#Shelljoin(go#tool#Files(), 1) + let &makeprg = cmd . go#util#Shelljoin(go#tool#Files(), 1) else - let &makeprg = "go run " . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) + let &makeprg = cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) endif let l:listtype = go#list#Type("GoRun") - if l:listtype == "locationlist" - exe 'lmake!' - else - exe 'make!' - endif + try - let items = go#list#Get(l:listtype) - let errors = go#tool#FilterValids(items) + " backup user's errorformat, will be restored once we are finished + let l:old_errorformat = &errorformat + let &errorformat = s:runerrorformat() + if l:listtype == "locationlist" + exe 'lmake!' + else + exe 'make!' + endif + finally + "restore errorformat + let &errorformat = l:old_errorformat + let &makeprg = default_makeprg + endtry - call go#list#Populate(l:listtype, errors, &makeprg) - call go#list#Window(l:listtype, len(errors)) - if !empty(errors) && !a:bang + let l:errors = go#list#Get(l:listtype) + + call go#list#Window(l:listtype, len(l:errors)) + if !empty(l:errors) && !a:bang call go#list#JumpToFirst(l:listtype) endif - let &makeprg = default_makeprg endfunction " Install installs the package by simple calling 'go install'. If any argument @@ -170,14 +200,11 @@ function! go#cmd#Install(bang, ...) abort " expand all wildcards(i.e: '%' to the current file name) let goargs = map(copy(a:000), "expand(v:val)") - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoProgress("installing dispatched ...") - endif - call s:cmd_job({ - \ 'cmd': ['go', 'install'] + goargs, + \ 'cmd': ['go', 'install', '-tags', go#config#BuildTags()] + goargs, \ 'bang': a:bang, \ 'for': 'GoInstall', + \ 'statustype': 'install' \}) return endif @@ -203,6 +230,7 @@ function! go#cmd#Install(bang, ...) abort redraw! finally execute cd . fnameescape(dir) + let &makeprg = default_makeprg endtry let errors = go#list#Get(l:listtype) @@ -212,8 +240,6 @@ function! go#cmd#Install(bang, ...) abort else call go#util#EchoSuccess("installed to ". go#path#Default()) endif - - let &makeprg = default_makeprg endfunction " Generate runs 'go generate' in similar fashion to go#cmd#Build() @@ -232,12 +258,17 @@ function! go#cmd#Generate(bang, ...) abort let l:listtype = go#list#Type("GoGenerate") echon "vim-go: " | echohl Identifier | echon "generating ..."| echohl None - if l:listtype == "locationlist" - silent! exe 'lmake!' - else - silent! exe 'make!' - endif - redraw! + + try + if l:listtype == "locationlist" + silent! exe 'lmake!' + else + silent! exe 'make!' + endif + finally + redraw! + let &makeprg = default_makeprg + endtry let errors = go#list#Get(l:listtype) call go#list#Window(l:listtype, len(errors)) @@ -249,64 +280,27 @@ function! go#cmd#Generate(bang, ...) abort redraws! | echon "vim-go: " | echohl Function | echon "[generate] SUCCESS"| echohl None endif - let &makeprg = default_makeprg +endfunction + +function! s:runerrorformat() + let l:panicaddress = "%\\t%#%f:%l +0x%[0-9A-Fa-f]%\\+" + let l:errorformat = '%A' . l:panicaddress . "," . &errorformat + return l:errorformat endfunction " --------------------- " | Vim job callbacks | " --------------------- -function s:cmd_job(args) abort - let status_dir = expand('%:p:h') - let started_at = reltime() - - call go#statusline#Update(status_dir, { - \ 'desc': "current status", - \ 'type': a:args.cmd[1], - \ 'state': "started", - \}) - +function! s:cmd_job(args) abort " autowrite is not enabled for jobs call go#cmd#autowrite() - function! s:complete(job, exit_status, data) closure abort - let status = { - \ 'desc': 'last status', - \ 'type': a:args.cmd[1], - \ 'state': "success", - \ } - - if a:exit_status - let status.state = "failed" - endif - - let elapsed_time = reltimestr(reltime(started_at)) - " strip whitespace - let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') - let status.state .= printf(" (%ss)", elapsed_time) - - call go#statusline#Update(status_dir, status) - endfunction - - let a:args.complete = funcref('s:complete') - let callbacks = go#job#Spawn(a:args) - - let start_options = { - \ 'callback': callbacks.callback, - \ 'exit_cb': callbacks.exit_cb, - \ 'close_cb': callbacks.close_cb, - \ } - - " pre start - let dir = getcwd() - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - call job_start(a:args.cmd, start_options) - - " post start - execute cd . fnameescape(dir) + call go#job#Spawn(a:args.cmd, a:args) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/cmd_test.vim b/pack/acp/start/vim-go/autoload/go/cmd_test.vim index ef39110..708a3ab 100644 --- a/pack/acp/start/vim-go/autoload/go/cmd_test.vim +++ b/pack/acp/start/vim-go/autoload/go/cmd_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_GoBuildErrors() try let l:filename = 'cmd/bad.go' @@ -27,4 +31,8 @@ func! Test_GoBuildErrors() endtry endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/complete.vim b/pack/acp/start/vim-go/autoload/go/complete.vim index 8013f00..987c484 100644 --- a/pack/acp/start/vim-go/autoload/go/complete.vim +++ b/pack/acp/start/vim-go/autoload/go/complete.vim @@ -1,16 +1,39 @@ -let s:sock_type = (has('win32') || has('win64')) ? 'tcp' : 'unix' +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim function! s:gocodeCommand(cmd, args) abort - let bin_path = go#path#CheckBinPath("gocode") + let l:gocode_bin = "gocode" + let l:gomod = go#util#gomod() + if filereadable(l:gomod) + let l:gocode_bin = "gocode-gomod" + endif + + let bin_path = go#path#CheckBinPath(l:gocode_bin) if empty(bin_path) return [] endif - let socket_type = get(g:, 'go_gocode_socket_type', s:sock_type) + let socket_type = go#config#GocodeSocketType() let cmd = [bin_path] let cmd = extend(cmd, ['-sock', socket_type]) let cmd = extend(cmd, ['-f', 'vim']) + + if go#config#GocodeProposeBuiltins() + let cmd = extend(cmd, ['-builtin']) + endif + + if go#config#GocodeProposeSource() + let cmd = extend(cmd, ['-source']) + else + let cmd = extend(cmd, ['-fallback-to-source', '-cache']) + endif + + if go#config#GocodeUnimportedPackages() + let cmd = extend(cmd, ['-unimported-packages']) + endif + let cmd = extend(cmd, [a:cmd]) let cmd = extend(cmd, a:args) @@ -45,32 +68,7 @@ function! s:sync_gocode(cmd, args, input) abort return l:result endfunction -" TODO(bc): reset when gocode isn't running -let s:optionsEnabled = 0 -function! s:gocodeEnableOptions() abort - if s:optionsEnabled - return - endif - - let bin_path = go#path#CheckBinPath("gocode") - if empty(bin_path) - return - endif - - let s:optionsEnabled = 1 - - call go#util#System(printf('%s set propose-builtins %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_propose_builtins', 1)))) - call go#util#System(printf('%s set autobuild %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_autobuild', 1)))) - call go#util#System(printf('%s set unimported-packages %s', go#util#Shellescape(bin_path), s:toBool(get(g:, 'go_gocode_unimported_packages', 0)))) -endfunction - -function! s:toBool(val) abort - if a:val | return 'true ' | else | return 'false' | endif -endfunction - function! s:gocodeAutocomplete() abort - call s:gocodeEnableOptions() - " use the offset as is, because the cursor position is the position for " which autocomplete candidates are needed. return s:sync_gocode('autocomplete', @@ -84,60 +82,20 @@ function! go#complete#GetInfo() abort return s:sync_info(0) endfunction -function! go#complete#Info(auto) abort - if go#util#has_job() - return s:async_info(a:auto) +function! go#complete#Info(showstatus) abort + if go#util#has_job(1) + return s:async_info(1, a:showstatus) else - return s:sync_info(a:auto) + return s:sync_info(1) endif endfunction -function! s:async_info(auto) - if exists("s:async_info_job") - call job_stop(s:async_info_job) - unlet s:async_info_job - endif +function! s:async_info(echo, showstatus) + let state = {'echo': a:echo} - let state = { - \ 'exited': 0, - \ 'exit_status': 0, - \ 'closed': 0, - \ 'messages': [], - \ 'auto': a:auto - \ } - - function! s:callback(chan, msg) dict - let l:msg = a:msg - if &encoding != 'utf-8' - let l:msg = iconv(l:msg, 'utf-8', &encoding) - endif - call add(self.messages, l:msg) - endfunction - - function! s:exit_cb(job, exitval) dict - let self.exit_status = a:exitval - let self.exited = 1 - - if self.closed - call self.complete() - endif - endfunction - - function! s:close_cb(ch) dict - let self.closed = 1 - if self.exited - call self.complete() - endif - endfunction - - function state.complete() dict - if self.exit_status != 0 - return - endif - - let result = s:info_filter(self.auto, join(self.messages, "\n")) - call s:info_complete(self.auto, result) - endfunction + " explicitly bind complete to state so that within it, self will + " always refer to state. See :help Partial for more information. + let state.complete = function('s:complete', [], state) " add 1 to the offset, so that the position at the cursor will be included " in gocode's search @@ -149,23 +107,49 @@ function! s:async_info(auto) \ "GOROOT": go#util#env("goroot") \ } + let opts = { + \ 'bang': 1, + \ 'complete': state.complete, + \ 'for': '_', + \ } + + if a:showstatus + let opts.statustype = 'gocode' + endif + + let opts = go#job#Options(l:opts) + let cmd = s:gocodeCommand('autocomplete', \ [expand('%:p'), offset]) - " TODO(bc): Don't write the buffer to a file; pass the buffer directrly to + " TODO(bc): Don't write the buffer to a file; pass the buffer directly to " gocode's stdin. It shouldn't be necessary to use {in_io: 'file', in_name: " s:gocodeFile()}, but unfortunately {in_io: 'buffer', in_buf: bufnr('%')} - " should work. - let options = { + " doesn't work. + call extend(opts, { \ 'env': env, \ 'in_io': 'file', \ 'in_name': s:gocodeFile(), - \ 'callback': funcref("s:callback", [], state), - \ 'exit_cb': funcref("s:exit_cb", [], state), - \ 'close_cb': funcref("s:close_cb", [], state) - \ } + \ }) - let s:async_info_job = job_start(cmd, options) + call go#job#Start(cmd, opts) +endfunction + +function! s:complete(job, exit_status, messages) abort dict + if a:exit_status != 0 + return + endif + + if &encoding != 'utf-8' + let i = 0 + while i < len(a:messages) + let a:messages[i] = iconv(a:messages[i], 'utf-8', &encoding) + let i += 1 + endwhile + endif + + let result = s:info_filter(self.echo, join(a:messages, "\n")) + call s:info_complete(self.echo, result) endfunction function! s:gocodeFile() @@ -174,9 +158,7 @@ function! s:gocodeFile() return file endfunction -function! s:sync_info(auto) - " auto is true if we were called by g:go_auto_type_info's autocmd - +function! s:sync_info(echo) " add 1 to the offset, so that the position at the cursor will be included " in gocode's search let offset = go#util#OffsetCursor()+1 @@ -185,11 +167,11 @@ function! s:sync_info(auto) \ [expand('%:p'), offset], \ go#util#GetLines()) - let result = s:info_filter(a:auto, result) - call s:info_complete(a:auto, result) + let result = s:info_filter(a:echo, result) + return s:info_complete(a:echo, result) endfunction -function! s:info_filter(auto, result) abort +function! s:info_filter(echo, result) abort if empty(a:result) return "" endif @@ -203,7 +185,7 @@ function! s:info_filter(auto, result) abort if len(l:candidates) == 1 " When gocode panics in vim mode, it returns " [0, [{'word': 'PANIC', 'abbr': 'PANIC PANIC PANIC', 'info': 'PANIC PANIC PANIC'}]] - if a:auto && l:candidates[0].info ==# "PANIC PANIC PANIC" + if a:echo && l:candidates[0].info ==# "PANIC PANIC PANIC" return "" endif @@ -216,17 +198,21 @@ function! s:info_filter(auto, result) abort let wordMatch = substitute(wordMatch, "'", "''", "g") let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'") - if len(l:filtered) != 1 - return "" + if len(l:filtered) == 0 + return "no matches" + elseif len(l:filtered) > 1 + return "ambiguous match" endif return l:filtered[0].info endfunction -function! s:info_complete(auto, result) abort - if !empty(a:result) - echo "vim-go: " | echohl Function | echon a:result | echohl None +function! s:info_complete(echo, result) abort + if a:echo + call go#util#ShowInfo(a:result) endif + + return a:result endfunction function! s:trim_bracket(val) abort @@ -234,12 +220,22 @@ function! s:trim_bracket(val) abort return a:val endfunction -let s:completions = "" -function! go#complete#Complete(findstart, base) abort +let s:completions = [] + +function! go#complete#GocodeComplete(findstart, base) abort "findstart = 1 when we need to get the text length if a:findstart == 1 - execute "silent let s:completions = " . s:gocodeAutocomplete() - return col('.') - s:completions[0] - 1 + let l:completions = [] + execute "silent let l:completions = " . s:gocodeAutocomplete() + + if len(l:completions) == 0 || len(l:completions) >= 2 && len(l:completions[1]) == 0 + " no matches. cancel and leave completion mode. + call go#util#EchoInfo("no matches") + return -3 + endif + + let s:completions = l:completions[1] + return col('.') - l:completions[0] - 1 "findstart = 0 when we need to return the list of completions else let s = getline(".")[col('.') - 1] @@ -251,15 +247,49 @@ function! go#complete#Complete(findstart, base) abort endif endfunction +function! go#complete#Complete(findstart, base) abort + let l:state = {'done': 0, 'matches': []} + + function! s:handler(state, matches) abort dict + let a:state.matches = a:matches + let a:state.done = 1 + endfunction + + "findstart = 1 when we need to get the start of the match + if a:findstart == 1 + call go#lsp#Completion(expand('%:p'), line('.'), col('.'), funcref('s:handler', [l:state])) + + while !l:state.done + sleep 10m + endwhile + + let s:completions = l:state.matches + + if len(l:state.matches) == 0 + " no matches. cancel and leave completion mode. + call go#util#EchoInfo("no matches") + return -3 + endif + + return col('.') + else "findstart = 0 when we need to return the list of completions + return s:completions + endif +endfunction + function! go#complete#ToggleAutoTypeInfo() abort - if get(g:, "go_auto_type_info", 0) - let g:go_auto_type_info = 0 + if go#config#AutoTypeInfo() + call go#config#SetAutoTypeInfo(0) call go#util#EchoProgress("auto type info disabled") return end - let g:go_auto_type_info = 1 + call go#config#SetAutoTypeInfo(1) call go#util#EchoProgress("auto type info enabled") endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/complete_test.vim b/pack/acp/start/vim-go/autoload/go/complete_test.vim new file mode 100644 index 0000000..4397142 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/complete_test.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/config.vim b/pack/acp/start/vim-go/autoload/go/config.vim new file mode 100644 index 0000000..a3ef0a6 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/config.vim @@ -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("") =~# "^[A-Z]"' . + \ '? go#util#pascalcase(expand(""))' . + \ ': go#util#camelcase(expand(""))') +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 diff --git a/pack/acp/start/vim-go/autoload/go/coverage.vim b/pack/acp/start/vim-go/autoload/go/coverage.vim index 7f362ca..50d4df1 100644 --- a/pack/acp/start/vim-go/autoload/go/coverage.vim +++ b/pack/acp/start/vim-go/autoload/go/coverage.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + let s:toggle = 0 " Buffer creates a new cover profile with 'go test -coverprofile' and changes @@ -44,43 +48,28 @@ function! go#coverage#Buffer(bang, ...) abort let s:toggle = 1 let l:tmpname = tempname() - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoProgress("testing...") - endif - if go#util#has_job() call s:coverage_job({ - \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname] + a:000, + \ 'cmd': ['go', 'test', '-tags', go#config#BuildTags(), '-coverprofile', l:tmpname] + a:000, \ 'complete': function('s:coverage_callback', [l:tmpname]), \ 'bang': a:bang, \ 'for': 'GoTest', + \ 'statustype': 'coverage', \ }) return endif + if go#config#EchoCommandInfo() + call go#util#EchoProgress("testing...") + endif + let args = [a:bang, 0, "-coverprofile", l:tmpname] if a:0 call extend(args, a:000) endif - let disabled_term = 0 - if get(g:, 'go_term_enabled') - let disabled_term = 1 - let g:go_term_enabled = 0 - endif - let id = call('go#test#Test', args) - if disabled_term - let g:go_term_enabled = 1 - endif - - if has('nvim') - call go#jobcontrol#AddHandler(function('s:coverage_handler')) - let s:coverage_handler_jobs[id] = l:tmpname - return - endif - if go#util#ShellError() == 0 call go#coverage#overlay(l:tmpname) endif @@ -96,7 +85,7 @@ function! go#coverage#Clear() abort " remove the autocmd we defined augroup vim-go-coverage - autocmd! + autocmd! * augroup end endfunction @@ -106,10 +95,11 @@ function! go#coverage#Browser(bang, ...) abort let l:tmpname = tempname() if go#util#has_job() call s:coverage_job({ - \ 'cmd': ['go', 'test', '-coverprofile', l:tmpname], + \ 'cmd': ['go', 'test', '-tags', go#config#BuildTags(), '-coverprofile', l:tmpname], \ 'complete': function('s:coverage_browser_callback', [l:tmpname]), \ 'bang': a:bang, \ 'for': 'GoTest', + \ 'statustype': 'coverage', \ }) return endif @@ -120,16 +110,9 @@ function! go#coverage#Browser(bang, ...) abort endif let id = call('go#test#Test', args) - if has('nvim') - call go#jobcontrol#AddHandler(function('s:coverage_browser_handler')) - let s:coverage_browser_handler_jobs[id] = l:tmpname - return - endif - if go#util#ShellError() == 0 - let openHTML = 'go tool cover -html='.l:tmpname - call go#tool#ExecuteInDir(openHTML) + call go#util#ExecInDir(['go', 'tool', 'cover', '-html=' . l:tmpname]) endif call delete(l:tmpname) @@ -259,7 +242,7 @@ function! go#coverage#overlay(file) abort " clear the matches if we leave the buffer augroup vim-go-coverage - autocmd! + autocmd! * autocmd BufWinLeave call go#coverage#Clear() augroup end @@ -277,48 +260,17 @@ function s:coverage_job(args) " autowrite is not enabled for jobs call go#cmd#autowrite() - let status_dir = expand('%:p:h') - let Complete = a:args.complete - function! s:complete(job, exit_status, data) closure - let status = { - \ 'desc': 'last status', - \ 'type': "coverage", - \ 'state': "finished", - \ } + let disabled_term = 0 + if go#config#TermEnabled() + let disabled_term = 1 + call go#config#SetTermEnabled(0) + endif - if a:exit_status - let status.state = "failed" - endif + call go#job#Spawn(a:args.cmd, a:args) - call go#statusline#Update(status_dir, status) - return Complete(a:job, a:exit_status, a:data) - endfunction - - let a:args.complete = funcref('s:complete') - let callbacks = go#job#Spawn(a:args) - - let start_options = { - \ 'callback': callbacks.callback, - \ 'exit_cb': callbacks.exit_cb, - \ 'close_cb': callbacks.close_cb, - \ } - - " pre start - let dir = getcwd() - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - call go#statusline#Update(status_dir, { - \ 'desc': "current status", - \ 'type': "coverage", - \ 'state': "started", - \}) - - call job_start(a:args.cmd, start_options) - - " post start - execute cd . fnameescape(dir) + if disabled_term + call go#config#SetTermEnabled(1) + endif endfunction " coverage_callback is called when the coverage execution is finished @@ -332,47 +284,14 @@ endfunction function! s:coverage_browser_callback(coverfile, job, exit_status, data) if a:exit_status == 0 - let openHTML = 'go tool cover -html='.a:coverfile - call go#tool#ExecuteInDir(openHTML) + call go#util#ExecInDir(['go', 'tool', 'cover', '-html=' . a:coverfile]) endif call delete(a:coverfile) endfunction -" ----------------------- -" | Neovim job handlers | -" ----------------------- - -let s:coverage_handler_jobs = {} -let s:coverage_browser_handler_jobs = {} - -function! s:coverage_handler(job, exit_status, data) abort - if !has_key(s:coverage_handler_jobs, a:job.id) - return - endif - let l:tmpname = s:coverage_handler_jobs[a:job.id] - if a:exit_status == 0 - call go#coverage#overlay(l:tmpname) - endif - - call delete(l:tmpname) - unlet s:coverage_handler_jobs[a:job.id] -endfunction - -function! s:coverage_browser_handler(job, exit_status, data) abort - if !has_key(s:coverage_browser_handler_jobs, a:job.id) - return - endif - - let l:tmpname = s:coverage_browser_handler_jobs[a:job.id] - if a:exit_status == 0 - let openHTML = 'go tool cover -html='.l:tmpname - call go#tool#ExecuteInDir(openHTML) - endif - - call delete(l:tmpname) - unlet s:coverage_browser_handler_jobs[a:job.id] -endfunction - +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/debug.vim b/pack/acp/start/vim-go/autoload/go/debug.vim index 64e4f8d..1d11301 100644 --- a/pack/acp/start/vim-go/autoload/go/debug.vim +++ b/pack/acp/start/vim-go/autoload/go/debug.vim @@ -1,22 +1,13 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + scriptencoding utf-8 -if !exists('g:go_debug_windows') - let g:go_debug_windows = { - \ 'stack': 'leftabove 20vnew', - \ 'out': 'botright 10new', - \ 'vars': 'leftabove 30vnew', - \ } -endif - -if !exists('g:go_debug_address') - let g:go_debug_address = '127.0.0.1:8181' -endif - if !exists('s:state') let s:state = { \ 'rpcid': 1, \ 'running': 0, - \ 'breakpoint': {}, \ 'currentThread': {}, \ 'localVars': {}, \ 'functionArgs': {}, @@ -25,7 +16,7 @@ if !exists('s:state') \} if go#util#HasDebug('debugger-state') - let g:go_debug_diag = s:state + call go#config#SetDebugDiag(s:state) endif endif @@ -37,14 +28,35 @@ function! s:groutineID() abort return s:state['currentThread'].goroutineID endfunction -function! s:exit(job, status) abort +function! s:complete(job, exit_status, data) abort + let l:gotready = get(s:state, 'ready', 0) + " copy messages to a:data _only_ when dlv exited non-zero and it was never + " detected as ready (e.g. there was a compiler error). + if a:exit_status > 0 && !l:gotready + " copy messages to data so that vim-go's usual handling of errors from + " async jobs will occur. + call extend(a:data, s:state['message']) + endif + + " return early instead of clearing any variables when the current job is not + " a:job + if has_key(s:state, 'job') && s:state['job'] != a:job + return + endif + if has_key(s:state, 'job') call remove(s:state, 'job') endif - call s:clearState() - if a:status > 0 - call go#util#EchoError(s:state['message']) + + if has_key(s:state, 'ready') + call remove(s:state, 'ready') endif + + if has_key(s:state, 'ch') + call remove(s:state, 'ch') + endif + + call s:clearState() endfunction function! s:logger(prefix, ch, msg) abort @@ -71,57 +83,46 @@ endfunction function! s:call_jsonrpc(method, ...) abort if go#util#HasDebug('debugger-commands') - if !exists('g:go_debug_commands') - let g:go_debug_commands = [] - endif echom 'sending to dlv ' . a:method endif - if len(a:000) > 0 && type(a:000[0]) == v:t_func - let Cb = a:000[0] - let args = a:000[1:] - else - let Cb = v:none - let args = a:000 - endif + let l:args = a:000 let s:state['rpcid'] += 1 - let req_json = json_encode({ + let l:req_json = json_encode({ \ 'id': s:state['rpcid'], \ 'method': a:method, - \ 'params': args, + \ 'params': l:args, \}) try - " Use callback - if type(Cb) == v:t_func - let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb}) - call ch_sendraw(s:ch, req_json) - - if go#util#HasDebug('debugger-commands') - let g:go_debug_commands = add(g:go_debug_commands, { - \ 'request': req_json, - \ 'response': Cb, - \ }) - endif - return + let l:ch = s:state['ch'] + if has('nvim') + call chansend(l:ch, l:req_json) + while len(s:state.data) == 0 + sleep 50m + if get(s:state, 'ready', 0) == 0 + return + endif + endwhile + let resp_json = s:state.data[0] + let s:state.data = s:state.data[1:] + else + call ch_sendraw(l:ch, req_json) + let l:resp_raw = ch_readraw(l:ch) + let resp_json = json_decode(l:resp_raw) endif - let ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'timeout': 20000}) - call ch_sendraw(ch, req_json) - let resp_json = ch_readraw(ch) - if go#util#HasDebug('debugger-commands') - let g:go_debug_commands = add(g:go_debug_commands, { - \ 'request': req_json, - \ 'response': resp_json, + let g:go_debug_commands = add(go#config#DebugCommands(), { + \ 'request': l:req_json, + \ 'response': l:resp_json, \ }) endif - let obj = json_decode(resp_json) - if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error) - throw obj.error + if type(l:resp_json) == v:t_dict && has_key(l:resp_json, 'error') && !empty(l:resp_json.error) + throw l:resp_json.error endif - return obj + return l:resp_json catch throw substitute(v:exception, '^Vim', '', '') endtry @@ -130,7 +131,7 @@ endfunction " Update the location of the current breakpoint or line we're halted on based on " response from dlv. function! s:update_breakpoint(res) abort - if type(a:res) ==# v:t_none + if type(a:res) ==# type(v:null) return endif @@ -227,26 +228,34 @@ function! s:clearState() abort let s:state['localVars'] = {} let s:state['functionArgs'] = {} let s:state['message'] = [] + silent! sign unplace 9999 endfunction function! s:stop() abort - call s:clearState() + let l:res = s:call_jsonrpc('RPCServer.Detach', {'kill': v:true}) + if has_key(s:state, 'job') - call job_stop(s:state['job']) - call remove(s:state, 'job') + call go#job#Wait(s:state['job']) + + " while waiting, the s:complete may have already removed job from s:state. + if has_key(s:state, 'job') + call remove(s:state, 'job') + endif endif + + if has_key(s:state, 'ready') + call remove(s:state, 'ready') + endif + + if has_key(s:state, 'ch') + call remove(s:state, 'ch') + endif + + call s:clearState() endfunction function! go#debug#Stop() abort - " Remove signs. - for k in keys(s:state['breakpoint']) - let bt = s:state['breakpoint'][k] - if bt.id >= 0 - silent exe 'sign unplace ' . bt.id - endif - endfor - " Remove all commands and add back the default commands. for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")') exe 'delcommand' k @@ -272,8 +281,15 @@ function! go#debug#Stop() abort silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c' silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c' - set noballooneval - set balloonexpr= + if has('balloon_eval') + let &ballooneval=s:ballooneval + let &balloonexpr=s:balloonexpr + endif + + augroup vim-go-debug + autocmd! + augroup END + augroup! vim-go-debug endfunction function! s:goto_file() abort @@ -393,22 +409,8 @@ function! s:expand_var() abort endif endfunction -function! s:start_cb(ch, json) abort - let res = json_decode(a:json) - if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error) - throw res.error - endif - if empty(res) || !has_key(res, 'result') - return - endif - for bt in res.result.Breakpoints - if bt.id >= 0 - let s:state['breakpoint'][bt.id] = bt - exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file - endif - endfor - - let oldbuf = bufnr('%') +function! s:start_cb() abort + let l:winid = win_getid() silent! only! let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) @@ -416,8 +418,9 @@ function! s:start_cb(ch, json) abort return endif - if exists('g:go_debug_windows["stack"]') && g:go_debug_windows['stack'] != '' - exe 'silent ' . g:go_debug_windows['stack'] + let debugwindows = go#config#DebugWindows() + if has_key(debugwindows, "stack") && debugwindows['stack'] != '' + exe 'silent ' . debugwindows['stack'] silent file `='__GODEBUG_STACKTRACE__'` setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline setlocal filetype=godebugstacktrace @@ -425,16 +428,16 @@ function! s:start_cb(ch, json) abort nmap q (go-debug-stop) endif - if exists('g:go_debug_windows["out"]') && g:go_debug_windows['out'] != '' - exe 'silent ' . g:go_debug_windows['out'] + if has_key(debugwindows, "out") && debugwindows['out'] != '' + exe 'silent ' . debugwindows['out'] silent file `='__GODEBUG_OUTPUT__'` setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline setlocal filetype=godebugoutput nmap q (go-debug-stop) endif - if exists('g:go_debug_windows["vars"]') && g:go_debug_windows['vars'] != '' - exe 'silent ' . g:go_debug_windows['vars'] + if has_key(debugwindows, "vars") && debugwindows['vars'] != '' + exe 'silent ' . debugwindows['vars'] silent file `='__GODEBUG_VARIABLES__'` setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline setlocal filetype=godebugvariables @@ -462,75 +465,124 @@ function! s:start_cb(ch, json) abort nnoremap (go-debug-stop) :call go#debug#Stop() nnoremap (go-debug-print) :call go#debug#Print(expand('')) - nmap (go-debug-continue) - nmap (go-debug-print) - nmap (go-debug-breakpoint) - nmap (go-debug-next) - nmap (go-debug-step) + if has('balloon_eval') + let s:balloonexpr=&balloonexpr + let s:ballooneval=&ballooneval - set balloonexpr=go#debug#BalloonExpr() - set ballooneval + set balloonexpr=go#debug#BalloonExpr() + set ballooneval + endif - exe bufwinnr(oldbuf) 'wincmd w' + call win_gotoid(l:winid) + + augroup vim-go-debug + autocmd! * + autocmd FileType go nmap (go-debug-continue) + autocmd FileType go nmap (go-debug-print) + autocmd FileType go nmap (go-debug-breakpoint) + autocmd FileType go nmap (go-debug-next) + autocmd FileType go nmap (go-debug-step) + augroup END + doautocmd vim-go-debug FileType go endfunction function! s:err_cb(ch, msg) abort - call go#util#EchoError(a:msg) + if get(s:state, 'ready', 0) != 0 + call call('s:logger', ['ERR: ', a:ch, a:msg]) + return + endif + let s:state['message'] += [a:msg] endfunction function! s:out_cb(ch, msg) abort - call go#util#EchoProgress(a:msg) + if get(s:state, 'ready', 0) != 0 + call call('s:logger', ['OUT: ', a:ch, a:msg]) + return + endif + let s:state['message'] += [a:msg] - " TODO: why do this in this callback? - if stridx(a:msg, g:go_debug_address) != -1 - call ch_setoptions(a:ch, { - \ 'out_cb': function('s:logger', ['OUT: ']), - \ 'err_cb': function('s:logger', ['ERR: ']), - \}) + if stridx(a:msg, go#config#DebugAddress()) != -1 + if has('nvim') + let s:state['data'] = [] + let l:state = {'databuf': ''} + + " explicitly bind callback to state so that within it, self will + " always refer to state. See :help Partial for more information. + let l:state.on_data = function('s:on_data', [], l:state) + let l:ch = sockconnect('tcp', go#config#DebugAddress(), {'on_data': l:state.on_data, 'state': l:state}) + if l:ch == 0 + call go#util#EchoError("could not connect to debugger") + call go#job#Stop(s:state['job']) + return + endif + else + let l:ch = ch_open(go#config#DebugAddress(), {'mode': 'raw', 'timeout': 20000}) + if ch_status(l:ch) !=# 'open' + call go#util#EchoError("could not connect to debugger") + call go#job#Stop(s:state['job']) + return + endif + endif - " Tell dlv about the breakpoints that the user added before delve started. - let l:breaks = copy(s:state.breakpoint) - let s:state['breakpoint'] = {} - for l:bt in values(l:breaks) - call go#debug#Breakpoint(bt.line) + let s:state['ch'] = l:ch + + " After this block executes, Delve will be running with all the + " breakpoints setup, so this callback doesn't have to run again; just log + " future messages. + let s:state['ready'] = 1 + + " replace all the breakpoints set before delve started so that the ids won't overlap. + let l:breakpoints = s:list_breakpoints() + for l:bt in s:list_breakpoints() + exe 'sign unplace '. l:bt.id + call go#debug#Breakpoint(l:bt.line, l:bt.file) endfor - call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb')) + call s:start_cb() endif endfunction +function! s:on_data(ch, data, event) dict abort + let l:data = self.databuf + for l:msg in a:data + let l:data .= l:msg + endfor + + try + let l:res = json_decode(l:data) + let s:state['data'] = add(s:state['data'], l:res) + let self.databuf = '' + catch + " there isn't a complete message in databuf: buffer l:data and try + " again when more data comes in. + let self.databuf = l:data + finally + endtry +endfunction + " Start the debug mode. The first argument is the package name to compile and " debug, anything else will be passed to the running program. function! go#debug#Start(is_test, ...) abort - if has('nvim') - call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(') - return - endif + call go#cmd#autowrite() + if !go#util#has_job() - call go#util#EchoError('This feature requires Vim 8.0.0087 or newer with +job.') + call go#util#EchoError('This feature requires either Vim 8.0.0087 or newer with +job or Neovim.') return endif " It's already running. - if has_key(s:state, 'job') && job_status(s:state['job']) == 'run' - return + if has_key(s:state, 'job') + return s:state['job'] endif let s:start_args = a:000 if go#util#HasDebug('debugger-state') - let g:go_debug_diag = s:state + call go#config#SetDebugDiag(s:state) endif - " cd in to test directory; this is also what running "go test" does. - if a:is_test - lcd %:p:h - endif - - let s:state.is_test = a:is_test - let dlv = go#path#CheckBinPath("dlv") if empty(dlv) return @@ -539,14 +591,27 @@ function! go#debug#Start(is_test, ...) abort try if len(a:000) > 0 let l:pkgname = a:1 - " Expand .; otherwise this won't work from a tmp dir. if l:pkgname[0] == '.' - let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:] + let l:pkgname = go#package#FromPath(l:pkgname) endif else let l:pkgname = go#package#FromPath(getcwd()) endif + if l:pkgname is -1 + call go#util#EchoError('could not determine package name') + return + endif + + " cd in to test directory; this is also what running "go test" does. + if a:is_test + " TODO(bc): Either remove this if it's ok to do so or else record it and + " reset cwd after the job completes. + lcd %:p:h + endif + + let s:state.is_test = a:is_test + let l:args = [] if len(a:000) > 1 let l:args = ['--'] + a:000[1:] @@ -555,29 +620,40 @@ function! go#debug#Start(is_test, ...) abort let l:cmd = [ \ dlv, \ (a:is_test ? 'test' : 'debug'), + \ l:pkgname, \ '--output', tempname(), \ '--headless', \ '--api-version', '2', - \ '--log', - \ '--listen', g:go_debug_address, - \ '--accept-multiclient', + \ '--listen', go#config#DebugAddress(), \] - if get(g:, 'go_build_tags', '') isnot '' - let l:cmd += ['--build-flags', '--tags=' . g:go_build_tags] + let l:debugLogOutput = go#config#DebugLogOutput() + if l:debugLogOutput != '' + let cmd += ['--log', '--log-output', l:debugLogOutput] + endif + + let l:buildtags = go#config#BuildTags() + if buildtags isnot '' + let l:cmd += ['--build-flags', '--tags=' . buildtags] endif let l:cmd += l:args - call go#util#EchoProgress('Starting GoDebug...') let s:state['message'] = [] - let s:state['job'] = job_start(l:cmd, { - \ 'out_cb': function('s:out_cb'), - \ 'err_cb': function('s:err_cb'), - \ 'exit_cb': function('s:exit'), - \ 'stoponexit': 'kill', - \}) + let l:opts = { + \ 'for': 'GoDebug', + \ 'statustype': 'debug', + \ 'complete': function('s:complete'), + \ } + let l:opts = go#job#Options(l:opts) + let l:opts.out_cb = function('s:out_cb') + let l:opts.err_cb = function('s:err_cb') + let l:opts.stoponexit = 'kill' + + let s:state['job'] = go#job#Start(l:cmd, l:opts) catch call go#util#EchoError(v:exception) endtry + + return s:state['job'] endfunction " Translate a reflect kind constant to a human string. @@ -670,13 +746,12 @@ endfunction function! s:eval(arg) abort try - let res = s:call_jsonrpc('RPCServer.State') - let goroutineID = res.result.State.currentThread.goroutineID - let res = s:call_jsonrpc('RPCServer.Eval', { + let l:res = s:call_jsonrpc('RPCServer.State') + let l:res = s:call_jsonrpc('RPCServer.Eval', { \ 'expr': a:arg, - \ 'scope': {'GoroutineID': goroutineID} + \ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID} \ }) - return s:eval_tree(res.result.Variable, 0) + return s:eval_tree(l:res.result.Variable, 0) catch call go#util#EchoError(v:exception) return '' @@ -726,12 +801,11 @@ endfunction function! go#debug#Set(symbol, value) abort try - let res = s:call_jsonrpc('RPCServer.State') - let goroutineID = res.result.State.currentThread.goroutineID + let l:res = s:call_jsonrpc('RPCServer.State') call s:call_jsonrpc('RPCServer.Set', { \ 'symbol': a:symbol, \ 'value': a:value, - \ 'scope': {'GoroutineID': goroutineID} + \ 'scope': {'GoroutineID': l:res.result.State.currentThread.goroutineID} \ }) catch call go#util#EchoError(v:exception) @@ -742,27 +816,20 @@ endfunction function! s:update_stacktrace() abort try - let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5}) - call s:show_stacktrace(res) + let l:res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5}) + call s:show_stacktrace(l:res) catch call go#util#EchoError(v:exception) endtry endfunction -function! s:stack_cb(ch, json) abort +function! s:stack_cb(res) abort let s:stack_name = '' - let res = json_decode(a:json) - if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error) - call go#util#EchoError(res.error) - call s:clearState() - call go#debug#Restart() - return - endif - if empty(res) || !has_key(res, 'result') + if empty(a:res) || !has_key(a:res, 'result') return endif - call s:update_breakpoint(res) + call s:update_breakpoint(a:res) call s:update_stacktrace() call s:update_variables() endfunction @@ -780,7 +847,7 @@ function! go#debug#Stack(name) abort endif " Add a breakpoint to the main.Main if the user didn't define any. - if len(s:state['breakpoint']) is 0 + if len(s:list_breakpoints()) is 0 if go#debug#Breakpoint() isnot 0 let s:state.running = 0 return @@ -793,36 +860,34 @@ function! go#debug#Stack(name) abort call s:call_jsonrpc('RPCServer.CancelNext') endif let s:stack_name = l:name - call s:call_jsonrpc('RPCServer.Command', function('s:stack_cb'), {'name': l:name}) + try + let res = s:call_jsonrpc('RPCServer.Command', {'name': l:name}) + call s:stack_cb(res) + catch + call go#util#EchoError(v:exception) + call s:clearState() + call go#debug#Restart() + endtry catch call go#util#EchoError(v:exception) endtry endfunction function! go#debug#Restart() abort - try - call job_stop(s:state['job']) - while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run' - sleep 50m - endwhile + call go#cmd#autowrite() + + try + call s:stop() - let l:breaks = s:state['breakpoint'] let s:state = { \ 'rpcid': 1, \ 'running': 0, - \ 'breakpoint': {}, \ 'currentThread': {}, \ 'localVars': {}, \ 'functionArgs': {}, \ 'message': [], \} - " Preserve breakpoints. - for bt in values(l:breaks) - " TODO: should use correct filename - exe 'sign unplace '. bt.id .' file=' . bt.file - call go#debug#Breakpoint(bt.line) - endfor call call('go#debug#Start', s:start_args) catch call go#util#EchoError(v:exception) @@ -837,47 +902,45 @@ endfunction " Toggle breakpoint. Returns 0 on success and 1 on failure. function! go#debug#Breakpoint(...) abort let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!') + let l:linenr = line('.') " Get line number from argument. if len(a:000) > 0 - let linenr = str2nr(a:1) - if linenr is 0 + let l:linenr = str2nr(a:1) + if l:linenr is 0 call go#util#EchoError('not a number: ' . a:1) return 0 endif - else - let linenr = line('.') + if len(a:000) > 1 + let l:filename = a:2 + endif endif try " Check if we already have a breakpoint for this line. - let found = v:none - for k in keys(s:state.breakpoint) - let bt = s:state.breakpoint[k] - if bt.file == l:filename && bt.line == linenr - let found = bt + let l:found = {} + for l:bt in s:list_breakpoints() + if l:bt.file is# l:filename && l:bt.line is# l:linenr + let l:found = l:bt break endif endfor " Remove breakpoint. - if type(found) == v:t_dict - call remove(s:state['breakpoint'], bt.id) - exe 'sign unplace '. found.id .' file=' . found.file + if type(l:found) == v:t_dict && !empty(l:found) + exe 'sign unplace '. l:found.id .' file=' . l:found.file if s:isActive() - let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': found.id}) + let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': l:found.id}) endif " Add breakpoint. else if s:isActive() - let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}}) - let bt = res.result.Breakpoint - exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file - let s:state['breakpoint'][bt.id] = bt + let l:res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': l:linenr}}) + let l:bt = res.result.Breakpoint + exe 'sign place '. l:bt.id .' line=' . l:bt.line . ' name=godebugbreakpoint file=' . l:bt.file else - let id = len(s:state['breakpoint']) + 1 - let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr} - exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename + let l:id = len(s:list_breakpoints()) + 1 + exe 'sign place ' . l:id . ' line=' . l:linenr . ' name=godebugbreakpoint file=' . l:filename endif endif catch @@ -888,17 +951,43 @@ function! go#debug#Breakpoint(...) abort return 0 endfunction -sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint -sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent +function! s:list_breakpoints() + " :sign place + " --- Signs --- + " Signs for a.go: + " line=15 id=2 name=godebugbreakpoint + " line=16 id=1 name=godebugbreakpoint + " Signs for a_test.go: + " line=6 id=3 name=godebugbreakpoint -fun! s:hi() - hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black - hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White -endfun -augroup vim-go-breakpoint - autocmd! - autocmd ColorScheme * call s:hi() -augroup end -call s:hi() + let l:signs = [] + let l:file = '' + for l:line in split(execute('sign place'), '\n')[1:] + if l:line =~# '^Signs for ' + let l:file = l:line[10:-2] + continue + endif + + if l:line !~# 'name=godebugbreakpoint' + continue + endif + + let l:sign = matchlist(l:line, '\vline\=(\d+) +id\=(\d+)') + call add(l:signs, { + \ 'id': l:sign[2], + \ 'file': fnamemodify(l:file, ':p'), + \ 'line': str2nr(l:sign[1]), + \ }) + endfor + + return l:signs +endfunction + +sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint +sign define godebugcurline text== texthl=GoDebugCurrent linehl=GoDebugCurrent + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/debug_test.vim b/pack/acp/start/vim-go/autoload/go/debug_test.vim new file mode 100644 index 0000000..f5572e5 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/debug_test.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/decls.vim b/pack/acp/start/vim-go/autoload/go/decls.vim index 493bbd3..1574318 100644 --- a/pack/acp/start/vim-go/autoload/go/decls.vim +++ b/pack/acp/start/vim-go/autoload/go/decls.vim @@ -1,11 +1,12 @@ -if !exists('g:go_decls_mode') - let g:go_decls_mode = '' -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim function! go#decls#Decls(mode, ...) abort - if g:go_decls_mode == 'ctrlp' + let decls_mode = go#config#DeclsMode() + if decls_mode == 'ctrlp' call ctrlp#init(call("ctrlp#decls#cmd", [a:mode] + a:000)) - elseif g:go_decls_mode == 'fzf' + elseif decls_mode == 'fzf' call call("fzf#decls#cmd", [a:mode] + a:000) else if globpath(&rtp, 'plugin/ctrlp.vim') != "" @@ -18,4 +19,8 @@ function! go#decls#Decls(mode, ...) abort end endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/def.vim b/pack/acp/start/vim-go/autoload/go/def.vim index 7b5c86d..5fa1141 100644 --- a/pack/acp/start/vim-go/autoload/go/def.vim +++ b/pack/acp/start/vim-go/autoload/go/def.vim @@ -1,83 +1,86 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + let s:go_stack = [] let s:go_stack_level = 0 -function! go#def#Jump(mode) abort +function! go#def#Jump(mode, type) abort let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') " so guru right now is slow for some people. previously we were using " godef which also has it's own quirks. But this issue come up so many " times I've decided to support both. By default we still use guru as it " covers all edge cases, but now anyone can switch to godef if they wish - let bin_name = get(g:, 'go_def_mode', 'guru') + let bin_name = go#config#DefMode() if bin_name == 'godef' - if &modified - " Write current unsaved buffer to a temp file and use the modified content - let l:tmpname = tempname() - call writefile(go#util#GetLines(), l:tmpname) - let fname = l:tmpname - endif + let l:cmd = ['godef', + \ '-f=' . l:fname, + \ '-o=' . go#util#OffsetCursor(), + \ '-t'] - let bin_path = go#path#CheckBinPath("godef") - if empty(bin_path) - return - endif - let command = printf("%s -f=%s -o=%s -t", go#util#Shellescape(bin_path), - \ go#util#Shellescape(fname), go#util#OffsetCursor()) - let out = go#util#System(command) - if exists("l:tmpname") - call delete(l:tmpname) + if &modified + let l:stdin_content = join(go#util#GetLines(), "\n") + call add(l:cmd, "-i") + let [l:out, l:err] = go#util#ExecInDir(l:cmd, l:stdin_content) + else + let [l:out, l:err] = go#util#ExecInDir(l:cmd) endif elseif bin_name == 'guru' - let bin_path = go#path#CheckBinPath("guru") - if empty(bin_path) - return + let cmd = [go#path#CheckBinPath(bin_name)] + let buildtags = go#config#BuildTags() + if buildtags isnot '' + let cmd += ['-tags', buildtags] endif - let cmd = [bin_path] let stdin_content = "" if &modified - let content = join(go#util#GetLines(), "\n") + let content = join(go#util#GetLines(), "\n") let stdin_content = fname . "\n" . strlen(content) . "\n" . content call add(cmd, "-modified") endif - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(cmd, ["-tags", tags]) - endif - - let fname = fname.':#'.go#util#OffsetCursor() - call extend(cmd, ["definition", fname]) + call extend(cmd, ["definition", fname . ':#' . go#util#OffsetCursor()]) if go#util#has_job() + let l:state = {} let l:spawn_args = { \ 'cmd': cmd, - \ 'complete': function('s:jump_to_declaration_cb', [a:mode, bin_name]), + \ 'complete': function('s:jump_to_declaration_cb', [a:mode, bin_name], l:state), + \ 'for': '_', + \ 'statustype': 'searching declaration', \ } if &modified let l:spawn_args.input = stdin_content endif - call go#util#EchoProgress("searching declaration ...") - - call s:def_job(spawn_args) + call s:def_job(spawn_args, l:state) return endif - let command = join(cmd, " ") if &modified - let out = go#util#System(command, stdin_content) + let [l:out, l:err] = go#util#ExecInDir(l:cmd, l:stdin_content) else - let out = go#util#System(command) + let [l:out, l:err] = go#util#ExecInDir(l:cmd) endif + elseif bin_name == 'gopls' + let [l:line, l:col] = getpos('.')[1:2] + " delegate to gopls, with an empty job object and an exit status of 0 + " (they're irrelevant for gopls). + if a:type + call go#lsp#TypeDef(l:fname, l:line, l:col, function('s:jump_to_declaration_cb', [a:mode, 'gopls', {}, 0])) + else + call go#lsp#Definition(l:fname, l:line, l:col, function('s:jump_to_declaration_cb', [a:mode, 'gopls', {}, 0])) + endif + return else - call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru]') + call go#util#EchoError('go_def_mode value: '. bin_name .' is not valid. Valid values are: [godef, guru, gopls]') return endif - if go#util#ShellError() != 0 + if l:err call go#util#EchoError(out) return endif @@ -85,19 +88,26 @@ function! go#def#Jump(mode) abort call go#def#jump_to_declaration(out, a:mode, bin_name) endfunction -function! s:jump_to_declaration_cb(mode, bin_name, job, exit_status, data) abort +function! s:jump_to_declaration_cb(mode, bin_name, job, exit_status, data) abort dict if a:exit_status != 0 return endif call go#def#jump_to_declaration(a:data[0], a:mode, a:bin_name) - call go#util#EchoSuccess(fnamemodify(a:data[0], ":t")) + + " capture the active window so that callbacks for jobs, exit_cb and + " close_cb, and callbacks for gopls can return to it when a:mode caused a + " split. + let self.winid = win_getid(winnr()) endfunction +" go#def#jump_to_declaration parses out (expected to be +" 'filename:line:col: message'). function! go#def#jump_to_declaration(out, mode, bin_name) abort let final_out = a:out if a:bin_name == "godef" - " append the type information to the same line so our we can parse it. + " append the type information to the same line so it will be parsed + " correctly using guru's output format. " This makes it compatible with guru output. let final_out = join(split(a:out, '\n'), ':') endif @@ -110,10 +120,24 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort let parts = split(out, ':') endif + if len(parts) == 0 + call go#util#EchoError('go jump_to_declaration '. a:bin_name .' output is not valid.') + return + endif + + let line = 1 + let col = 1 + let ident = 0 let filename = parts[0] - let line = parts[1] - let col = parts[2] - let ident = parts[3] + if len(parts) > 1 + let line = parts[1] + endif + if len(parts) > 2 + let col = parts[2] + endif + if len(parts) > 3 + let ident = parts[3] + endif " Remove anything newer than the current position, just like basic " vim tag support @@ -138,7 +162,7 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort if filename != fnamemodify(expand("%"), ':p:gs?\\?/?') " jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded " and 3. there is buffer window number we switch to - if get(g:, 'go_def_reuse_buffer', 0) && bufloaded(filename) != 0 && bufwinnr(filename) != -1 + if go#config#DefReuseBuffer() && bufloaded(filename) != 0 && bufwinnr(filename) != -1 " jumpt to existing buffer if it exists execute bufwinnr(filename) . 'wincmd w' else @@ -291,14 +315,25 @@ function! go#def#Stack(...) abort endif endfunction -function s:def_job(args) abort - let callbacks = go#job#Spawn(a:args) +function s:def_job(args, state) abort + let l:start_options = go#job#Options(a:args) - let start_options = { - \ 'callback': callbacks.callback, - \ 'exit_cb': callbacks.exit_cb, - \ 'close_cb': callbacks.close_cb, - \ } + let l:state = a:state + function! s:exit_cb(next, job, exitval) dict + call call(a:next, [a:job, a:exitval]) + if has_key(self, 'winid') + call win_gotoid(self.winid) + endif + endfunction + let l:start_options.exit_cb = funcref('s:exit_cb', [l:start_options.exit_cb], l:state) + + function! s:close_cb(next, ch) dict + call call(a:next, [a:ch]) + if has_key(self, 'winid') + call win_gotoid(self.winid) + endif + endfunction + let l:start_options.close_cb = funcref('s:close_cb', [l:start_options.close_cb], l:state) if &modified let l:tmpname = tempname() @@ -307,7 +342,11 @@ function s:def_job(args) abort let l:start_options.in_name = l:tmpname endif - call job_start(a:args.cmd, start_options) + call go#job#Start(a:args.cmd, l:start_options) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/def_test.vim b/pack/acp/start/vim-go/autoload/go/def_test.vim index 7e69b12..a054471 100644 --- a/pack/acp/start/vim-go/autoload/go/def_test.vim +++ b/pack/acp/start/vim-go/autoload/go/def_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_jump_to_declaration_guru() abort try let l:filename = 'def/jump.go' @@ -34,4 +38,37 @@ func! Test_jump_to_declaration_godef() abort endtry endfunc +func! Test_Jump_leaves_lists() abort + try + let filename = 'def/jump.go' + let l:tmp = gotest#load_fixture(l:filename) + + let expected = [{'lnum': 10, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'quux'}] + + call setloclist(winnr(), copy(expected), 'r' ) + call setqflist(copy(expected), 'r' ) + + let l:bufnr = bufnr('%') + call cursor(6, 7) + call go#def#Jump('', 0) + + let start = reltime() + while bufnr('%') == l:bufnr && reltimefloat(reltime(start)) < 10 + sleep 100m + endwhile + + let actual = getloclist(winnr()) + call gotest#assert_quickfix(actual, expected) + + let actual = getqflist() + call gotest#assert_quickfix(actual, expected) + finally + call delete(l:tmp, 'rf') + endtry +endfunc + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/doc.vim b/pack/acp/start/vim-go/autoload/go/doc.vim index e06a790..5449bf7 100644 --- a/pack/acp/start/vim-go/autoload/go/doc.vim +++ b/pack/acp/start/vim-go/autoload/go/doc.vim @@ -2,11 +2,11 @@ " Use of this source code is governed by a BSD-style " license that can be found in the LICENSE file. -let s:buf_nr = -1 +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim -if !exists("g:go_doc_command") - let g:go_doc_command = ["godoc"] -endif +let s:buf_nr = -1 function! go#doc#OpenBrowser(...) abort " check if we have gogetdoc as it gives us more and accurate information. @@ -14,8 +14,8 @@ function! go#doc#OpenBrowser(...) abort " non-json output of gogetdoc let bin_path = go#path#CheckBinPath('gogetdoc') if !empty(bin_path) && exists('*json_decode') - let json_out = s:gogetdoc(1) - if go#util#ShellError() != 0 + let [l:json_out, l:err] = s:gogetdoc(1) + if l:err call go#util#EchoError(json_out) return endif @@ -29,15 +29,13 @@ function! go#doc#OpenBrowser(...) abort let name = out["name"] let decl = out["decl"] - let godoc_url = s:custom_godoc_url() + let godoc_url = go#config#DocUrl() let godoc_url .= "/" . import if decl !~ "^package" let godoc_url .= "#" . name endif - echo godoc_url - - call go#tool#OpenBrowser(godoc_url) + call go#util#OpenBrowser(godoc_url) return endif @@ -50,28 +48,22 @@ function! go#doc#OpenBrowser(...) abort let exported_name = pkgs[1] " example url: https://godoc.org/github.com/fatih/set#Set - let godoc_url = s:custom_godoc_url() . "/" . pkg . "#" . exported_name - call go#tool#OpenBrowser(godoc_url) + let godoc_url = go#config#DocUrl() . "/" . pkg . "#" . exported_name + call go#util#OpenBrowser(godoc_url) endfunction function! go#doc#Open(newmode, mode, ...) abort " With argument: run "godoc [arg]". if len(a:000) - if empty(go#path#CheckBinPath(g:go_doc_command[0])) - return - endif - - let command = printf("%s %s", go#util#Shelljoin(g:go_doc_command), join(a:000, ' ')) - let out = go#util#System(command) - " Without argument: run gogetdoc on cursor position. - else - let out = s:gogetdoc(0) + let [l:out, l:err] = go#util#Exec(['go', 'doc'] + a:000) + else " Without argument: run gogetdoc on cursor position. + let [l:out, l:err] = s:gogetdoc(0) if out == -1 return endif endif - if go#util#ShellError() != 0 + if l:err call go#util#EchoError(out) return endif @@ -97,7 +89,7 @@ function! s:GodocView(newposition, position, content) abort if !is_visible if a:position == "split" " cap window height to 20, but resize it for smaller contents - let max_height = get(g:, "go_doc_max_height", 20) + let max_height = go#config#DocMaxHeight() let content_height = len(split(a:content, "\n")) if content_height > max_height exe 'resize ' . max_height @@ -129,39 +121,29 @@ function! s:GodocView(newposition, position, content) abort setlocal nomodifiable sil normal! gg - " close easily with or enter + " close easily with enter noremap :close noremap :close + " make sure any key that sends an escape as a prefix (e.g. the arrow keys) + " don't cause the window to close. + nnoremap [ [ endfunction function! s:gogetdoc(json) abort - " check if we have 'gogetdoc' and use it automatically - let bin_path = go#path#CheckBinPath('gogetdoc') - if empty(bin_path) - return -1 - endif - - let cmd = [go#util#Shellescape(bin_path)] - - let offset = go#util#OffsetCursor() - let fname = expand("%:p:gs!\\!/!") - let pos = shellescape(fname.':#'.offset) - - let cmd += ["-pos", pos] + let l:cmd = [ + \ 'gogetdoc', + \ '-tags', go#config#BuildTags(), + \ '-pos', expand("%:p:gs!\\!/!") . ':#' . go#util#OffsetCursor()] if a:json - let cmd += ["-json"] + let l:cmd += ['-json'] endif - let command = join(cmd, " ") - if &modified - let command .= " -modified" - let out = go#util#System(command, go#util#archive()) - else - let out = go#util#System(command) + let l:cmd += ['-modified'] + return go#util#Exec(l:cmd, go#util#archive()) endif - return out + return go#util#Exec(l:cmd) endfunction " returns the package and exported name. exported name might be empty. @@ -206,18 +188,8 @@ function! s:godocWord(args) abort return [pkg, exported_name] endfunction -function! s:custom_godoc_url() abort - let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org') - if godoc_url isnot 'https://godoc.org' - " strip last '/' character if available - let last_char = strlen(godoc_url) - 1 - if godoc_url[last_char] == '/' - let godoc_url = strpart(godoc_url, 0, last_char) - endif - " custom godoc installations expect /pkg before package names - let godoc_url .= "/pkg" - endif - return godoc_url -endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/fillstruct.vim b/pack/acp/start/vim-go/autoload/go/fillstruct.vim index ec88a76..1e406d7 100644 --- a/pack/acp/start/vim-go/autoload/go/fillstruct.vim +++ b/pack/acp/start/vim-go/autoload/go/fillstruct.vim @@ -1,8 +1,14 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + function! go#fillstruct#FillStruct() abort let l:cmd = ['fillstruct', \ '-file', bufname(''), \ '-offset', go#util#OffsetCursor(), \ '-line', line('.')] + " Needs: https://github.com/davidrjenni/reftools/pull/14 + "\ '-tags', go#config#BuildTags()] " Read from stdin if modified. if &modified @@ -59,4 +65,8 @@ function! go#fillstruct#FillStruct() abort endtry endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim b/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim index d677ab7..956cd1e 100644 --- a/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim +++ b/pack/acp/start/vim-go/autoload/go/fillstruct_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_fillstruct() abort try let l:tmp = gotest#write_file('a/a.go', [ @@ -87,4 +91,8 @@ func! Test_fillstruct_two_cursor() abort endtry endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/fmt.vim b/pack/acp/start/vim-go/autoload/go/fmt.vim index 08fc3b3..908c0f5 100644 --- a/pack/acp/start/vim-go/autoload/go/fmt.vim +++ b/pack/acp/start/vim-go/autoload/go/fmt.vim @@ -5,21 +5,9 @@ " fmt.vim: Vim command to format Go files with gofmt (and gofmt compatible " toorls, such as goimports). -if !exists("g:go_fmt_command") - let g:go_fmt_command = "gofmt" -endif - -if !exists('g:go_fmt_options') - let g:go_fmt_options = '' -endif - -if !exists('g:go_fmt_fail_silently') - let g:go_fmt_fail_silently = 0 -endif - -if !exists("g:go_fmt_experimental") - let g:go_fmt_experimental = 0 -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim " we have those problems : " http://stackoverflow.com/questions/12741977/prevent-vim-from-updating-its-undo-tree @@ -30,7 +18,7 @@ endif " this and have VimL experience, please look at the function for " improvements, patches are welcome :) function! go#fmt#Format(withGoimport) abort - if g:go_fmt_experimental == 1 + if go#config#FmtExperimental() " Using winsaveview to save/restore cursor state has the problem of " closing folds on save: " https://github.com/fatih/vim-go/issues/502 @@ -64,18 +52,18 @@ function! go#fmt#Format(withGoimport) abort let l:tmpname = tr(l:tmpname, '\', '/') endif - let bin_name = g:go_fmt_command + let bin_name = go#config#FmtCommand() if a:withGoimport == 1 let bin_name = "goimports" endif let current_col = col('.') - let out = go#fmt#run(bin_name, l:tmpname, expand('%')) + let [l:out, l:err] = go#fmt#run(bin_name, l:tmpname, expand('%')) let diff_offset = len(readfile(l:tmpname)) - line('$') - if go#util#ShellError() == 0 + if l:err == 0 call go#fmt#update_file(l:tmpname, expand('%')) - elseif g:go_fmt_fail_silently == 0 + elseif !go#config#FmtFailSilently() let errors = s:parse_errors(expand('%'), out) call s:show_errors(errors) endif @@ -83,7 +71,7 @@ function! go#fmt#Format(withGoimport) abort " We didn't use the temp file, so clean up call delete(l:tmpname) - if g:go_fmt_experimental == 1 + if go#config#FmtExperimental() " restore our undo history silent! exe 'rundo ' . tmpundofile call delete(tmpundofile) @@ -154,64 +142,27 @@ endfunction " run runs the gofmt/goimport command for the given source file and returns " the output of the executed command. Target is the real file to be formatted. function! go#fmt#run(bin_name, source, target) - let cmd = s:fmt_cmd(a:bin_name, a:source, a:target) - if empty(cmd) + let l:cmd = s:fmt_cmd(a:bin_name, a:source, a:target) + if empty(l:cmd) return endif - - let command = join(cmd, " ") - - " execute our command... - let out = go#util#System(command) - - return out + return go#util#Exec(l:cmd) endfunction -" fmt_cmd returns a dict that contains the command to execute gofmt (or -" goimports). args is dict with +" fmt_cmd returns the command to run as a list. function! s:fmt_cmd(bin_name, source, target) - " check if the user has installed command binary. - " For example if it's goimports, let us check if it's installed, - " if not the user get's a warning via go#path#CheckBinPath() - let bin_path = go#path#CheckBinPath(a:bin_name) - if empty(bin_path) - return [] - endif - - " start constructing the command - let bin_path = go#util#Shellescape(bin_path) - let cmd = [bin_path] - call add(cmd, "-w") + let l:cmd = [a:bin_name, '-w'] " add the options for binary (if any). go_fmt_options was by default of type " string, however to allow customization it's now a dictionary of binary " name mapping to options. - let opts = g:go_fmt_options - if type(g:go_fmt_options) == type({}) - let opts = has_key(g:go_fmt_options, a:bin_name) ? g:go_fmt_options[a:bin_name] : "" + let opts = go#config#FmtOptions() + if type(opts) == type({}) + let opts = has_key(opts, a:bin_name) ? opts[a:bin_name] : "" endif call extend(cmd, split(opts, " ")) - - if a:bin_name == "goimports" - " lazy check if goimports support `-srcdir`. We should eventually remove - " this in the future - if !exists('b:goimports_vendor_compatible') - let out = go#util#System(bin_path . " --help") - if out !~ "-srcdir" - call go#util#EchoWarning(printf("vim-go: goimports (%s) does not support srcdir. Update with: :GoUpdateBinaries", bin_path)) - else - let b:goimports_vendor_compatible = 1 - endif - endif - - if exists('b:goimports_vendor_compatible') && b:goimports_vendor_compatible - let ssl_save = &shellslash - set noshellslash - " use the filename without the fully qualified name if the tree is - " symlinked into the GOPATH, goimports won't work properly. - call extend(cmd, ["-srcdir", shellescape(a:target)]) - let &shellslash = ssl_save - endif + if a:bin_name is# 'goimports' + call extend(cmd, ["-srcdir", a:target]) endif call add(cmd, a:source) @@ -254,14 +205,18 @@ function! s:show_errors(errors) abort endfunction function! go#fmt#ToggleFmtAutoSave() abort - if get(g:, "go_fmt_autosave", 1) - let g:go_fmt_autosave = 0 + if go#config#FmtAutosave() + call go#config#SetFmtAutosave(0) call go#util#EchoProgress("auto fmt disabled") return end - let g:go_fmt_autosave = 1 + call go#config#SetFmtAutosave(1) call go#util#EchoProgress("auto fmt enabled") endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/fmt_test.vim b/pack/acp/start/vim-go/autoload/go/fmt_test.vim index 2adbfba..b1b740a 100644 --- a/pack/acp/start/vim-go/autoload/go/fmt_test.vim +++ b/pack/acp/start/vim-go/autoload/go/fmt_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_run_fmt() abort let actual_file = tempname() call writefile(readfile("test-fixtures/fmt/hello.go"), actual_file) @@ -46,4 +50,8 @@ func! Test_goimports() abort call assert_equal(expected, actual) endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/guru.vim b/pack/acp/start/vim-go/autoload/go/guru.vim index 3e735ec..8ff3b3f 100644 --- a/pack/acp/start/vim-go/autoload/go/guru.vim +++ b/pack/acp/start/vim-go/autoload/go/guru.vim @@ -1,5 +1,9 @@ " guru.vim -- Vim integration for the Go guru. +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " guru_cmd returns a dict that contains the command to execute guru. args " is dict with following options: " mode : guru mode, such as 'implements' @@ -14,14 +18,9 @@ function! s:guru_cmd(args) range abort let format = a:args.format let needs_scope = a:args.needs_scope let selected = a:args.selected + let postype = get(a:args, 'postype', 'cursor') let result = {} - let pkg = go#package#ImportPath() - - " this is important, check it! - if pkg == -1 && needs_scope - return {'err': "current directory is not inside of a valid GOPATH"} - endif "return with a warning if the binary doesn't exist let bin_path = go#path#CheckBinPath("guru") @@ -30,9 +29,8 @@ function! s:guru_cmd(args) range abort endif " start constructing the command - let cmd = [bin_path] + let cmd = [bin_path, '-tags', go#config#BuildTags()] - let filename = fnamemodify(expand("%"), ':p:gs?\\?/?') if &modified let result.stdin_content = go#util#archive() call add(cmd, "-modified") @@ -43,59 +41,41 @@ function! s:guru_cmd(args) range abort call add(cmd, "-json") endif - " check for any tags - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(cmd, ["-tags", tags]) - let result.tags = tags - endif - - " some modes require scope to be defined (such as callers). For these we - " choose a sensible setting, which is using the current file's package - let scopes = [] - if needs_scope - let scopes = [pkg] - endif - - " check for any user defined scope setting. users can define the scope, - " in package pattern form. examples: - " golang.org/x/tools/cmd/guru # a single package - " golang.org/x/tools/... # all packages beneath dir - " ... # the entire workspace. - if exists('g:go_guru_scope') - " check that the setting is of type list - if type(get(g:, 'go_guru_scope')) != type([]) - return {'err' : "go_guru_scope should of type list"} + let scopes = go#config#GuruScope() + if empty(scopes) + " some modes require scope to be defined (such as callers). For these we + " choose a sensible setting, which is using the current file's package + if needs_scope + let pkg = go#package#ImportPath() + if pkg == -1 + return {'err': "current directory is not inside of a valid GOPATH"} + endif + let scopes = [pkg] endif - - let scopes = get(g:, 'go_guru_scope') endif - " now add the scope to our command if there is any + " Add the scope. if !empty(scopes) - " strip trailing slashes for each path in scoped. bug: - " https://github.com/golang/go/issues/14584 - let scopes = go#util#StripTrailingSlash(scopes) - - " create shell-safe entries of the list - if !has("nvim") && !go#util#has_job() | let scopes = go#util#Shelllist(scopes) | endif - - " guru expect a comma-separated list of patterns, construct it + " guru expect a comma-separated list of patterns. let l:scope = join(scopes, ",") let result.scope = l:scope call extend(cmd, ["-scope", l:scope]) endif - let pos = printf("#%s", go#util#OffsetCursor()) - if selected != -1 - " means we have a range, get it - let pos1 = go#util#Offset(line("'<"), col("'<")) - let pos2 = go#util#Offset(line("'>"), col("'>")) - let pos = printf("#%s,#%s", pos1, pos2) + if postype == 'balloon' + let pos = printf("#%s", go#util#Offset(v:beval_lnum, v:beval_col)) + else + let pos = printf("#%s", go#util#OffsetCursor()) + if selected != -1 + " means we have a range, get it + let pos1 = go#util#Offset(line("'<"), col("'<")) + let pos2 = go#util#Offset(line("'>"), col("'>")) + let pos = printf("#%s,#%s", pos1, pos2) + endif endif - let filename .= ':'.pos - call extend(cmd, [mode, filename]) + let l:filename = fnamemodify(expand("%"), ':p:gs?\\?/?') . ':' . pos + call extend(cmd, [mode, l:filename]) let result.cmd = cmd return result @@ -119,51 +99,22 @@ function! s:sync_guru(args) abort endif endif - " run, forrest run!!! - let command = join(result.cmd, " ") - if has_key(result, 'stdin_content') - let out = go#util#System(command, result.stdin_content) + if has_key(l:result, 'stdin_content') + let [l:out, l:err] = go#util#Exec(l:result.cmd, l:result.stdin_content) else - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(l:result.cmd) endif if has_key(a:args, 'custom_parse') - call a:args.custom_parse(go#util#ShellError(), out, a:args.mode) + call a:args.custom_parse(l:err, l:out, a:args.mode) else - call s:parse_guru_output(go#util#ShellError(), out, a:args.mode) + call s:parse_guru_output(l:err, l:out, a:args.mode) endif - return out + return l:out endfunc -" use vim or neovim job api as appropriate -function! s:job_start(cmd, start_options) abort - if go#util#has_job() - return job_start(a:cmd, a:start_options) - endif - - let opts = {'stdout_buffered': v:true, 'stderr_buffered': v:true} - function opts.on_stdout(job_id, data, event) closure - call a:start_options.callback(a:job_id, join(a:data, "\n")) - endfunction - function opts.on_stderr(job_id, data, event) closure - call a:start_options.callback(a:job_id, join(a:data, "\n")) - endfunction - function opts.on_exit(job_id, exit_code, event) closure - call a:start_options.exit_cb(a:job_id, a:exit_code) - call a:start_options.close_cb(a:job_id) - endfunction - - " use a shell for input redirection if needed - let cmd = a:cmd - if has_key(a:start_options, 'in_io') && a:start_options.in_io ==# 'file' && !empty(a:start_options.in_name) - let cmd = ['/bin/sh', '-c', join(a:cmd, ' ') . ' <' . a:start_options.in_name] - endif - - return jobstart(cmd, opts) -endfunction - " async_guru runs guru in async mode with the given arguments function! s:async_guru(args) abort let result = s:guru_cmd(a:args) @@ -172,92 +123,51 @@ function! s:async_guru(args) abort return endif - - if !has_key(a:args, 'disable_progress') - if a:args.needs_scope - call go#util#EchoProgress("analysing with scope " . result.scope . - \ " (see ':help go-guru-scope' if this doesn't work)...") - endif - endif - let state = { - \ 'status_dir': expand('%:p:h'), - \ 'statusline_type': printf("%s", a:args.mode), \ 'mode': a:args.mode, - \ 'status': {}, - \ 'exitval': 0, - \ 'closed': 0, - \ 'exited': 0, - \ 'messages': [], \ 'parse' : get(a:args, 'custom_parse', funcref("s:parse_guru_output")) \ } - function! s:callback(chan, msg) dict - call add(self.messages, a:msg) - endfunction + " explicitly bind complete to state so that within it, self will + " always refer to state. See :help Partial for more information. + let state.complete = function('s:complete', [], state) - function! s:exit_cb(job, exitval) dict - let self.exited = 1 + let opts = { + \ 'statustype': get(a:args, 'statustype', a:args.mode), + \ 'for': '_', + \ 'errorformat': "%f:%l.%c-%[%^:]%#:\ %m,%f:%l:%c:\ %m", + \ 'complete': state.complete, + \ } - let status = { - \ 'desc': 'last status', - \ 'type': self.statusline_type, - \ 'state': "finished", - \ } + if has_key(a:args, 'disable_progress') + let opts.statustype = '' + endif - if a:exitval - let self.exitval = a:exitval - let status.state = "failed" - endif - - call go#statusline#Update(self.status_dir, status) - - if self.closed - call self.complete() - endif - endfunction - - function! s:close_cb(ch) dict - let self.closed = 1 - - if self.exited - call self.complete() - endif - endfunction - - function state.complete() dict - let out = join(self.messages, "\n") - - call self.parse(self.exitval, out, self.mode) - endfunction - - " explicitly bind the callbacks to state so that self within them always - " refers to state. See :help Partial for more information. - let start_options = { - \ 'callback': function('s:callback', [], state), - \ 'exit_cb': function('s:exit_cb', [], state), - \ 'close_cb': function('s:close_cb', [], state) - \ } + let opts = go#job#Options(l:opts) if has_key(result, 'stdin_content') let l:tmpname = tempname() call writefile(split(result.stdin_content, "\n"), l:tmpname, "b") - let l:start_options.in_io = "file" - let l:start_options.in_name = l:tmpname + let l:opts.in_io = "file" + let l:opts.in_name = l:tmpname endif - call go#statusline#Update(state.status_dir, { - \ 'desc': "current status", - \ 'type': state.statusline_type, - \ 'state': "analysing", - \}) + call go#job#Start(result.cmd, opts) - return s:job_start(result.cmd, start_options) + if a:args.needs_scope && go#config#EchoCommandInfo() && !has_key(a:args, 'disable_progress') + call go#util#EchoProgress("analysing with scope " . result.scope . + \ " (see ':help go-guru-scope' if this doesn't work)...") + endif endfunc +function! s:complete(job, exit_status, messages) dict abort + let output = join(a:messages, "\n") + call self.parse(a:exit_status, output, self.mode) +endfunction + " run_guru runs the given guru argument function! s:run_guru(args) abort - if has('nvim') || go#util#has_job() + if go#util#has_job() let res = s:async_guru(a:args) else let res = s:sync_guru(a:args) @@ -278,6 +188,18 @@ function! go#guru#Implements(selected) abort call s:run_guru(args) endfunction +" Shows the set of possible objects to which a pointer may point. +function! go#guru#PointsTo(selected) abort + let l:args = { + \ 'mode': 'pointsto', + \ 'format': 'plain', + \ 'selected': a:selected, + \ 'needs_scope': 1, + \ } + + call s:run_guru(l:args) +endfunction + " Report the possible constants, global variables, and concrete types that may " appear in a value of type error function! go#guru#Whicherrs(selected) abort @@ -309,7 +231,7 @@ function! go#guru#Describe(selected) abort call s:run_guru(args) endfunction -function! go#guru#DescribeInfo() abort +function! go#guru#DescribeInfo(showstatus) abort " json_encode() and friends are introduced with this patch (7.4.1304) " vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ " nvim: https://github.com/neovim/neovim/pull/4131 @@ -318,103 +240,103 @@ function! go#guru#DescribeInfo() abort return endif - function! s:info(exit_val, output, mode) - if a:exit_val != 0 - return - endif - - if a:output[0] !=# '{' - return - endif - - if empty(a:output) || type(a:output) != type("") - return - endif - - let result = json_decode(a:output) - if type(result) != type({}) - call go#util#EchoError(printf("malformed output from guru: %s", a:output)) - return - endif - - if !has_key(result, 'detail') - " if there is no detail check if there is a description and print it - if has_key(result, "desc") - call go#util#EchoInfo(result["desc"]) - return - endif - - call go#util#EchoError("detail key is missing. Please open a bug report on vim-go repo.") - return - endif - - let detail = result['detail'] - let info = "" - - " guru gives different information based on the detail mode. Let try to - " extract the most useful information - - if detail == "value" - if !has_key(result, 'value') - call go#util#EchoError("value key is missing. Please open a bug report on vim-go repo.") - return - endif - - let val = result["value"] - if !has_key(val, 'type') - call go#util#EchoError("type key is missing (value.type). Please open a bug report on vim-go repo.") - return - endif - - let info = val["type"] - elseif detail == "type" - if !has_key(result, 'type') - call go#util#EchoError("type key is missing. Please open a bug report on vim-go repo.") - return - endif - - let type = result["type"] - if !has_key(type, 'type') - call go#util#EchoError("type key is missing (type.type). Please open a bug report on vim-go repo.") - return - endif - - let info = type["type"] - elseif detail == "package" - if !has_key(result, 'package') - call go#util#EchoError("package key is missing. Please open a bug report on vim-go repo.") - return - endif - - let package = result["package"] - if !has_key(package, 'path') - call go#util#EchoError("path key is missing (package.path). Please open a bug report on vim-go repo.") - return - endif - - let info = printf("package %s", package["path"]) - elseif detail == "unknown" - let info = result["desc"] - else - call go#util#EchoError(printf("unknown detail mode found '%s'. Please open a bug report on vim-go repo", detail)) - return - endif - - call go#util#EchoInfo(info) - endfunction - let args = { \ 'mode': 'describe', \ 'format': 'json', \ 'selected': -1, \ 'needs_scope': 0, \ 'custom_parse': function('s:info'), - \ 'disable_progress': 1, + \ 'disable_progress': a:showstatus == 0, \ } call s:run_guru(args) endfunction +function! s:info(exit_val, output, mode) + if a:exit_val != 0 + return + endif + + if a:output[0] !=# '{' + return + endif + + if empty(a:output) || type(a:output) != type("") + return + endif + + let result = json_decode(a:output) + if type(result) != type({}) + call go#util#EchoError(printf("malformed output from guru: %s", a:output)) + return + endif + + if !has_key(result, 'detail') + " if there is no detail check if there is a description and print it + if has_key(result, "desc") + call go#util#EchoInfo(result["desc"]) + return + endif + + call go#util#EchoError("detail key is missing. Please open a bug report on vim-go repo.") + return + endif + + let detail = result['detail'] + let info = "" + + " guru gives different information based on the detail mode. Let try to + " extract the most useful information + + if detail == "value" + if !has_key(result, 'value') + call go#util#EchoError("value key is missing. Please open a bug report on vim-go repo.") + return + endif + + let val = result["value"] + if !has_key(val, 'type') + call go#util#EchoError("type key is missing (value.type). Please open a bug report on vim-go repo.") + return + endif + + let info = val["type"] + elseif detail == "type" + if !has_key(result, 'type') + call go#util#EchoError("type key is missing. Please open a bug report on vim-go repo.") + return + endif + + let type = result["type"] + if !has_key(type, 'type') + call go#util#EchoError("type key is missing (type.type). Please open a bug report on vim-go repo.") + return + endif + + let info = type["type"] + elseif detail == "package" + if !has_key(result, 'package') + call go#util#EchoError("package key is missing. Please open a bug report on vim-go repo.") + return + endif + + let package = result["package"] + if !has_key(package, 'path') + call go#util#EchoError("path key is missing (package.path). Please open a bug report on vim-go repo.") + return + endif + + let info = printf("package %s", package["path"]) + elseif detail == "unknown" + let info = result["desc"] + else + call go#util#EchoError(printf("unknown detail mode found '%s'. Please open a bug report on vim-go repo", detail)) + return + endif + + call go#util#ShowInfo(info) +endfunction + " Show possible targets of selected function call function! go#guru#Callees(selected) abort let args = { @@ -493,7 +415,7 @@ function! go#guru#Referrers(selected) abort call s:run_guru(args) endfunction -function! go#guru#SameIds() abort +function! go#guru#SameIds(showstatus) abort " we use matchaddpos() which was introduce with 7.4.330, be sure we have " it: http://ftp.vim.org/vim/patches/7.4/7.4.330 if !exists("*matchaddpos") @@ -516,6 +438,9 @@ function! go#guru#SameIds() abort \ 'needs_scope': 0, \ 'custom_parse': function('s:same_ids_highlight'), \ } + if !a:showstatus + let args.disable_progress = 1 + endif call s:run_guru(args) endfunction @@ -524,20 +449,20 @@ function! s:same_ids_highlight(exit_val, output, mode) abort call go#guru#ClearSameIds() " run after calling guru to reduce flicker. if a:output[0] !=# '{' - if !get(g:, 'go_auto_sameids', 0) + if !go#config#AutoSameids() call go#util#EchoError(a:output) endif return endif let result = json_decode(a:output) - if type(result) != type({}) && !get(g:, 'go_auto_sameids', 0) + if type(result) != type({}) && !go#config#AutoSameids() call go#util#EchoError("malformed output from guru") return endif if !has_key(result, 'sameids') - if !get(g:, 'go_auto_sameids', 0) + if !go#config#AutoSameids() call go#util#EchoError("no same_ids founds for the given identifier") endif return @@ -563,12 +488,12 @@ function! s:same_ids_highlight(exit_val, output, mode) abort call matchaddpos('goSameId', [[str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]]) endfor - if get(g:, "go_auto_sameids", 0) + if go#config#AutoSameids() " re-apply SameIds at the current cursor position at the time the buffer " is redisplayed: e.g. :edit, :GoRename, etc. augroup vim-go-sameids - autocmd! - autocmd BufWinEnter nested call go#guru#SameIds() + autocmd! * + autocmd BufWinEnter nested call go#guru#SameIds(0) augroup end endif endfunction @@ -592,7 +517,7 @@ function! go#guru#ClearSameIds() abort " remove the autocmds we defined augroup vim-go-sameids - autocmd! + autocmd! * augroup end return 0 @@ -600,20 +525,20 @@ endfunction function! go#guru#ToggleSameIds() abort if go#guru#ClearSameIds() != 0 - call go#guru#SameIds() + call go#guru#SameIds(1) endif endfunction -function! go#guru#AutoToogleSameIds() abort - if get(g:, "go_auto_sameids", 0) +function! go#guru#AutoToggleSameIds() abort + if go#config#AutoSameids() call go#util#EchoProgress("sameids auto highlighting disabled") call go#guru#ClearSameIds() - let g:go_auto_sameids = 0 + call go#config#SetAutoSameids(0) return endif call go#util#EchoSuccess("sameids auto highlighting enabled") - let g:go_auto_sameids = 1 + call go#config#SetAutoSameids(1) endfunction @@ -648,22 +573,165 @@ endfun function! go#guru#Scope(...) abort if a:0 + let scope = a:000 if a:0 == 1 && a:1 == '""' - unlet g:go_guru_scope + let scope = [] + endif + + call go#config#SetGuruScope(scope) + if empty(scope) call go#util#EchoSuccess("guru scope is cleared") else - let g:go_guru_scope = a:000 call go#util#EchoSuccess("guru scope changed to: ". join(a:000, ",")) endif return endif - if !exists('g:go_guru_scope') + let scope = go#config#GuruScope() + if empty(scope) call go#util#EchoError("guru scope is not set") else - call go#util#EchoSuccess("current guru scope: ". join(g:go_guru_scope, ",")) + call go#util#EchoSuccess("current guru scope: ". join(scope, ",")) endif endfunction +function! go#guru#DescribeBalloon() abort + " don't even try if async isn't available. + if !go#util#has_job() + return + endif + + " json_encode() and friends are introduced with this patch (7.4.1304) + " vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ + " nvim: https://github.com/neovim/neovim/pull/4131 + if !exists("*json_decode") + call go#util#EchoError("requires 'json_decode'. Update your Vim/Neovim version.") + return + endif + + " change the active window to the window where the cursor is. + let l:winid = win_getid(winnr()) + call win_gotoid(v:beval_winid) + + let l:args = { + \ 'mode': 'describe', + \ 'format': 'json', + \ 'selected': -1, + \ 'needs_scope': 0, + \ 'custom_parse': function('s:describe_balloon'), + \ 'disable_progress': 1, + \ 'postype': 'balloon', + \ } + + call s:async_guru(args) + + " make the starting window active again + call win_gotoid(l:winid) + + return '' +endfunction + +function! s:describe_balloon(exit_val, output, mode) + if a:exit_val != 0 + return + endif + + if a:output[0] !=# '{' + return + endif + + if empty(a:output) || type(a:output) != type("") + return + endif + + let l:result = json_decode(a:output) + if type(l:result) != type({}) + call go#util#EchoError(printf('malformed output from guru: %s', a:output)) + return + endif + + let l:info = [] + if has_key(l:result, 'desc') + if l:result['desc'] != 'identifier' + let l:info = add(l:info, l:result['desc']) + endif + endif + + if has_key(l:result, 'detail') + let l:detail = l:result['detail'] + + " guru gives different information based on the detail mode. Let try to + " extract the most useful information + + if l:detail == 'value' + if !has_key(l:result, 'value') + call go#util#EchoError('value key is missing. Please open a bug report on vim-go repo.') + return + endif + + let l:val = l:result['value'] + if !has_key(l:val, 'type') + call go#util#EchoError('type key is missing (value.type). Please open a bug report on vim-go repo.') + return + endif + + let l:info = add(l:info, printf('type: %s', l:val['type'])) + if has_key(l:val, 'value') + let l:info = add(l:info, printf('value: %s', l:val['value'])) + endif + elseif l:detail == 'type' + if !has_key(l:result, 'type') + call go#util#EchoError('type key is missing. Please open a bug report on vim-go repo.') + return + endif + + let l:type = l:result['type'] + if !has_key(l:type, 'type') + call go#util#EchoError('type key is missing (type.type). Please open a bug report on vim-go repo.') + return + endif + + let l:info = add(l:info, printf('type: %s', l:type['type'])) + + if has_key(l:type, 'methods') + let l:info = add(l:info, 'methods:') + for l:m in l:type.methods + let l:info = add(l:info, printf("\t%s", l:m['name'])) + endfor + endif + elseif l:detail == 'package' + if !has_key(l:result, 'package') + call go#util#EchoError('package key is missing. Please open a bug report on vim-go repo.') + return + endif + + let l:package = result['package'] + if !has_key(l:package, 'path') + call go#util#EchoError('path key is missing (package.path). Please open a bug report on vim-go repo.') + return + endif + + let l:info = add(l:info, printf('package: %s', l:package["path"])) + elseif l:detail == 'unknown' + " the description is already included in l:info, and there's no other + " information on unknowns. + else + call go#util#EchoError(printf('unknown detail mode (%s) found. Please open a bug report on vim-go repo', l:detail)) + return + endif + endif + + if has('balloon_eval') + call balloon_show(join(l:info, "\n")) + return + endif + + call balloon_show(l:info) +endfunction + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/guru_test.vim b/pack/acp/start/vim-go/autoload/go/guru_test.vim new file mode 100644 index 0000000..47e6bf6 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/guru_test.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/highlight_test.vim b/pack/acp/start/vim-go/autoload/go/highlight_test.vim new file mode 100644 index 0000000..5693871 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/highlight_test.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/iferr.vim b/pack/acp/start/vim-go/autoload/go/iferr.vim new file mode 100644 index 0000000..cc777b3 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/iferr.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/impl.vim b/pack/acp/start/vim-go/autoload/go/impl.vim index 73707fa..1a04d0c 100644 --- a/pack/acp/start/vim-go/autoload/go/impl.vim +++ b/pack/acp/start/vim-go/autoload/go/impl.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + function! go#impl#Impl(...) abort let recv = "" let iface = "" @@ -101,13 +105,29 @@ function! s:root_dirs() abort return dirs endfunction -function! s:go_packages(dirs) abort +function! s:go_packages(dirs, arglead) abort let pkgs = [] - for d in a:dirs - let pkg_root = expand(d . '/pkg/' . go#util#osarch()) - call extend(pkgs, split(globpath(pkg_root, '**/*.a', 1), "\n")) + for dir in a:dirs + " this may expand to multiple lines + let scr_root = expand(dir . '/src/') + for pkg in split(globpath(scr_root, a:arglead.'*'), "\n") + if isdirectory(pkg) + let pkg .= '/' + elseif pkg !~ '\.a$' + continue + endif + + " without this the result can have duplicates in form of + " 'encoding/json' and '/encoding/json/' + let pkg = go#util#StripPathSep(pkg) + + " remove the scr root and keep the package in tact + let pkg = substitute(pkg, scr_root, "", "") + call add(pkgs, pkg) + endfor endfor - return map(pkgs, "fnamemodify(v:val, ':t:r')") + + return pkgs endfunction function! s:interface_list(pkg) abort @@ -124,17 +144,32 @@ endfunction " Complete package and interface for {interface} function! go#impl#Complete(arglead, cmdline, cursorpos) abort let words = split(a:cmdline, '\s\+', 1) + if words[-1] ==# '' - return s:uniq(sort(s:go_packages(s:root_dirs()))) - elseif words[-1] =~# '^\h\w*$' - return s:uniq(sort(filter(s:go_packages(s:root_dirs()), 'stridx(v:val, words[-1]) == 0'))) - elseif words[-1] =~# '^\h\w*\.\%(\h\w*\)\=$' - let [pkg, interface] = split(words[-1], '\.', 1) - echomsg pkg + " if no words are given, just start completing the first package we found + return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead))) + elseif words[-1] =~# '^\(\h\w.*\.\%(\h\w*\)\=$\)\@!\S*$' + " start matching go packages. It's negate match of the below match + return s:uniq(sort(s:go_packages(s:root_dirs(), a:arglead))) + elseif words[-1] =~# '^\h\w.*\.\%(\h\w*\)\=$' + " match the following, anything that could indicate an interface candidate + " + " io. + " io.Wr + " github.com/fatih/color. + " github.com/fatih/color.U + " github.com/fatih/color.Un + let splitted = split(words[-1], '\.', 1) + let pkg = join(splitted[:-2], '.') + let interface = splitted[-1] return s:uniq(sort(filter(s:interface_list(pkg), 'v:val =~? words[-1]'))) else return [] endif endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/impl_test.vim b/pack/acp/start/vim-go/autoload/go/impl_test.vim index c417cd4..0809836 100644 --- a/pack/acp/start/vim-go/autoload/go/impl_test.vim +++ b/pack/acp/start/vim-go/autoload/go/impl_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_impl() abort try let l:tmp = gotest#write_file('a/a.go', [ @@ -35,3 +39,9 @@ func! Test_impl_get() abort call delete(l:tmp, 'rf') endtry endfunc + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/import.vim b/pack/acp/start/vim-go/autoload/go/import.vim index 8d9e8d4..4e84896 100644 --- a/pack/acp/start/vim-go/autoload/go/import.vim +++ b/pack/acp/start/vim-go/autoload/go/import.vim @@ -4,6 +4,11 @@ " " Check out the docs for more information at /doc/vim-go.txt " + +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + function! go#import#SwitchImport(enabled, localname, path, bang) abort let view = winsaveview() let path = substitute(a:path, '^\s*\(.\{-}\)\s*$', '\1', '') @@ -27,8 +32,8 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort endif if a:bang == "!" - let out = go#util#System("go get -u -v ".shellescape(path)) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#Exec(['go', 'get', '-u', '-v', path]) + if err != 0 call s:Error("Can't find import: " . path . ":" . out) endif endif @@ -65,6 +70,9 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort let packageline = line let appendline = line + elseif linestr =~# '^import\s\+(\+)' + let appendline = line + let appendstr = qlocalpath elseif linestr =~# '^import\s\+(' let appendstr = qlocalpath let indentstr = 1 @@ -161,8 +169,16 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort let linesdelta += 3 let appendstr = qlocalpath let indentstr = 1 + call append(appendline, appendstr) + elseif getline(appendline) =~# '^import\s\+(\+)' + call setline(appendline, 'import (') + call append(appendline + 0, appendstr) + call append(appendline + 1, ')') + let linesdelta -= 1 + let indentstr = 1 + else + call append(appendline, appendstr) endif - call append(appendline, appendstr) execute appendline + 1 if indentstr execute 'normal! >>' @@ -209,5 +225,8 @@ function! s:Error(s) abort echohl Error | echo a:s | echohl None endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/indent_test.vim b/pack/acp/start/vim-go/autoload/go/indent_test.vim new file mode 100644 index 0000000..c969fde --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/indent_test.vim @@ -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\" + 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\" + 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\" + 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 diff --git a/pack/acp/start/vim-go/autoload/go/issue.vim b/pack/acp/start/vim-go/autoload/go/issue.vim new file mode 100644 index 0000000..65db9d8 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/issue.vim @@ -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(':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 diff --git a/pack/acp/start/vim-go/autoload/go/job.vim b/pack/acp/start/vim-go/autoload/go/job.vim index 62214b4..328572e 100644 --- a/pack/acp/start/vim-go/autoload/go/job.vim +++ b/pack/acp/start/vim-go/autoload/go/job.vim @@ -1,24 +1,46 @@ -" Spawn returns callbacks to be used with job_start. It is abstracted to be +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + +" Spawn starts an asynchronous job. See the description of go#job#Options to +" understand the args parameter. +" +" Spawn returns a job. +function! go#job#Spawn(cmd, args) + let l:options = go#job#Options(a:args) + return go#job#Start(a:cmd, l:options) +endfunction + +" Options returns callbacks to be used with job_start. It is abstracted to be " used with various go commands, such as build, test, install, etc.. This " allows us to avoid writing the same callback over and over for some -" commands. It's fully customizable so each command can change it to it's own +" commands. It's fully customizable so each command can change it to its own " logic. " " args is a dictionary with the these keys: -" 'cmd': -" The value to pass to job_start(). " 'bang': " Set to 0 to jump to the first error in the error list. " Defaults to 0. +" 'statustype': +" The status type to use when updating the status. +" See statusline.vim. " 'for': " The g:go_list_type_command key to use to get the error list type to use. +" Errors will not be handled when the value is '_'. " Defaults to '_job' +" 'errorformat': +" The errorformat string to use when parsing errors. Defaults to +" &errorformat. +" See :help 'errorformat'. " 'complete': " A function to call after the job exits and the channel is closed. The " function will be passed three arguments: the job, its exit code, and the -" list of messages received from the channel. The default value will -" process the messages and manage the error list after the job exits and -" the channel is closed. +" list of messages received from the channel. The default is a no-op. A +" custom value can modify the messages before they are processed by the +" returned exit_cb and close_cb callbacks. When the function is called, +" the current window will be the window that was hosting the buffer when +" the job was started. After it returns, the current window will be +" restored to what it was before the function was called. " The return value is a dictionary with these keys: " 'callback': @@ -30,22 +52,30 @@ " 'close_cb': " A function suitable to be passed as a job close_cb handler. See " job-close_cb. -function go#job#Spawn(args) +" 'cwd': +" The path to the directory which contains the current buffer. The +" callbacks are configured to expect this directory is the working +" directory for the job; it should not be modified by callers. +function! go#job#Options(args) let cbs = {} let state = { - \ 'winnr': winnr(), + \ 'winid': win_getid(winnr()), \ 'dir': getcwd(), \ 'jobdir': fnameescape(expand("%:p:h")), \ 'messages': [], - \ 'args': a:args.cmd, \ 'bang': 0, \ 'for': "_job", \ 'exited': 0, \ 'exit_status': 0, \ 'closed': 0, - \ 'errorformat': &errorformat + \ 'errorformat': &errorformat, + \ 'statustype' : '' \ } + if has("patch-8.0.0902") || has('nvim') + let cbs.cwd = state.jobdir + endif + if has_key(a:args, 'bang') let state.bang = a:args.bang endif @@ -54,90 +84,139 @@ function go#job#Spawn(args) let state.for = a:args.for endif - " do nothing in state.complete by default. + if has_key(a:args, 'statustype') + let state.statustype = a:args.statustype + endif + + if has_key(a:args, 'errorformat') + let state.errorformat = a:args.errorformat + endif + function state.complete(job, exit_status, data) + if has_key(self, 'custom_complete') + let l:winid = win_getid(winnr()) + " Always set the active window to the window that was active when the job + " was started. Among other things, this makes sure that the correct + " window's location list will be populated when the list type is + " 'location' and the user has moved windows since starting the job. + call win_gotoid(self.winid) + call self.custom_complete(a:job, a:exit_status, a:data) + call win_gotoid(l:winid) + endif + + call self.show_errors(a:job, a:exit_status, a:data) + endfunction + + function state.show_status(job, exit_status) dict + if self.statustype == '' + return + endif + + if go#config#EchoCommandInfo() + let prefix = '[' . self.statustype . '] ' + if a:exit_status == 0 + call go#util#EchoSuccess(prefix . "SUCCESS") + else + call go#util#EchoError(prefix . "FAIL") + endif + endif + + let status = { + \ 'desc': 'last status', + \ 'type': self.statustype, + \ 'state': "success", + \ } + + if a:exit_status + let status.state = "failed" + endif + + if has_key(self, 'started_at') + let elapsed_time = reltimestr(reltime(self.started_at)) + " strip whitespace + let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') + let status.state .= printf(" (%ss)", elapsed_time) + endif + + call go#statusline#Update(self.jobdir, status) endfunction if has_key(a:args, 'complete') - let state.complete = a:args.complete + let state.custom_complete = a:args.complete endif - function! s:callback(chan, msg) dict - call add(self.messages, a:msg) - endfunction + " explicitly bind _start to state so that within it, self will + " always refer to state. See :help Partial for more information. + " + " _start is intended only for internal use and should not be referenced + " outside of this file. + let cbs._start = function('s:start', [''], state) + " explicitly bind callback to state so that within it, self will " always refer to state. See :help Partial for more information. let cbs.callback = function('s:callback', [], state) - function! s:exit_cb(job, exitval) dict - let self.exit_status = a:exitval - let self.exited = 1 - - if get(g:, 'go_echo_command_info', 1) - if a:exitval == 0 - call go#util#EchoSuccess("SUCCESS") - else - call go#util#EchoError("FAILED") - endif - endif - - if self.closed - call self.complete(a:job, self.exit_status, self.messages) - call self.show_errors(a:job, self.exit_status, self.messages) - endif - endfunction " explicitly bind exit_cb to state so that within it, self will always refer " to state. See :help Partial for more information. let cbs.exit_cb = function('s:exit_cb', [], state) - function! s:close_cb(ch) dict - let self.closed = 1 - - if self.exited - let job = ch_getjob(a:ch) - call self.complete(job, self.exit_status, self.messages) - call self.show_errors(job, self.exit_status, self.messages) - endif - endfunction " explicitly bind close_cb to state so that within it, self will " always refer to state. See :help Partial for more information. let cbs.close_cb = function('s:close_cb', [], state) function state.show_errors(job, exit_status, data) + if self.for == '_' + return + endif + + let l:winid = win_getid(winnr()) + " Always set the active window to the window that was active when the job + " was started. Among other things, this makes sure that the correct + " window's location list will be populated when the list type is + " 'location' and the user has moved windows since starting the job. + call win_gotoid(self.winid) + let l:listtype = go#list#Type(self.for) if a:exit_status == 0 call go#list#Clean(l:listtype) + call win_gotoid(l:winid) return endif let l:listtype = go#list#Type(self.for) if len(a:data) == 0 call go#list#Clean(l:listtype) + call win_gotoid(l:winid) return endif let out = join(self.messages, "\n") - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' try " parse the errors relative to self.jobdir - execute cd self.jobdir + execute l:cd self.jobdir call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for) let errors = go#list#Get(l:listtype) finally - execute cd . fnameescape(self.dir) + execute l:cd fnameescape(self.dir) endtry if empty(errors) " failed to parse errors, output the original content - call go#util#EchoError(self.messages + [self.dir]) + call go#util#EchoError([self.dir] + self.messages) + call win_gotoid(l:winid) return endif - if self.winnr == winnr() + " only open the error window if user was still in the window from which + " the job was started. + if self.winid == l:winid call go#list#Window(l:listtype, len(errors)) - if !self.bang + if self.bang + call win_gotoid(l:winid) + else call go#list#JumpToFirst(l:listtype) endif endif @@ -146,4 +225,311 @@ function go#job#Spawn(args) return cbs endfunction +function! s:start(args) dict + if go#config#EchoCommandInfo() && self.statustype != "" + let prefix = '[' . self.statustype . '] ' + call go#util#EchoSuccess(prefix . "dispatched") + endif + + if self.statustype != '' + let status = { + \ 'desc': 'current status', + \ 'type': self.statustype, + \ 'state': "started", + \ } + + call go#statusline#Update(self.jobdir, status) + endif + let self.started_at = reltime() +endfunction + +function! s:callback(chan, msg) dict + call add(self.messages, a:msg) +endfunction + +function! s:exit_cb(job, exitval) dict + let self.exit_status = a:exitval + let self.exited = 1 + + call self.show_status(a:job, a:exitval) + + if self.closed || has('nvim') + call self.complete(a:job, self.exit_status, self.messages) + endif +endfunction + +function! s:close_cb(ch) dict + let self.closed = 1 + + if self.exited + let job = ch_getjob(a:ch) + call self.complete(job, self.exit_status, self.messages) + endif +endfunction + +" go#job#Start runs a job. The options are expected to be the options +" suitable for Vim8 jobs. When called from Neovim, Vim8 options will be +" transformed to their Neovim equivalents. +function! go#job#Start(cmd, options) + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' + let l:options = copy(a:options) + + if has('nvim') + let l:options = s:neooptions(l:options) + endif + + " Verify that the working directory for the job actually exists. Return + " early if the directory does not exist. This helps avoid errors when + " working with plugins that use virtual files that don't actually exist on + " the file system. + let l:filedir = expand("%:p:h") + if has_key(l:options, 'cwd') && !isdirectory(l:options.cwd) + return + elseif !isdirectory(l:filedir) + return + endif + + let l:manualcd = 0 + if !has_key(l:options, 'cwd') + " pre start + let l:manualcd = 1 + let dir = getcwd() + execute l:cd fnameescape(filedir) + elseif !(has("patch-8.0.0902") || has('nvim')) + let l:manualcd = 1 + let l:dir = l:options.cwd + execute l:cd fnameescape(l:dir) + call remove(l:options, 'cwd') + endif + + if has_key(l:options, '_start') + call l:options._start() + " remove _start to play nicely with vim (when vim encounters an unexpected + " job option it reports an "E475: invalid argument" error). + unlet l:options._start + endif + + " noblock was added in 8.1.350; remove it if it's not supported. + if has_key(l:options, 'noblock') && (has('nvim') || !has("patch-8.1.350")) + call remove(l:options, 'noblock') + endif + + if go#util#HasDebug('shell-commands') + call go#util#EchoInfo('job command: ' . string(a:cmd)) + endif + + if has('nvim') + let l:input = [] + if has_key(a:options, 'in_io') && a:options.in_io ==# 'file' && !empty(a:options.in_name) + let l:input = readfile(a:options.in_name, "b") + endif + + let job = jobstart(a:cmd, l:options) + + if len(l:input) > 0 + call chansend(job, l:input) + " close stdin to signal that no more bytes will be sent. + call chanclose(job, 'stdin') + endif + else + let l:cmd = a:cmd + if go#util#IsWin() + let l:cmd = join(map(copy(a:cmd), function('s:winjobarg')), " ") + endif + + let job = job_start(l:cmd, l:options) + endif + + if l:manualcd + " post start + execute l:cd fnameescape(l:dir) + endif + + return job +endfunction + +" s:neooptions returns a dictionary of job options suitable for use by Neovim +" based on a dictionary of job options suitable for Vim8. +function! s:neooptions(options) + let l:options = {} + let l:options['stdout_buf'] = '' + let l:options['stderr_buf'] = '' + + let l:err_mode = get(a:options, 'err_mode', get(a:options, 'mode', '')) + let l:out_mode = get(a:options, 'out_mode', get(a:options, 'mode', '')) + + for key in keys(a:options) + if key == 'cwd' + let l:options['cwd'] = a:options['cwd'] + continue + endif + + if key == 'callback' + let l:options['callback'] = a:options['callback'] + + if !has_key(a:options, 'out_cb') + let l:options['on_stdout'] = function('s:callback2on_stdout', [l:out_mode], l:options) + endif + + if !has_key(a:options, 'err_cb') + let l:options['on_stderr'] = function('s:callback2on_stderr', [l:err_mode], l:options) + endif + + continue + endif + + if key == 'out_cb' + let l:options['out_cb'] = a:options['out_cb'] + let l:options['on_stdout'] = function('s:on_stdout', [l:out_mode], l:options) + + continue + endif + + if key == 'err_cb' + let l:options['err_cb'] = a:options['err_cb'] + let l:options['on_stderr'] = function('s:on_stderr', [l:err_mode], l:options) + + continue + endif + + if key == 'exit_cb' + let l:options['exit_cb'] = a:options['exit_cb'] + let l:options['on_exit'] = function('s:on_exit', [], l:options) + + continue + endif + + if key == 'close_cb' + continue + endif + + if key == 'stoponexit' + if a:options['stoponexit'] == '' + let l:options['detach'] = 1 + endif + continue + endif + endfor + return l:options +endfunction + +function! s:callback2on_stdout(mode, ch, data, event) dict + let self.stdout_buf = s:neocb(a:mode, a:ch, self.stdout_buf, a:data, self.callback) +endfunction + +function! s:callback2on_stderr(mode, ch, data, event) dict + let self.stderr_buf = s:neocb(a:mode, a:ch, self.stderr_buf, a:data, self.callback) +endfunction + +function! s:on_stdout(mode, ch, data, event) dict + let self.stdout_buf = s:neocb(a:mode, a:ch, self.stdout_buf, a:data, self.out_cb) +endfunction + +function! s:on_stderr(mode, ch, data, event) dict + let self.stderr_buf = s:neocb(a:mode, a:ch, self.stderr_buf, a:data, self.err_cb ) +endfunction + +function! s:on_exit(jobid, exitval, event) dict + call self.exit_cb(a:jobid, a:exitval) +endfunction + +function! go#job#Stop(job) abort + if has('nvim') + call jobstop(a:job) + return + endif + + call job_stop(a:job) + call go#job#Wait(a:job) + return +endfunction + +function! go#job#Wait(job) abort + if has('nvim') + call jobwait([a:job]) + return + endif + + while job_status(a:job) is# 'run' + sleep 50m + endwhile +endfunction + +function! s:winjobarg(idx, val) abort + if empty(a:val) + return '""' + endif + return a:val +endfunction + +function! s:neocb(mode, ch, buf, data, callback) + " dealing with the channel lines of Neovim is awful. The docs (:help + " channel-lines) say: + " stream event handlers may receive partial (incomplete) lines. For a + " given invocation of on_stdout etc, `a:data` is not guaranteed to end + " with a newline. + " - `abcdefg` may arrive as `['abc']`, `['defg']`. + " - `abc\nefg` may arrive as `['abc', '']`, `['efg']` or `['abc']`, + " `['','efg']`, or even `['ab']`, `['c','efg']`. + " + " Thankfully, though, this is explained a bit better in an issue: + " https://github.com/neovim/neovim/issues/3555. Specifically in these two + " comments: + " * https://github.com/neovim/neovim/issues/3555#issuecomment-152290804 + " * https://github.com/neovim/neovim/issues/3555#issuecomment-152588749 + " + " The key is + " Every item in the list passed to job control callbacks represents a + " string after a newline(Except the first, of course). If the program + " outputs: "hello\nworld" the corresponding list is ["hello", "world"]. + " If the program outputs "hello\nworld\n", the corresponding list is + " ["hello", "world", ""]. In other words, you can always determine if + " the last line received is complete or not. + " and + " for every list you receive in a callback, all items except the first + " represent newlines. + + let l:buf = '' + + " A single empty string means EOF was reached. The first item will never be + " an empty string except for when it's the only item and is signaling that + " EOF was reached. + if len(a:data) == 1 && a:data[0] == '' + " when there's nothing buffered, return early so that an + " erroneous message will not be added. + if a:buf == '' + return '' + endif + + let l:data = [a:buf] + else + let l:data = copy(a:data) + let l:data[0] = a:buf . l:data[0] + + " The last element may be a partial line; save it for next time. + if a:mode != 'raw' + let l:buf = l:data[-1] + let l:data = l:data[:-2] + endif + endif + + let l:i = 0 + let l:last = len(l:data) - 1 + while l:i <= l:last + let l:msg = l:data[l:i] + if a:mode == 'raw' && l:i < l:last + let l:msg = l:msg . "\n" + endif + call a:callback(a:ch, l:msg) + + let l:i += 1 + endwhile + + return l:buf +endfunction + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/jobcontrol.vim b/pack/acp/start/vim-go/autoload/go/jobcontrol.vim deleted file mode 100644 index 2550581..0000000 --- a/pack/acp/start/vim-go/autoload/go/jobcontrol.vim +++ /dev/null @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/keyify.vim b/pack/acp/start/vim-go/autoload/go/keyify.vim index 23ca0f3..41a543f 100644 --- a/pack/acp/start/vim-go/autoload/go/keyify.vim +++ b/pack/acp/start/vim-go/autoload/go/keyify.vim @@ -1,20 +1,24 @@ -function! go#keyify#Keyify() - let bin_path = go#path#CheckBinPath("keyify") - let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim - if empty(bin_path) || !exists('*json_decode') +function! go#keyify#Keyify() + " Needs: https://github.com/dominikh/go-tools/pull/272 + "\ '-tags', go#config#BuildTags(), + let l:cmd = ['keyify', + \ '-json', + \ printf('%s:#%s', fnamemodify(expand('%'), ':p:gs?\\?/?'), go#util#OffsetCursor())] + + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError("non-zero exit code: " . l:out) return endif - - " Get result of command as json, that contains `start`, `end` and `replacement` - let command = printf("%s -json %s:#%s", go#util#Shellescape(bin_path), - \ go#util#Shellescape(fname), go#util#OffsetCursor()) - let output = go#util#System(command) - silent! let result = json_decode(output) + silent! let result = json_decode(l:out) " We want to output the error message in case the result isn't a JSON if type(result) != type({}) - call go#util#EchoError(s:chomp(output)) + call go#util#EchoError(s:chomp(l:out)) return endif @@ -53,4 +57,8 @@ function! s:chomp(string) return substitute(a:string, '\n\+$', '', '') endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/lint.vim b/pack/acp/start/vim-go/autoload/go/lint.vim index 4204694..21e2102 100644 --- a/pack/acp/start/vim-go/autoload/go/lint.vim +++ b/pack/acp/start/vim-go/autoload/go/lint.vim @@ -1,62 +1,34 @@ -if !exists("g:go_metalinter_command") - let g:go_metalinter_command = "" -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim -if !exists("g:go_metalinter_autosave_enabled") - let g:go_metalinter_autosave_enabled = ['vet', 'golint'] -endif - -if !exists("g:go_metalinter_enabled") - let g:go_metalinter_enabled = ['vet', 'golint', 'errcheck'] -endif - -if !exists("g:go_metalinter_disabled") - let g:go_metalinter_disabled = [] -endif - -if !exists("g:go_golint_bin") - let g:go_golint_bin = "golint" -endif - -if !exists("g:go_errcheck_bin") - let g:go_errcheck_bin = "errcheck" -endif - -function! go#lint#Gometa(autosave, ...) abort +function! go#lint#Gometa(bang, autosave, ...) abort if a:0 == 0 let goargs = [expand('%:p:h')] else let goargs = a:000 endif - let bin_path = go#path#CheckBinPath("gometalinter") - if empty(bin_path) - return - endif + let l:metalinter = go#config#MetalinterCommand() - let cmd = [bin_path] - let cmd += ["--disable-all"] + if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint' + let cmd = s:metalintercmd(l:metalinter) + if empty(cmd) + return + endif - if a:autosave || empty(g:go_metalinter_command) " linters - let linters = a:autosave ? g:go_metalinter_autosave_enabled : g:go_metalinter_enabled + let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled() for linter in linters let cmd += ["--enable=".linter] endfor - for linter in g:go_metalinter_disabled + for linter in go#config#MetalinterDisabled() let cmd += ["--disable=".linter] endfor - - " gometalinter has a --tests flag to tell its linters whether to run - " against tests. While not all of its linters respect this flag, for those - " that do, it means if we don't pass --tests, the linter won't run against - " test files. One example of a linter that will not run against tests if - " we do not specify this flag is errcheck. - let cmd += ["--tests"] else " the user wants something else, let us use it. - let cmd += split(g:go_metalinter_command, " ") + let cmd = split(go#config#MetalinterCommand(), " ") endif if a:autosave @@ -64,37 +36,44 @@ function! go#lint#Gometa(autosave, ...) abort " will be cleared redraw - " Include only messages for the active buffer for autosave. - let cmd += [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))] - endif - - " gometalinter has a default deadline of 5 seconds. - " - " For async mode (s:lint_job), we want to override the default deadline only - " if we have a deadline configured. - " - " For sync mode (go#util#System), always explicitly pass the 5 seconds - " deadline if there is no other deadline configured. If a deadline is - " configured, then use it. - - " Call gometalinter asynchronously. - if go#util#has_job() && has('lambda') - let deadline = get(g:, 'go_metalinter_deadline', 0) - if deadline != 0 - let cmd += ["--deadline=" . deadline] + if l:metalinter == "gometalinter" + " Include only messages for the active buffer for autosave. + let include = [printf('--include=^%s:.*$', fnamemodify(expand('%:p'), ":."))] + if go#util#has_job() + let include = [printf('--include=^%s:.*$', expand('%:p:t'))] + endif + let cmd += include + elseif l:metalinter == "golangci-lint" + let goargs[0] = expand('%:p') endif - - let cmd += goargs - - call s:lint_job({'cmd': cmd}, a:autosave) - return endif - " We're calling gometalinter synchronously. - let cmd += ["--deadline=" . get(g:, 'go_metalinter_deadline', "5s")] + " Call metalinter asynchronously. + let deadline = go#config#MetalinterDeadline() + if deadline != '' + let cmd += ["--deadline=" . deadline] + endif let cmd += goargs + if l:metalinter == "gometalinter" + " Gometalinter can output one of the two, so we look for both: + " :::: () + " :::: () + " 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: + " ::: () + " This can be defined by the following errorformat: + let errformat = "%f:%l:%c:\ %m" + endif + + if go#util#has_job() + call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave) + return + endif + let [l:out, l:err] = go#util#Exec(cmd) if a:autosave @@ -107,48 +86,46 @@ function! go#lint#Gometa(autosave, ...) abort call go#list#Clean(l:listtype) echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None else - " GoMetaLinter can output one of the two, so we look for both: - " ::[]: () - " ::: () - " This can be defined by the following errorformat: - let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m" - + let l:winid = win_getid(winnr()) " Parse and populate our location list call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter') let errors = go#list#Get(l:listtype) call go#list#Window(l:listtype, len(errors)) - if !a:autosave - call go#list#JumpToFirst(l:listtype) + if a:autosave || a:bang + call win_gotoid(l:winid) + return endif + call go#list#JumpToFirst(l:listtype) endif endfunction " Golint calls 'golint' on the current directory. Any warnings are populated in " the location list -function! go#lint#Golint(...) abort - let bin_path = go#path#CheckBinPath(g:go_golint_bin) - if empty(bin_path) - return - endif - let bin_path = go#util#Shellescape(bin_path) - +function! go#lint#Golint(bang, ...) abort if a:0 == 0 - let out = go#util#System(bin_path . " " . go#util#Shellescape(go#package#ImportPath())) + let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), go#package#ImportPath()]) else - let out = go#util#System(bin_path . " " . go#util#Shelljoin(a:000)) + let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000) endif - if empty(out) - echon "vim-go: " | echohl Function | echon "[lint] PASS" | echohl None + if empty(l:out) + call go#util#EchoSuccess('[lint] PASS') return endif + let l:winid = win_getid(winnr()) let l:listtype = go#list#Type("GoLint") - call go#list#Parse(l:listtype, out, "GoLint") - let errors = go#list#Get(l:listtype) - call go#list#Window(l:listtype, len(errors)) + call go#list#Parse(l:listtype, l:out, "GoLint") + let l:errors = go#list#Get(l:listtype) + call go#list#Window(l:listtype, len(l:errors)) + + if a:bang + call win_gotoid(l:winid) + return + endif + call go#list#JumpToFirst(l:listtype) endfunction @@ -156,202 +133,152 @@ endfunction " the location list function! go#lint#Vet(bang, ...) abort call go#cmd#autowrite() - echon "vim-go: " | echohl Identifier | echon "calling vet..." | echohl None + + if go#config#EchoCommandInfo() + call go#util#EchoProgress('calling vet...') + endif + if a:0 == 0 - let out = go#util#System('go vet ' . go#util#Shellescape(go#package#ImportPath())) + let [l:out, l:err] = go#util#Exec(['go', 'vet', go#package#ImportPath()]) else - let out = go#util#System('go tool vet ' . go#util#Shelljoin(a:000)) + let [l:out, l:err] = go#util#Exec(['go', 'tool', 'vet'] + a:000) endif let l:listtype = go#list#Type("GoVet") - if go#util#ShellError() != 0 - let errorformat="%-Gexit status %\\d%\\+," . &errorformat + if l:err != 0 + let l:winid = win_getid(winnr()) + let errorformat = "%-Gexit status %\\d%\\+," . &errorformat call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet") let errors = go#list#Get(l:listtype) call go#list#Window(l:listtype, len(errors)) if !empty(errors) && !a:bang call go#list#JumpToFirst(l:listtype) + else + call win_gotoid(l:winid) endif - echon "vim-go: " | echohl ErrorMsg | echon "[vet] FAIL" | echohl None else call go#list#Clean(l:listtype) - redraw | echon "vim-go: " | echohl Function | echon "[vet] PASS" | echohl None + call go#util#EchoSuccess('[vet] PASS') endif endfunction " ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in " the location list -function! go#lint#Errcheck(...) abort +function! go#lint#Errcheck(bang, ...) abort if a:0 == 0 - let import_path = go#package#ImportPath() + let l:import_path = go#package#ImportPath() if import_path == -1 - echohl Error | echomsg "vim-go: package is not inside GOPATH src" | echohl None + call go#util#EchoError('package is not inside GOPATH src') return endif else - let import_path = go#util#Shelljoin(a:000) + let l:import_path = join(a:000, ' ') endif - let bin_path = go#path#CheckBinPath(g:go_errcheck_bin) - if empty(bin_path) - return - endif - - echon "vim-go: " | echohl Identifier | echon "errcheck analysing ..." | echohl None + call go#util#EchoProgress('[errcheck] analysing ...') redraw - let command = go#util#Shellescape(bin_path) . ' -abspath ' . import_path - let out = go#tool#ExecuteInDir(command) + let [l:out, l:err] = go#util#Exec([go#config#ErrcheckBin(), '-abspath', l:import_path]) let l:listtype = go#list#Type("GoErrCheck") - if go#util#ShellError() != 0 + if l:err != 0 + let l:winid = win_getid(winnr()) let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m" " Parse and populate our location list call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck') - let errors = go#list#Get(l:listtype) - if empty(errors) - echohl Error | echomsg "GoErrCheck returned error" | echohl None - echo out + let l:errors = go#list#Get(l:listtype) + if empty(l:errors) + call go#util#EchoError(l:out) return endif if !empty(errors) - echohl Error | echomsg "GoErrCheck found errors" | echohl None call go#list#Populate(l:listtype, errors, 'Errcheck') call go#list#Window(l:listtype, len(errors)) - if !empty(errors) + if !a:bang call go#list#JumpToFirst(l:listtype) + else + call win_gotoid(l:winid) endif endif else call go#list#Clean(l:listtype) - echon "vim-go: " | echohl Function | echon "[errcheck] PASS" | echohl None + call go#util#EchoSuccess('[errcheck] PASS') endif - endfunction function! go#lint#ToggleMetaLinterAutoSave() abort - if get(g:, "go_metalinter_autosave", 0) - let g:go_metalinter_autosave = 0 + if go#config#MetalinterAutosave() + call go#config#SetMetalinterAutosave(0) call go#util#EchoProgress("auto metalinter disabled") return end - let g:go_metalinter_autosave = 1 + call go#config#SetMetalinterAutosave(1) call go#util#EchoProgress("auto metalinter enabled") endfunction -function! s:lint_job(args, autosave) - let state = { - \ 'status_dir': expand('%:p:h'), - \ 'started_at': reltime(), - \ 'messages': [], - \ 'exited': 0, - \ 'closed': 0, - \ 'exit_status': 0, - \ 'winnr': winnr(), - \ 'autosave': a:autosave - \ } +function! s:lint_job(args, bang, autosave) + let l:opts = { + \ 'statustype': a:args.statustype, + \ 'errorformat': a:args.errformat, + \ 'for': "GoMetaLinter", + \ 'bang': a:bang, + \ } - call go#statusline#Update(state.status_dir, { - \ 'desc': "current status", - \ 'type': "gometalinter", - \ 'state': "analysing", - \}) + if a:autosave + let l:opts.for = "GoMetaLinterAutoSave" + endif " autowrite is not enabled for jobs call go#cmd#autowrite() - if a:autosave - let state.listtype = go#list#Type("GoMetaLinterAutoSave") - else - let state.listtype = go#list#Type("GoMetaLinter") - endif - - function! s:callback(chan, msg) dict closure - call add(self.messages, a:msg) - endfunction - - function! s:exit_cb(job, exitval) dict - let self.exited = 1 - let self.exit_status = a:exitval - - let status = { - \ 'desc': 'last status', - \ 'type': "gometaliner", - \ 'state': "finished", - \ } - - if a:exitval - let status.state = "failed" - endif - - let elapsed_time = reltimestr(reltime(self.started_at)) - " strip whitespace - let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') - let status.state .= printf(" (%ss)", elapsed_time) - - call go#statusline#Update(self.status_dir, status) - - if self.closed - call self.show_errors() - endif - endfunction - - function! s:close_cb(ch) dict - let self.closed = 1 - - if self.exited - call self.show_errors() - endif - endfunction - - - function state.show_errors() - let l:winnr = winnr() - - " make sure the current window is the window from which gometalinter was - " run when the listtype is locationlist so that the location list for the - " correct window will be populated. - if self.listtype == 'locationlist' - exe self.winnr . "wincmd w" - endif - - let l:errorformat = '%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m' - call go#list#ParseFormat(self.listtype, l:errorformat, self.messages, 'GoMetaLinter') - - let errors = go#list#Get(self.listtype) - call go#list#Window(self.listtype, len(errors)) - - " move to the window that was active before processing the errors, because - " the user may have moved around within the window or even moved to a - " different window since saving. Moving back to current window as of the - " start of this function avoids the perception that the quickfix window - " steals focus when linting takes a while. - if self.autosave - exe l:winnr . "wincmd w" - endif - - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoSuccess("linting finished") - endif - endfunction - - " explicitly bind the callbacks to state so that self within them always - " refers to state. See :help Partial for more information. - let start_options = { - \ 'callback': funcref("s:callback", [], state), - \ 'exit_cb': funcref("s:exit_cb", [], state), - \ 'close_cb': funcref("s:close_cb", [], state), - \ } - - call job_start(a:args.cmd, start_options) - - if get(g:, 'go_echo_command_info', 1) - call go#util#EchoProgress("linting started ...") - endif + call go#job#Spawn(a:args.cmd, l:opts) endfunction +function! s:metalintercmd(metalinter) + let l:cmd = [] + let bin_path = go#path#CheckBinPath(a:metalinter) + if !empty(bin_path) + if a:metalinter == "gometalinter" + let l:cmd = s:gometalintercmd(bin_path) + elseif a:metalinter == "golangci-lint" + let l:cmd = s:golangcilintcmd(bin_path) + endif + endif + + return cmd +endfunction + +function! s:gometalintercmd(bin_path) + let cmd = [a:bin_path] + let cmd += ["--disable-all"] + + " gometalinter has a --tests flag to tell its linters whether to run + " against tests. While not all of its linters respect this flag, for those + " that do, it means if we don't pass --tests, the linter won't run against + " test files. One example of a linter that will not run against tests if + " we do not specify this flag is errcheck. + let cmd += ["--tests"] + return cmd +endfunction + +function! s:golangcilintcmd(bin_path) + let cmd = [a:bin_path] + let cmd += ["run"] + let cmd += ["--print-issued-lines=false"] + let cmd += ["--disable-all"] + " do not use the default exclude patterns, because doing so causes golint + " problems about missing doc strings to be ignored and other things that + " golint identifies. + let cmd += ["--exclude-use-default=false"] + return cmd +endfunction + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/lint_test.vim b/pack/acp/start/vim-go/autoload/go/lint_test.vim index 141d57c..dccc138 100644 --- a/pack/acp/start/vim-go/autoload/go/lint_test.vim +++ b/pack/acp/start/vim-go/autoload/go/lint_test.vim @@ -1,114 +1,131 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_Gometa() abort + call s:gometa('gometaliner') +endfunc + +func! Test_GometaGolangciLint() abort + call s:gometa('golangci-lint') +endfunc + +func! s:gometa(metalinter) abort let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' silent exe 'e ' . $GOPATH . '/src/lint/lint.go' - let expected = [ - \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} - \ ] + try + let g:go_metalinter_comand = a:metalinter + let expected = [ + \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} + \ ] - " clear the quickfix lists - call setqflist([], 'r') + " clear the quickfix lists + call setqflist([], 'r') - " call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will - " be autoloaded and the default for g:go_metalinter_enabled will be set so - " we can capture it to restore it after the test is run. - call go#lint#ToggleMetaLinterAutoSave() - " And restore it back to its previous value - call go#lint#ToggleMetaLinterAutoSave() + let g:go_metalinter_enabled = ['golint'] - let orig_go_metalinter_enabled = g:go_metalinter_enabled - let g:go_metalinter_enabled = ['golint'] + call go#lint#Gometa(0, 0, $GOPATH . '/src/foo') - call go#lint#Gometa(0, $GOPATH . '/src/foo') - - let actual = getqflist() - let start = reltime() - while len(actual) == 0 && reltimefloat(reltime(start)) < 10 - sleep 100m let actual = getqflist() - endwhile + let start = reltime() + while len(actual) == 0 && reltimefloat(reltime(start)) < 10 + sleep 100m + let actual = getqflist() + endwhile - call gotest#assert_quickfix(actual, expected) - let g:go_metalinter_enabled = orig_go_metalinter_enabled + call gotest#assert_quickfix(actual, expected) + finally + unlet g:go_metalinter_enabled + endtry endfunc func! Test_GometaWithDisabled() abort + call s:gometawithdisabled('gometalinter') +endfunc + +func! Test_GometaWithDisabledGolangciLint() abort + call s:gometawithdisabled('golangci-lint') +endfunc + +func! s:gometawithdisabled(metalinter) abort let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' silent exe 'e ' . $GOPATH . '/src/lint/lint.go' - let expected = [ - \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} - \ ] + try + let g:go_metalinter_comand = a:metalinter + let expected = [ + \ {'lnum': 5, 'bufnr': bufnr('%')+1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingFooDoc should have comment or be unexported (golint)'} + \ ] - " clear the quickfix lists - call setqflist([], 'r') + " clear the quickfix lists + call setqflist([], 'r') - " call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will - " be autoloaded and the default for g:go_metalinter_disabled will be set so - " we can capture it to restore it after the test is run. - call go#lint#ToggleMetaLinterAutoSave() - " And restore it back to its previous value - call go#lint#ToggleMetaLinterAutoSave() + let g:go_metalinter_disabled = ['vet'] - let orig_go_metalinter_disabled = g:go_metalinter_disabled - let g:go_metalinter_disabled = ['vet'] + call go#lint#Gometa(0, 0, $GOPATH . '/src/foo') - call go#lint#Gometa(0, $GOPATH . '/src/foo') - - let actual = getqflist() - let start = reltime() - while len(actual) == 0 && reltimefloat(reltime(start)) < 10 - sleep 100m let actual = getqflist() - endwhile + let start = reltime() + while len(actual) == 0 && reltimefloat(reltime(start)) < 10 + sleep 100m + let actual = getqflist() + endwhile - call gotest#assert_quickfix(actual, expected) - let g:go_metalinter_disabled = orig_go_metalinter_disabled + call gotest#assert_quickfix(actual, expected) + finally + unlet g:go_metalinter_disabled + endtry endfunc func! Test_GometaAutoSave() abort + call s:gometaautosave('gometalinter') +endfunc + +func! Test_GometaAutoSaveGolangciLint() abort + call s:gometaautosave('golangci-lint') +endfunc + +func! s:gometaautosave(metalinter) abort let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' silent exe 'e ' . $GOPATH . '/src/lint/lint.go' - let expected = [ - \ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'} - \ ] + try + let g:go_metalinter_comand = a:metalinter + let expected = [ + \ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported (golint)'} + \ ] - let winnr = winnr() + let winnr = winnr() - " clear the location lists - call setloclist(l:winnr, [], 'r') + " clear the location lists + call setloclist(l:winnr, [], 'r') - " call go#lint#ToggleMetaLinterAutoSave from lint.vim so that the file will - " be autoloaded and the default for g:go_metalinter_autosave_enabled will be - " set so we can capture it to restore it after the test is run. - call go#lint#ToggleMetaLinterAutoSave() - " And restore it back to its previous value - call go#lint#ToggleMetaLinterAutoSave() + let g:go_metalinter_autosave_enabled = ['golint'] - let orig_go_metalinter_autosave_enabled = g:go_metalinter_autosave_enabled - let g:go_metalinter_autosave_enabled = ['golint'] + call go#lint#Gometa(0, 1) - call go#lint#Gometa(1) - - let actual = getloclist(l:winnr) - let start = reltime() - while len(actual) == 0 && reltimefloat(reltime(start)) < 10 - sleep 100m let actual = getloclist(l:winnr) - endwhile + let start = reltime() + while len(actual) == 0 && reltimefloat(reltime(start)) < 10 + sleep 100m + let actual = getloclist(l:winnr) + endwhile - call gotest#assert_quickfix(actual, expected) - let g:go_metalinter_autosave_enabled = orig_go_metalinter_autosave_enabled + call gotest#assert_quickfix(actual, expected) + finally + unlet g:go_metalinter_autosave_enabled + endtry endfunc -func! Test_Vet() +func! Test_Vet() abort let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' silent exe 'e ' . $GOPATH . '/src/vet/vet.go' compiler go let expected = [ - \ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'arg str for printf verb %d of wrong type: string'} + \ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', + \ 'text': 'Printf format %d has arg str of wrong type string'} \ ] let winnr = winnr() @@ -128,4 +145,8 @@ func! Test_Vet() call gotest#assert_quickfix(actual, expected) endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/list.vim b/pack/acp/start/vim-go/autoload/go/list.vim index 23b0cbd..1805d4d 100644 --- a/pack/acp/start/vim-go/autoload/go/list.vim +++ b/pack/acp/start/vim-go/autoload/go/list.vim @@ -1,10 +1,6 @@ -if !exists("g:go_list_type") - let g:go_list_type = "" -endif - -if !exists("g:go_list_type_commands") - let g:go_list_type_commands = {} -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim " Window opens the list with the given height up to 10 lines maximum. " Otherwise g:go_loclist_height is used. @@ -22,7 +18,7 @@ function! go#list#Window(listtype, ...) abort return endif - let height = get(g:, "go_list_height", 0) + let height = go#config#ListHeight() if height == 0 " prevent creating a large location height for a large set of numbers if a:1 > 10 @@ -113,7 +109,7 @@ endfunction " Close closes the location list function! go#list#Close(listtype) abort - let autoclose_window = get(g:, 'go_list_autoclose', 1) + let autoclose_window = go#config#ListAutoclose() if !autoclose_window return endif @@ -126,13 +122,12 @@ function! go#list#Close(listtype) abort endfunction function! s:listtype(listtype) abort - if g:go_list_type == "locationlist" - return "locationlist" - elseif g:go_list_type == "quickfix" - return "quickfix" + let listtype = go#config#ListType() + if empty(listtype) + return a:listtype endif - return a:listtype + return listtype endfunction " s:default_list_type_commands is the defaults that will be used for each of @@ -143,6 +138,7 @@ endfunction " in g:go_list_type_commands. let s:default_list_type_commands = { \ "GoBuild": "quickfix", + \ "GoDebug": "quickfix", \ "GoErrCheck": "quickfix", \ "GoFmt": "locationlist", \ "GoGenerate": "quickfix", @@ -150,6 +146,7 @@ let s:default_list_type_commands = { \ "GoLint": "quickfix", \ "GoMetaLinter": "quickfix", \ "GoMetaLinterAutoSave": "locationlist", + \ "GoModFmt": "locationlist", \ "GoModifyTags": "locationlist", \ "GoRename": "quickfix", \ "GoRun": "quickfix", @@ -169,7 +166,11 @@ function! go#list#Type(for) abort let l:listtype = "quickfix" endif - return get(g:go_list_type_commands, a:for, l:listtype) + return get(go#config#ListTypeCommands(), a:for, l:listtype) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/lsp.vim b/pack/acp/start/vim-go/autoload/go/lsp.vim new file mode 100644 index 0000000..aa21bce --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/lsp.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim b/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim new file mode 100644 index 0000000..37c00a8 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/lsp/completionitemkind.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/lsp/message.vim b/pack/acp/start/vim-go/autoload/go/lsp/message.vim new file mode 100644 index 0000000..6398185 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/lsp/message.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/mod.vim b/pack/acp/start/vim-go/autoload/go/mod.vim new file mode 100644 index 0000000..021f7f9 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/mod.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/package.vim b/pack/acp/start/vim-go/autoload/go/package.vim index 7cf7c5c..ced39cc 100644 --- a/pack/acp/start/vim-go/autoload/go/package.vim +++ b/pack/acp/start/vim-go/autoload/go/package.vim @@ -5,6 +5,10 @@ " This file provides a utility function that performs auto-completion of " package names, for use by other commands. +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + let s:goos = $GOOS let s:goarch = $GOARCH @@ -28,7 +32,7 @@ if len(s:goarch) == 0 endif endif -function! go#package#Paths() abort +function! s:paths() abort let dirs = [] if !exists("s:goroot") @@ -54,63 +58,119 @@ function! go#package#Paths() abort return dirs endfunction +function! s:module() abort + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-m', '-f', '{{.Dir}}']) + if l:err != 0 + return {} + endif + let l:dir = split(l:out, '\n')[0] + + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-m', '-f', '{{.Path}}']) + if l:err != 0 + return {} + endif + let l:path = split(l:out, '\n')[0] + + return {'dir': l:dir, 'path': l:path} +endfunction + +function! s:vendordirs() abort + let l:vendorsuffix = go#util#PathSep() . 'vendor' + let l:module = s:module() + if empty(l:module) + let [l:root, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Root}}']) + if l:err != 0 + return [] + endif + let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src' + + let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}']) + if l:err != 0 + return [] + endif + let l:dir = split(l:dir, '\n')[0] + + let l:vendordirs = [] + while l:dir != l:root + let l:vendordir = l:dir . l:vendorsuffix + if isdirectory(l:vendordir) + let l:vendordirs = add(l:vendordirs, l:vendordir) + endif + + let l:dir = fnamemodify(l:dir, ':h') + endwhile + + return l:vendordirs + endif + + let l:vendordir = l:module.dir . l:vendorsuffix + if !isdirectory(l:vendordir) + return [] + endif + return [l:vendordir] +endfunction + let s:import_paths = {} -" ImportPath returns the import path in the current directory it was executed +" ImportPath returns the import path of the package for current buffer. function! go#package#ImportPath() abort let dir = expand("%:p:h") if has_key(s:import_paths, dir) return s:import_paths[dir] endif - let out = go#tool#ExecuteInDir("go list") - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#ExecInDir(['go', 'list']) + if l:err != 0 return -1 endif - let import_path = split(out, '\n')[0] + let l:importpath = split(out, '\n')[0] " go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH. " Check it and retun an error if that is the case - if import_path[0] ==# '_' + if l:importpath[0] ==# '_' return -1 endif - let s:import_paths[dir] = import_path + let s:import_paths[dir] = l:importpath - return import_path + return l:importpath endfunction +" FromPath returns the import path of arg. function! go#package#FromPath(arg) abort - let path = fnamemodify(resolve(a:arg), ':p') - let dirs = go#package#Paths() + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' + let l:dir = getcwd() - for dir in dirs - if len(dir) && match(path, dir) == 0 - let workspace = dir - break - endif - endfor + let l:path = a:arg + if !isdirectory(l:path) + let l:path = fnamemodify(l:path, ':h') + endif - if !exists('workspace') + execute l:cd fnameescape(l:path) + let [l:out, l:err] = go#util#Exec(['go', 'list']) + execute l:cd fnameescape(l:dir) + if l:err != 0 return -1 endif - let path = substitute(path, '/*$', '', '') - let workspace = substitute(workspace . '/src/', '/+', '', '') - if isdirectory(path) - return substitute(path, workspace, '', '') - else - return substitute(substitute(path, workspace, '', ''), - \ '/' . fnamemodify(path, ':t'), '', '') + let l:importpath = split(l:out, '\n')[0] + + " go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH. + " Check it and retun an error if that is the case + if l:importpath[0] ==# '_' + return -1 endif + + return l:importpath endfunction function! go#package#CompleteMembers(package, member) abort - silent! let content = go#util#System('godoc ' . a:package) - if go#util#ShellError() || !len(content) + let [l:content, l:err] = go#util#Exec(['go', 'doc', a:package]) + if l:err || !len(content) return [] endif + let lines = filter(split(content, "\n"),"v:val !~ '^\\s\\+$'") try let mx1 = '^\s\+\(\S+\)\s\+=\s\+.*' @@ -136,37 +196,88 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort return go#package#CompleteMembers(words[1], words[2]) endif - let dirs = go#package#Paths() + let dirs = s:paths() + let module = s:module() - if len(dirs) == 0 - " should not happen - return [] - endif + if len(dirs) == 0 && empty(module) + " should not happen + return [] + endif - let ret = {} - for dir in dirs - " this may expand to multiple lines - let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n") - call add(root, expand(dir . '/src')) - for r in root - for i in split(globpath(r, a:ArgLead.'*'), "\n") - if isdirectory(i) - let i .= '/' - elseif i !~ '\.a$' - continue - endif - let i = substitute(substitute(i[len(r)+1:], '[\\]', '/', 'g'), - \ '\.a$', '', 'g') + let vendordirs = s:vendordirs() - " without this the result can have duplicates in form of - " 'encoding/json' and '/encoding/json/' - let i = go#util#StripPathSep(i) + let ret = {} + for dir in dirs + " this may expand to multiple lines + let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n") + let root = add(root, expand(dir . '/src'), ) + let root = extend(root, vendordirs) + let root = add(root, module) + for item in root + " item may be a dictionary when operating in a module. + if type(item) == type({}) + if empty(item) + continue + endif + let dir = item.dir + let path = item.path + else + let dir = item + let path = item + endif - let ret[i] = i - endfor - endfor + if !empty(module) && dir ==# module.dir + if stridx(a:ArgLead, module.path) == 0 + if len(a:ArgLead) != len(module.path) + let glob = globpath(module.dir, substitute(a:ArgLead, module.path . '/\?', '', '').'*') + else + let glob = module.dir + endif + elseif stridx(module.path, a:ArgLead) == 0 && stridx(module.path, '/', len(a:ArgLead)) < 0 + " use the module directory when a:ArgLead is contained in + " module.path and module.path does not have any path segments after + " a:ArgLead. + let glob = module.dir + else + continue + endif + else + let glob = globpath(dir, a:ArgLead.'*') + endif + for candidate in split(glob) + if isdirectory(candidate) + " TODO(bc): use wildignore instead of filtering out vendor + " directories manually? + if fnamemodify(candidate, ':t') == 'vendor' + continue + endif + let candidate .= '/' + elseif candidate !~ '\.a$' + continue + endif + + if dir !=# path + let candidate = substitute(candidate, '^' . dir, path, 'g') + else + let candidate = candidate[len(dir)+1:] + endif + " replace a backslash with a forward slash and drop .a suffixes + let candidate = substitute(substitute(candidate, '[\\]', '/', 'g'), + \ '\.a$', '', 'g') + + " without this the result can have duplicates in form of + " 'encoding/json' and '/encoding/json/' + let candidate = go#util#StripPathSep(candidate) + + let ret[candidate] = candidate + endfor endfor - return sort(keys(ret)) + endfor + return sort(keys(ret)) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/package_test.vim b/pack/acp/start/vim-go/autoload/go/package_test.vim new file mode 100644 index 0000000..4938d48 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/package_test.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/path.vim b/pack/acp/start/vim-go/autoload/go/path.vim index 49d647c..656afec 100644 --- a/pack/acp/start/vim-go/autoload/go/path.vim +++ b/pack/acp/start/vim-go/autoload/go/path.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " initial_go_path is used to store the initial GOPATH that was set when Vim " was started. It's used with :GoPathClear to restore the GOPATH when the user " changed it explicitly via :GoPath. Initially it's empty. It's being set when @@ -121,13 +125,14 @@ endfunction " BinPath returns the binary path of installed go tools. function! go#path#BinPath() abort - let bin_path = "" + let bin_path = go#config#BinPath() + if bin_path != "" + return bin_path + endif " check if our global custom path is set, if not check if $GOBIN is set so " we can use it, otherwise use default GOPATH - if exists("g:go_bin_path") - let bin_path = g:go_bin_path - elseif $GOBIN != "" + if $GOBIN != "" let bin_path = $GOBIN else let go_paths = split(go#path#Default(), go#util#PathListSep()) @@ -141,11 +146,13 @@ function! go#path#BinPath() abort endfunction " CheckBinPath checks whether the given binary exists or not and returns the -" path of the binary. It returns an empty string doesn't exists. +" path of the binary, respecting the go_bin_path and go_search_bin_path_first +" settings. It returns an empty string if the binary doesn't exist. function! go#path#CheckBinPath(binpath) abort " remove whitespaces if user applied something like 'goimports ' let binpath = substitute(a:binpath, '^\s*\(.\{-}\)\s*$', '\1', '') - " save off original path + + " save original path let old_path = $PATH " check if we have an appropriate bin_path @@ -153,7 +160,12 @@ function! go#path#CheckBinPath(binpath) abort if !empty(go_bin_path) " append our GOBIN and GOPATH paths and be sure they can be found there... " let us search in our GOBIN and GOPATH paths - let $PATH = go_bin_path . go#util#PathListSep() . $PATH + " respect the ordering specified by go_search_bin_path_first + if go#config#SearchBinPathFirst() + let $PATH = go_bin_path . go#util#PathListSep() . $PATH + else + let $PATH = $PATH . go#util#PathListSep() . go_bin_path + endif endif " if it's in PATH just return it @@ -193,4 +205,44 @@ function! s:CygwinPath(path) return substitute(a:path, '\\', '/', "g") endfunction +" go#path#ToURI converts path to a file URI. path should be an absolute path. +" Relative paths cannot be properly converted to a URI; when path is a +" relative path, the file scheme will not be prepended. +function! go#path#ToURI(path) + let l:absolute = !go#util#IsWin() && a:path[0] is# '/' + let l:prefix = '' + let l:path = a:path + + if go#util#IsWin() && l:path[1:2] is# ':\' + let l:absolute = 1 + let l:prefix = '/' . l:path[0:1] + let l:path = l:path[2:] + endif + + return substitute( + \ (l:absolute ? 'file://' : '') . l:prefix . go#uri#EncodePath(l:path), + \ '\\', + \ '/', + \ 'g', + \) +endfunction + +function! go#path#FromURI(uri) abort + let l:i = len('file://') + let l:encoded_path = a:uri[: l:i - 1] is# 'file://' ? a:uri[l:i :] : a:uri + + let l:path = go#uri#Decode(l:encoded_path) + + " If the path is like /C:/foo/bar, it should be C:\foo\bar instead. + if go#util#IsWin() && l:path =~# '^/[a-zA-Z]:' + let l:path = substitute(l:path[1:], '/', '\\', 'g') + endif + + return l:path +endfunction + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/play.vim b/pack/acp/start/vim-go/autoload/go/play.vim index 2cf5009..117af25 100644 --- a/pack/acp/start/vim-go/autoload/go/play.vim +++ b/pack/acp/start/vim-go/autoload/go/play.vim @@ -1,7 +1,6 @@ -if !exists("g:go_play_open_browser") - let g:go_play_open_browser = 1 -endif - +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim function! go#play#Share(count, line1, line2) abort if !executable('curl') @@ -13,15 +12,16 @@ function! go#play#Share(count, line1, line2) abort let share_file = tempname() call writefile(split(content, "\n"), share_file, "b") - let command = "curl -s -X POST https://play.golang.org/share --data-binary '@".share_file."'" - let snippet_id = go#util#System(command) + let l:cmd = ['curl', '-s', '-X', 'POST', 'https://play.golang.org/share', + \ '--data-binary', '@' . l:share_file] + let [l:snippet_id, l:err] = go#util#Exec(l:cmd) " we can remove the temp file because it's now posted. call delete(share_file) - if go#util#ShellError() != 0 - echo 'A error has occurred. Run this command to see what the problem is:' - echo command + if l:err != 0 + echom 'A error has occurred. Run this command to see what the problem is:' + echom go#util#Shelljoin(l:cmd) return endif @@ -34,8 +34,8 @@ function! go#play#Share(count, line1, line2) abort let @+ = url endif - if g:go_play_open_browser != 0 - call go#tool#OpenBrowser(url) + if go#config#PlayOpenBrowser() + call go#util#OpenBrowser(url) endif echo "vim-go: snippet uploaded: ".url @@ -70,4 +70,8 @@ function! s:get_visual_selection() abort return join(lines, "\n") endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/rename.vim b/pack/acp/start/vim-go/autoload/go/rename.vim index 095462f..30ff590 100644 --- a/pack/acp/start/vim-go/autoload/go/rename.vim +++ b/pack/acp/start/vim-go/autoload/go/rename.vim @@ -1,26 +1,14 @@ -if !exists("g:go_gorename_bin") - let g:go_gorename_bin = "gorename" -endif - -" Set the default value. A value of "1" is a shortcut for this, for -" compatibility reasons. -function! s:default() abort - if !exists("g:go_gorename_prefill") || g:go_gorename_prefill == 1 - let g:go_gorename_prefill = 'expand("") =~# "^[A-Z]"' . - \ '? go#util#pascalcase(expand(""))' . - \ ': go#util#camelcase(expand(""))' - endif -endfunction -call s:default() +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim function! go#rename#Rename(bang, ...) abort - call s:default() - let to_identifier = "" if a:0 == 0 let ask = printf("vim-go: rename '%s' to: ", expand("")) - if g:go_gorename_prefill != '' - let to_identifier = input(ask, eval(g:go_gorename_prefill)) + let prefill = go#config#GorenamePrefill() + if prefill != '' + let to_identifier = input(ask, eval(prefill)) else let to_identifier = input(ask) endif @@ -33,7 +21,7 @@ function! go#rename#Rename(bang, ...) abort endif " return with a warning if the bin doesn't exist - let bin_path = go#path#CheckBinPath(g:go_gorename_bin) + let bin_path = go#path#CheckBinPath(go#config#GorenameBin()) if empty(bin_path) return endif @@ -41,22 +29,9 @@ function! go#rename#Rename(bang, ...) abort let fname = expand('%:p') let pos = go#util#OffsetCursor() let offset = printf('%s:#%d', fname, pos) - - " no need to escape for job call - let bin_path = go#util#has_job() ? bin_path : shellescape(bin_path) - let offset = go#util#has_job() ? offset : shellescape(offset) - let to_identifier = go#util#has_job() ? to_identifier : shellescape(to_identifier) - - let cmd = [bin_path, "-offset", offset, "-to", to_identifier] - - " check for any tags - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(cmd, ["-tags", tags]) - endif + let cmd = [bin_path, "-offset", offset, "-to", to_identifier, '-tags', go#config#BuildTags()] if go#util#has_job() - call go#util#EchoProgress(printf("renaming to '%s' ...", to_identifier)) call s:rename_job({ \ 'cmd': cmd, \ 'bang': a:bang, @@ -64,71 +39,42 @@ function! go#rename#Rename(bang, ...) abort return endif - let command = join(cmd, " ") - let out = go#tool#ExecuteInDir(command) - - let splitted = split(out, '\n') - call s:parse_errors(go#util#ShellError(), a:bang, splitted) + let [l:out, l:err] = go#util#ExecInDir(l:cmd) + call s:parse_errors(l:err, a:bang, split(l:out, '\n')) endfunction function s:rename_job(args) - let state = { - \ 'exited': 0, - \ 'closed': 0, - \ 'exitval': 0, - \ 'messages': [], - \ 'status_dir': expand('%:p:h'), - \ 'bang': a:args.bang - \ } - - function! s:callback(chan, msg) dict - call add(self.messages, a:msg) - endfunction - - function! s:exit_cb(job, exitval) dict - let self.exited = 1 - let self.exitval = a:exitval - - let status = { - \ 'desc': 'last status', - \ 'type': "gorename", - \ 'state': "finished", - \ } - - if a:exitval - let status.state = "failed" - endif - - call go#statusline#Update(self.status_dir, status) - - if self.closed - call s:parse_errors(self.exitval, self.bang, self.messages) - endif - endfunction - - function! s:close_cb(ch) dict - let self.closed = 1 - - if self.exited - call s:parse_errors(self.exitval, self.bang, self.messages) - endif - endfunction - - " explicitly bind the callbacks to state so that self within them always - " refers to state. See :help Partial for more information. - let start_options = { - \ 'callback': funcref("s:callback", [], state), - \ 'exit_cb': funcref("s:exit_cb", [], state), - \ 'close_cb': funcref("s:close_cb", [], state), + let l:job_opts = { + \ 'bang': a:args.bang, + \ 'for': 'GoRename', + \ 'statustype': 'gorename', \ } - call go#statusline#Update(state.status_dir, { - \ 'desc': "current status", - \ 'type': "gorename", - \ 'state': "started", - \}) + " autowrite is not enabled for jobs + call go#cmd#autowrite() + let l:cbs = go#job#Options(l:job_opts) - call job_start(a:args.cmd, start_options) + " wrap l:cbs.exit_cb in s:exit_cb. + let l:cbs.exit_cb = funcref('s:exit_cb', [l:cbs.exit_cb]) + + call go#job#Start(a:args.cmd, l:cbs) +endfunction + +function! s:reload_changed() abort + " reload all files to reflect the new changes. We explicitly call + " checktime to trigger a reload of all files. See + " http://www.mail-archive.com/vim@vim.org/msg05900.html for more info + " about the autoread bug + let current_autoread = &autoread + set autoread + silent! checktime + let &autoread = current_autoread +endfunction + +" s:exit_cb reloads any changed buffers and then calls next. +function! s:exit_cb(next, job, exitval) abort + call s:reload_changed() + call call(a:next, [a:job, a:exitval]) endfunction function s:parse_errors(exit_val, bang, out) @@ -143,8 +89,7 @@ function s:parse_errors(exit_val, bang, out) let l:listtype = go#list#Type("GoRename") if a:exit_val != 0 - call go#util#EchoError("FAILED") - let errors = go#tool#ParseErrors(a:out) + let errors = go#util#ParseErrors(a:out) call go#list#Populate(l:listtype, errors, 'Rename') call go#list#Window(l:listtype, len(errors)) if !empty(errors) && !a:bang @@ -163,9 +108,6 @@ function s:parse_errors(exit_val, bang, out) call go#util#EchoSuccess(a:out[0]) " refresh the buffer so we can see the new content - " TODO(arslan): also find all other buffers and refresh them too. For this - " we need a way to get the list of changes from gorename upon an success - " change. silent execute ":e" endfunction @@ -178,4 +120,8 @@ function! go#rename#Complete(lead, cmdline, cursor) \ 'strpart(v:val, 0, len(a:lead)) == a:lead') endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/statusline.vim b/pack/acp/start/vim-go/autoload/go/statusline.vim index 6a4f0bf..f0ec5cd 100644 --- a/pack/acp/start/vim-go/autoload/go/statusline.vim +++ b/pack/acp/start/vim-go/autoload/go/statusline.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " Statusline """""""""""""""""""""""""""""""" @@ -26,7 +30,7 @@ function! go#statusline#Show() abort " lazy initialiation of the cleaner if !s:timer_id " clean every 60 seconds all statuses - let interval = get(g:, 'go_statusline_duration', 60000) + let interval = go#config#StatuslineDuration() let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1}) endif @@ -54,11 +58,11 @@ function! go#statusline#Show() abort " only update highlight if status has changed. if status_text != s:last_status if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass" - hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22 + hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22 guibg=#5fd700 guifg=#005f00 elseif status.state =~ "started" || status.state =~ "analysing" || status.state =~ "compiling" - hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88 + hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88 guibg=#ff8700 guifg=#870000 elseif status.state =~ "failed" - hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52 + hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52 guibg=#ff0000 guifg=#5f0000 endif endif @@ -109,4 +113,8 @@ function! go#statusline#Clear(timer_id) abort exe 'let &ro = &ro' endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/tags.vim b/pack/acp/start/vim-go/autoload/go/tags.vim index af030d2..c1239b0 100644 --- a/pack/acp/start/vim-go/autoload/go/tags.vim +++ b/pack/acp/start/vim-go/autoload/go/tags.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " mapped to :GoAddTags function! go#tags#Add(start, end, count, ...) abort let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') @@ -31,24 +35,22 @@ function! go#tags#run(start, end, offset, mode, fname, test_mode, ...) abort let args["modified"] = 1 endif - let result = s:create_cmd(args) + let l:result = s:create_cmd(args) if has_key(result, 'err') call go#util#EchoError(result.err) return -1 endif - let command = join(result.cmd, " ") - if &modified let filename = expand("%:p:gs!\\!/!") let content = join(go#util#GetLines(), "\n") let in = filename . "\n" . strlen(content) . "\n" . content - let out = go#util#System(command, in) + let [l:out, l:err] = go#util#Exec(l:result.cmd, in) else - let out = go#util#System(command) + let [l:out, l:err] = go#util#Exec(l:result.cmd) endif - if go#util#ShellError() != 0 + if l:err != 0 call go#util#EchoError(out) return endif @@ -115,19 +117,18 @@ func s:create_cmd(args) abort if empty(bin_path) return {'err': "gomodifytags does not exist"} endif - let bin_path = go#util#Shellescape(bin_path) let l:start = a:args.start let l:end = a:args.end let l:offset = a:args.offset let l:mode = a:args.mode let l:cmd_args = a:args.cmd_args - let l:modifytags_transform = get(g:, 'go_addtags_transform', "snakecase") + let l:modifytags_transform = go#config#AddtagsTransform() " start constructing the command let cmd = [bin_path] call extend(cmd, ["-format", "json"]) - call extend(cmd, ["-file", go#util#Shellescape(a:args.fname)]) + call extend(cmd, ["-file", a:args.fname]) call extend(cmd, ["-transform", l:modifytags_transform]) if has_key(a:args, "modified") @@ -162,17 +163,17 @@ func s:create_cmd(args) abort endfor endif - " construct options + " default value + if empty(l:tags) + let l:tags = ["json"] + endif + + " construct tags + call extend(cmd, ["-add-tags", join(l:tags, ",")]) + + " construct options if !empty(l:options) call extend(cmd, ["-add-options", join(l:options, ",")]) - else - " default value - if empty(l:tags) - let l:tags = ["json"] - endif - - " construct tags - call extend(cmd, ["-add-tags", join(l:tags, ",")]) endif elseif l:mode == "remove" if empty(l:cmd_args) @@ -211,4 +212,8 @@ func s:create_cmd(args) abort return {'cmd': cmd} endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/tags_test.vim b/pack/acp/start/vim-go/autoload/go/tags_test.vim index 7d4d32b..17aec41 100644 --- a/pack/acp/start/vim-go/autoload/go/tags_test.vim +++ b/pack/acp/start/vim-go/autoload/go/tags_test.vim @@ -1,4 +1,8 @@ -func! Test_add_tags() abort +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + +func! TestAddTags() abort try let l:tmp = gotest#load_fixture('tags/add_all_input.go') silent call go#tags#run(0, 0, 40, "add", bufname(''), 1) @@ -9,6 +13,28 @@ func! Test_add_tags() abort endfunc +func! TestAddTags_WithOptions() abort + try + let l:tmp = gotest#load_fixture('tags/add_all_input.go') + silent call go#tags#run(0, 0, 40, "add", bufname(''), 1, 'json,omitempty') + call gotest#assert_fixture('tags/add_all_golden_options.go') + finally + call delete(l:tmp, 'rf') + endtry +endfunc + +func! TestAddTags_AddOptions() abort + try + let l:tmp = gotest#load_fixture('tags/add_all_input.go') + silent call go#tags#run(0, 0, 40, "add", bufname(''), 1, 'json') + call gotest#assert_fixture('tags/add_all_golden.go') + silent call go#tags#run(0, 0, 40, "add", bufname(''), 1, 'json,omitempty') + call gotest#assert_fixture('tags/add_all_golden_options.go') + finally + call delete(l:tmp, 'rf') + endtry +endfunc + func! Test_remove_tags() abort try let l:tmp = gotest#load_fixture('tags/remove_all_input.go') @@ -19,4 +45,8 @@ func! Test_remove_tags() abort endtry endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim:ts=2:sts=2:sw=2:et diff --git a/pack/acp/start/vim-go/autoload/go/template.vim b/pack/acp/start/vim-go/autoload/go/template.vim index edb26a6..3f5e538 100644 --- a/pack/acp/start/vim-go/autoload/go/template.vim +++ b/pack/acp/start/vim-go/autoload/go/template.vim @@ -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("") function! go#template#create() abort - let l:go_template_use_pkg = get(g:, 'go_template_use_pkg', 0) + let l:go_template_use_pkg = go#config#TemplateUsePkg() let l:root_dir = fnamemodify(s:current_file, ':h:h:h') - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let dir = getcwd() - let l:package_name = -1 + let l:package_name = go#tool#PackageName() - if isdirectory(expand('%:p:h')) - execute cd . fnameescape(expand('%:p:h')) - let l:package_name = go#tool#PackageName() - endif - - " if we can't figure out any package name(no Go files or non Go package - " files) from the directory create the template or use the cwd - " as the name - if l:package_name == -1 && l:go_template_use_pkg != 1 - let l:filename = fnamemodify(expand("%"), ':t') - if l:filename =~ "_test.go$" - let l:template_file = get(g:, 'go_template_test_file', "hello_world_test.go") + " if we can't figure out any package name (i.e. no Go files in the directory) + " from the directory create the template or use the directory as the name. + if l:package_name == -1 + if l:go_template_use_pkg == 1 + let l:path = fnamemodify(expand('%:p:h'), ':t') + let l:content = printf("package %s", l:path) + call append(0, l:content) else - let l:template_file = get(g:, 'go_template_file', "hello_world.go") + let l:filename = expand('%:t') + if l:filename =~ "_test.go$" + let l:template_file = go#config#TemplateTestFile() + else + let l:template_file = go#config#TemplateFile() + endif + let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file) + silent exe 'keepalt 0r ' . fnameescape(l:template_path) endif - let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file) - silent exe 'keepalt 0r ' . fnameescape(l:template_path) - elseif l:package_name == -1 && l:go_template_use_pkg == 1 - " cwd is now the dir of the package - let l:path = fnamemodify(getcwd(), ':t') - let l:content = printf("package %s", l:path) - call append(0, l:content) else let l:content = printf("package %s", l:package_name) call append(0, l:content) endif - $delete _ - - execute cd . fnameescape(dir) + " checking that the last line is empty shouldn't be necessary, but for some + " reason the last line isn't the expected empty line when run via tests. + if getline('$') is '' + $delete _ + endif endfunction function! go#template#ToggleAutoCreate() abort - if get(g:, "go_template_autocreate", 1) - let g:go_template_autocreate = 0 + if go#config#TemplateAutocreate() + call go#config#SetTemplateAutocreate(0) call go#util#EchoProgress("auto template create disabled") return end - let g:go_template_autocreate = 1 + call go#config#SetTemplateAutocreate(1) call go#util#EchoProgress("auto template create enabled") endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/template_test.vim b/pack/acp/start/vim-go/autoload/go/template_test.vim new file mode 100644 index 0000000..2e02580 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/template_test.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/term.vim b/pack/acp/start/vim-go/autoload/go/term.vim index 1e085a3..d7a463e 100644 --- a/pack/acp/start/vim-go/autoload/go/term.vim +++ b/pack/acp/start/vim-go/autoload/go/term.vim @@ -1,34 +1,36 @@ -if has('nvim') && !exists("g:go_term_mode") - let g:go_term_mode = 'vsplit' -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim " new creates a new terminal with the given command. Mode is set based on the " global variable g:go_term_mode, which is by default set to :vsplit -function! go#term#new(bang, cmd) abort - return go#term#newmode(a:bang, a:cmd, g:go_term_mode) +function! go#term#new(bang, cmd, errorformat) abort + return go#term#newmode(a:bang, a:cmd, a:errorformat, go#config#TermMode()) endfunction -" new creates a new terminal with the given command and window mode. -function! go#term#newmode(bang, cmd, mode) abort - let mode = a:mode - if empty(mode) - let mode = g:go_term_mode +" go#term#newmode creates a new terminal with the given command and window mode. +function! go#term#newmode(bang, cmd, errorformat, mode) abort + let l:mode = a:mode + if empty(l:mode) + let l:mode = go#config#TermMode() endif - let state = { + let l:state = { \ 'cmd': a:cmd, \ 'bang' : a:bang, \ 'winid': win_getid(winnr()), - \ 'stdout': [] + \ 'stdout': [], + \ 'stdout_buf': '', + \ 'errorformat': a:errorformat, \ } " execute go build in the files directory - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let dir = getcwd() + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let l:dir = getcwd() - execute cd . fnameescape(expand("%:p:h")) + execute l:cd . fnameescape(expand("%:p:h")) - execute mode.' __go_term__' + execute l:mode . ' __go_term__' setlocal filetype=goterm setlocal bufhidden=delete @@ -41,79 +43,107 @@ function! go#term#newmode(bang, cmd, mode) abort " " Don't set an on_stderr, because it will be passed the same data as " on_stdout. See https://github.com/neovim/neovim/issues/2836 - let job = { + let l:job = { \ 'on_stdout': function('s:on_stdout', [], state), \ 'on_exit' : function('s:on_exit', [], state), \ } - let state.id = termopen(a:cmd, job) - let state.termwinid = win_getid(winnr()) + let l:state.id = termopen(a:cmd, l:job) + let l:state.termwinid = win_getid(winnr()) - execute cd . fnameescape(dir) + execute l:cd . fnameescape(l:dir) " resize new term if needed. - let height = get(g:, 'go_term_height', winheight(0)) - let width = get(g:, 'go_term_width', winwidth(0)) + let l:height = go#config#TermHeight() + let l:width = go#config#TermWidth() " Adjust the window width or height depending on whether it's a vertical or " horizontal split. - if mode =~ "vertical" || mode =~ "vsplit" || mode =~ "vnew" - exe 'vertical resize ' . width + if l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew" + exe 'vertical resize ' . l:width elseif mode =~ "split" || mode =~ "new" - exe 'resize ' . height + exe 'resize ' . l:height endif " we also need to resize the pty, so there you go... - call jobresize(state.id, width, height) + call jobresize(l:state.id, l:width, l:height) - call win_gotoid(state.winid) + call win_gotoid(l:state.winid) - return state.id + return l:state.id endfunction function! s:on_stdout(job_id, data, event) dict abort - call extend(self.stdout, a:data) + " A single empty string means EOF was reached. The first item will never be + " the empty string except for when it's the only item and is signaling that + " EOF was reached. + if len(a:data) == 1 && a:data[0] == '' + " when there's nothing buffered, return early so that an + " erroneous message will not be added. + if self.stdout_buf == '' + return + endif + + let self.stdout = add(self.stdout, self.stdout_buf) + else + let l:data = copy(a:data) + let l:data[0] = self.stdout_buf . l:data[0] + + " The last element may be a partial line; save it for next time. + let self.stdout_buf = l:data[-1] + let self.stdout = extend(self.stdout, l:data[:-2]) + endif endfunction function! s:on_exit(job_id, exit_status, event) dict abort + let l:winid = win_getid(winnr()) + call win_gotoid(self.winid) let l:listtype = go#list#Type("_term") - " usually there is always output so never branch into this clause - if empty(self.stdout) - call s:cleanlist(self.winid, l:listtype) + if a:exit_status == 0 + call go#list#Clean(l:listtype) + call win_gotoid(l:winid) return endif - let errors = go#tool#ParseErrors(self.stdout) - let errors = go#tool#FilterValids(errors) + call win_gotoid(self.winid) - if !empty(errors) - " close terminal; we don't need it anymore - call win_gotoid(self.termwinid) - close + let l:title = self.cmd + if type(l:title) == v:t_list + let l:title = join(self.cmd) + endif - call win_gotoid(self.winid) + let l:i = 0 + while l:i < len(self.stdout) + let self.stdout[l:i] = substitute(self.stdout[l:i], "\r$", '', 'g') + let l:i += 1 + endwhile - call go#list#Populate(l:listtype, errors, self.cmd) - call go#list#Window(l:listtype, len(errors)) - if !self.bang - call go#list#JumpToFirst(l:listtype) - endif + call go#list#ParseFormat(l:listtype, self.errorformat, self.stdout, l:title) + let l:errors = go#list#Get(l:listtype) + call go#list#Window(l:listtype, len(l:errors)) + if empty(l:errors) + call go#util#EchoError( '[' . l:title . '] ' . "FAIL") + call win_gotoid(l:winid) return endif - call s:cleanlist(self.winid, l:listtype) + " close terminal; we don't need it anymore + call win_gotoid(self.termwinid) + close! + + if self.bang + call win_gotoid(l:winid) + return + endif + + call win_gotoid(self.winid) + call go#list#JumpToFirst(l:listtype) endfunction -function! s:cleanlist(winid, listtype) abort - " There are no errors. Clean and close the list. Jump to the window to which - " the location list is attached, close the list, and then jump back to the - " current window. - let winid = win_getid(winnr()) - call win_gotoid(a:winid) - call go#list#Clean(a:listtype) - call win_gotoid(l:winid) -endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/term_test.vim b/pack/acp/start/vim-go/autoload/go/term_test.vim index 7a630ee..40f0a52 100644 --- a/pack/acp/start/vim-go/autoload/go/term_test.vim +++ b/pack/acp/start/vim-go/autoload/go/term_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_GoTermNewMode() if !has('nvim') return @@ -13,7 +17,7 @@ func! Test_GoTermNewMode() let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) set nosplitright - call go#term#newmode(0, cmd, '') + call go#term#new(0, cmd, &errorformat) let actual = expand('%:p') call assert_equal(actual, l:expected) @@ -37,7 +41,7 @@ func! Test_GoTermNewMode_SplitRight() let cmd = "go run ". go#util#Shelljoin(go#tool#Files()) set splitright - call go#term#newmode(0, cmd, '') + call go#term#new(0, cmd, &errorformat) let actual = expand('%:p') call assert_equal(actual, l:expected) @@ -47,4 +51,8 @@ func! Test_GoTermNewMode_SplitRight() endtry endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/complete/complete.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/complete/complete.go new file mode 100644 index 0000000..f25063f --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/complete/complete.go @@ -0,0 +1,9 @@ +package complete + +type T struct { + V string +} + +func Example(s string) { + Example("") +} diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/debug/compilerror/main.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/debug/compilerror/main.go new file mode 100644 index 0000000..3f3dc7c --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/debug/compilerror/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go" +} diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/debug/debugmain/debugmain.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/debug/debugmain/debugmain.go new file mode 100644 index 0000000..50e8d8d --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/debug/debugmain/debugmain.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go") +} diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports deleted file mode 120000 index 60ee253..0000000 --- a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports +++ /dev/null @@ -1 +0,0 @@ -../imports/ \ No newline at end of file diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports new file mode 100644 index 0000000..60ee253 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/fmt/src/imports @@ -0,0 +1 @@ +../imports/ \ No newline at end of file diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/bar/.gitkeep b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/bar/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/baz/.gitkeep b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/baz/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/go.mod b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/go.mod new file mode 100644 index 0000000..d414eac --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/go.mod @@ -0,0 +1,3 @@ +module package + +go 1.12 diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/package.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/package.go new file mode 100644 index 0000000..50e8d8d --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/package.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go") +} diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/vendor/foo/.gitkeep b/pack/acp/start/vim-go/autoload/go/test-fixtures/package/src/package/vendor/foo/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/tags/add_all_golden_options.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/tags/add_all_golden_options.go new file mode 100644 index 0000000..86d296b --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/tags/add_all_golden_options.go @@ -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"` +} diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/testcompilerror/testcompilerror_test.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/testcompilerror/testcompilerror_test.go new file mode 100644 index 0000000..75fdade --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/testcompilerror/testcompilerror_test.go @@ -0,0 +1,11 @@ +package main + +import ( + "io/ioutil" + "testing" +) + +func TestSomething(t *testing.T) { + r := struct{}{} + ioutil.ReadAll(r) +} diff --git a/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/veterror/veterror.go b/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/veterror/veterror.go new file mode 100644 index 0000000..8c585f2 --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/test-fixtures/test/src/veterror/veterror.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Errorf("%v") +} diff --git a/pack/acp/start/vim-go/autoload/go/test.vim b/pack/acp/start/vim-go/autoload/go/test.vim index c62f508..f33d7d9 100644 --- a/pack/acp/start/vim-go/autoload/go/test.vim +++ b/pack/acp/start/vim-go/autoload/go/test.vim @@ -1,8 +1,12 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " Test runs `go test` in the current directory. If compile is true, it'll " compile the tests instead of running them (useful to catch errors in the " test files). Any other argument is appended to the final `go test` command. function! go#test#Test(bang, compile, ...) abort - let args = ["test"] + let args = ["test", '-tags', go#config#BuildTags()] " don't run the test, only compile it. Useful to capture and fix errors. if a:compile @@ -10,11 +14,6 @@ function! go#test#Test(bang, compile, ...) abort call extend(args, ["-c", "-o", testfile]) endif - if exists('g:go_build_tags') - let tags = get(g:, 'go_build_tags') - call extend(args, ["-tags", tags]) - endif - if a:0 let goargs = a:000 @@ -24,18 +23,35 @@ function! go#test#Test(bang, compile, ...) abort let goargs = map(copy(a:000), "expand(v:val)") endif - if !(has('nvim') || go#util#has_job()) - let goargs = go#util#Shelllist(goargs, 1) - endif - call extend(args, goargs, 1) else " only add this if no custom flags are passed - let timeout = get(g:, 'go_test_timeout', '10s') + let timeout = go#config#TestTimeout() call add(args, printf("-timeout=%s", timeout)) endif - if get(g:, 'go_echo_command_info', 1) + if go#config#TermEnabled() + call go#term#new(a:bang, ["go"] + args, s:errorformat()) + endif + + if go#util#has_job() + " use vim's job functionality to call it asynchronously + let job_options = { + \ 'bang': a:bang, + \ 'for': 'GoTest', + \ 'statustype': 'test', + \ 'errorformat': s:errorformat(), + \ } + + if a:compile + let job_options.statustype = 'compile ' . job_options.statustype + endif + + call s:test_job(['go'] + args, job_options) + return + endif + + if go#config#EchoCommandInfo() if a:compile call go#util#EchoProgress("compiling tests ...") else @@ -43,35 +59,12 @@ function! go#test#Test(bang, compile, ...) abort endif endif - if go#util#has_job() - " use vim's job functionality to call it asynchronously - let job_args = { - \ 'cmd': ['go'] + args, - \ 'bang': a:bang, - \ 'winnr': winnr(), - \ 'dir': getcwd(), - \ 'compile_test': a:compile, - \ 'jobdir': fnameescape(expand("%:p:h")), - \ } - - call s:test_job(job_args) - return - elseif has('nvim') - " use nvims's job functionality - if get(g:, 'go_term_enabled', 0) - let id = go#term#new(a:bang, ["go"] + args) - else - let id = go#jobcontrol#Spawn(a:bang, "test", "GoTest", args) - endif - - return id - endif - call go#cmd#autowrite() redraw - let command = "go " . join(args, ' ') - let out = go#tool#ExecuteInDir(command) + let l:cmd = ['go'] + l:args + + let [l:out, l:err] = go#util#ExecInDir(l:cmd) " TODO(bc): When the output is JSON, the JSON should be run through a " filter to produce lines that are more easily described by errorformat. @@ -81,17 +74,19 @@ function! go#test#Test(bang, compile, ...) abort let dir = getcwd() execute cd fnameescape(expand("%:p:h")) - if go#util#ShellError() != 0 - call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), command) + if l:err != 0 + let l:winid = win_getid(winnr()) + call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), l:cmd) let errors = go#list#Get(l:listtype) call go#list#Window(l:listtype, len(errors)) - if !empty(errors) && !a:bang - call go#list#JumpToFirst(l:listtype) - elseif empty(errors) + if empty(errors) " failed to parse errors, output the original content call go#util#EchoError(out) + elseif a:bang + call win_gotoid(l:winid) + else + call go#list#JumpToFirst(l:listtype) endif - call go#util#EchoError("[test] FAIL") else call go#list#Clean(l:listtype) @@ -130,156 +125,22 @@ function! go#test#Func(bang, ...) abort call extend(args, a:000) else " only add this if no custom flags are passed - let timeout = get(g:, 'go_test_timeout', '10s') + let timeout = go#config#TestTimeout() call add(args, printf("-timeout=%s", timeout)) endif call call('go#test#Test', args) endfunction -function! s:test_job(args) abort - let status = { - \ 'desc': 'current status', - \ 'type': "test", - \ 'state': "started", - \ } - - if a:args.compile_test - let status.state = "compiling" - endif - +function! s:test_job(cmd, args) abort " autowrite is not enabled for jobs call go#cmd#autowrite() - let state = { - \ 'exited': 0, - \ 'closed': 0, - \ 'exitval': 0, - \ 'messages': [], - \ 'args': a:args, - \ 'compile_test': a:args.compile_test, - \ 'status_dir': expand('%:p:h'), - \ 'started_at': reltime() - \ } - - call go#statusline#Update(state.status_dir, status) - - function! s:callback(chan, msg) dict - call add(self.messages, a:msg) - endfunction - - function! s:exit_cb(job, exitval) dict - let self.exited = 1 - let self.exitval = a:exitval - - let status = { - \ 'desc': 'last status', - \ 'type': "test", - \ 'state': "pass", - \ } - - if self.compile_test - let status.state = "success" - endif - - if a:exitval - let status.state = "failed" - endif - - if get(g:, 'go_echo_command_info', 1) - if a:exitval == 0 - if self.compile_test - call go#util#EchoSuccess("[test] SUCCESS") - else - call go#util#EchoSuccess("[test] PASS") - endif - else - call go#util#EchoError("[test] FAIL") - endif - endif - - let elapsed_time = reltimestr(reltime(self.started_at)) - " strip whitespace - let elapsed_time = substitute(elapsed_time, '^\s*\(.\{-}\)\s*$', '\1', '') - let status.state .= printf(" (%ss)", elapsed_time) - - call go#statusline#Update(self.status_dir, status) - - if self.closed - call s:show_errors(self.args, self.exitval, self.messages) - endif - endfunction - - function! s:close_cb(ch) dict - let self.closed = 1 - - if self.exited - call s:show_errors(self.args, self.exitval, self.messages) - endif - endfunction - - " explicitly bind the callbacks to state so that self within them always - " refers to state. See :help Partial for more information. - let start_options = { - \ 'callback': funcref("s:callback", [], state), - \ 'exit_cb': funcref("s:exit_cb", [], state), - \ 'close_cb': funcref("s:close_cb", [], state) - \ } - - " pre start - let dir = getcwd() - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let jobdir = fnameescape(expand("%:p:h")) - execute cd . jobdir - - call job_start(a:args.cmd, start_options) - - " post start - execute cd . fnameescape(dir) + call go#job#Spawn(a:cmd, a:args) endfunction -" show_errors parses the given list of lines of a 'go test' output and returns -" a quickfix compatible list of errors. It's intended to be used only for go -" test output. -function! s:show_errors(args, exit_val, messages) abort - let l:listtype = go#list#Type("GoTest") - if a:exit_val == 0 - call go#list#Clean(l:listtype) - return - endif - - " TODO(bc): When messages is JSON, the JSON should be run through a - " filter to produce lines that are more easily described by errorformat. - - let l:listtype = go#list#Type("GoTest") - - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - try - execute cd a:args.jobdir - call go#list#ParseFormat(l:listtype, s:errorformat(), a:messages, join(a:args.cmd)) - let errors = go#list#Get(l:listtype) - finally - execute cd . fnameescape(a:args.dir) - endtry - - if !len(errors) - " failed to parse errors, output the original content - call go#util#EchoError(a:messages) - call go#util#EchoError(a:args.dir) - return - endif - - if a:args.winnr == winnr() - call go#list#Window(l:listtype, len(errors)) - if !empty(errors) && !a:args.bang - call go#list#JumpToFirst(l:listtype) - endif - endif -endfunction - - -let s:efm= "" -let s:go_test_show_name=0 +let s:efm = "" +let s:go_test_show_name = 0 function! s:errorformat() abort " NOTE(arslan): once we get JSON output everything will be easier :). @@ -288,7 +149,7 @@ function! s:errorformat() abort " https://github.com/golang/go/issues/2981. let goroot = go#util#goroot() - let show_name=get(g:, 'go_test_show_name', 0) + let show_name = go#config#TestShowName() if s:efm != "" && s:go_test_show_name == show_name return s:efm endif @@ -297,15 +158,12 @@ function! s:errorformat() abort " each level of test indents the test output 4 spaces. Capturing groups " (e.g. \(\)) cannot be used in an errorformat, but non-capturing groups can " (e.g. \%(\)). - let indent = '%\\%( %\\)%#' - - " match compiler errors - let format = "%f:%l:%c: %m" + let indent = '%\\%( %\\)' " ignore `go test -v` output for starting tests - let format .= ",%-G=== RUN %.%#" + let format = "%-G=== RUN %.%#" " ignore `go test -v` output for passing tests - let format .= ",%-G" . indent . "--- PASS: %.%#" + let format .= ",%-G" . indent . "%#--- PASS: %.%#" " Match failure lines. " @@ -315,24 +173,25 @@ function! s:errorformat() abort " e.g.: " '--- FAIL: TestSomething (0.00s)' if show_name - let format .= ",%G" . indent . "--- FAIL: %m (%.%#)" + let format .= ",%G" . indent . "%#--- FAIL: %m (%.%#)" else - let format .= ",%-G" . indent . "--- FAIL: %.%#" + let format .= ",%-G" . indent . "%#--- FAIL: %.%#" endif + " Go 1.10 test output {{{1 " Matches test output lines. " " All test output lines start with the test indentation and a tab, followed " by the filename, a colon, the line number, another colon, a space, and the " message. e.g.: " '\ttime_test.go:30: Likely problem: the time zone files have not been installed.' - let format .= ",%A" . indent . "%\\t%\\+%f:%l: %m" + let format .= ",%A" . indent . "%#%\\t%\\+%f:%l: %m" " also match lines that don't have a message (i.e. the message begins with a " newline or is the empty string): " e.g.: " t.Errorf("\ngot %v; want %v", actual, expected) " t.Error("") - let format .= ",%A" . indent . "%\\t%\\+%f:%l: " + let format .= ",%A" . indent . "%#%\\t%\\+%f:%l: " " Match the 2nd and later lines of multi-line output. These lines are " indented the number of spaces for the level of nesting of the test, @@ -345,7 +204,17 @@ function! s:errorformat() abort " indicate that they're multiple lines of output, but in that case the lines " get concatenated in the quickfix list, which is not what users typically " want when writing a newline into their test output. - let format .= ",%G" . indent . "%\\t%\\{2}%m" + let format .= ",%G" . indent . "%#%\\t%\\{2}%m" + " }}}1 + + " Go 1.11 test output {{{1 + " Match test output lines similarly to Go 1.10 test output lines, but they + " use an indent level where the Go 1.10 test output uses tabs, so they'll + " always have at least one level indentation... + let format .= ",%A" . indent . "%\\+%f:%l: %m" + let format .= ",%A" . indent . "%\\+%f:%l: " + let format .= ",%G" . indent . "%\\{2\\,}%m" + " }}}1 " set the format for panics. @@ -375,7 +244,7 @@ function! s:errorformat() abort " e.g.: " '\t/usr/local/go/src/time.go:1313 +0x5d' - " panicaddress, and readyaddress are identical except for + " panicaddress and readyaddress are identical except for " panicaddress sets the filename and line number. let panicaddress = "%\\t%f:%l +0x%[0-9A-Fa-f]%\\+" let readyaddress = "%\\t%\\f%\\+:%\\d%\\+ +0x%[0-9A-Fa-f]%\\+" @@ -398,6 +267,11 @@ function! s:errorformat() abort " the running goroutine's stack. let format .= ",%Z" . panicaddress + " Match and ignore errors from runtime.goparkunlock(). These started + " appearing in stack traces from Go 1.12 test timeouts. + let format .= ",%-Gruntime.goparkunlock(%.%#" + let format .= ",%-G%\\t" . goroot . "%\\f%\\+:%\\d%\\+" + " Match and ignore panic address without being part of a multi-line message. " This is to catch those lines that come after the top most non-standard " library line in stack traces. @@ -409,12 +283,26 @@ function! s:errorformat() abort let format .= ",%-Cexit status %[0-9]%\\+" "let format .= ",exit status %[0-9]%\\+" - " Match and ignore exit failure lines whether part of a multi-line message + " Match and ignore failure lines whether part of a multi-line message " or not, because these lines sometimes come before and sometimes after " panic stacktraces. let format .= ",%-CFAIL%\\t%.%#" "let format .= ",FAIL%\\t%.%#" + " match compiler errors. + " These are very smilar to errors from <=go1.10 test output, but lack + " leading tabs for the first line of an error, and subsequent lines only + " have one tab instead of two. + let format .= ",%A%f:%l:%c: %m" + let format .= ",%A%f:%l: %m" + " It would be nice if this weren't necessary, but panic lines from tests are + " prefixed with a single leading tab, making them very similar to 2nd and + " later lines of a multi-line compiler error. Swallow it so that it doesn't + " cause a quickfix entry since the next %G entry can add a quickfix entry + " for 2nd and later lines of a multi-line compiler error. + let format .= ",%-C%\\tpanic: %.%#" + let format .= ",%G%\\t%m" + " Match and ignore everything else in multi-line messages. let format .= ",%-C%.%#" " Match and ignore everything else not in a multi-line message: @@ -425,4 +313,8 @@ function! s:errorformat() abort return s:efm endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/test_test.vim b/pack/acp/start/vim-go/autoload/go/test_test.vim index d0abc3c..28d7993 100644 --- a/pack/acp/start/vim-go/autoload/go/test_test.vim +++ b/pack/acp/start/vim-go/autoload/go/test_test.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_GoTest() abort let expected = [ \ {'lnum': 12, 'bufnr': 2, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'log message'}, @@ -62,26 +66,33 @@ endfunc func! Test_GoTestShowName() abort let expected = [ \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'}, - \ {'lnum': 6, 'bufnr': 9, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'}, + \ {'lnum': 6, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld/sub'}, - \ {'lnum': 9, 'bufnr': 9, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'}, + \ {'lnum': 9, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'}, \ ] let g:go_test_show_name=1 call s:test('showname/showname_test.go', expected) - let g:go_test_show_name=0 + unlet g:go_test_show_name +endfunc + +func! Test_GoTestVet() abort + let expected = [ + \ {'lnum': 6, 'bufnr': 10, 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'Errorf format %v reads arg #1, but call has 0 args'}, + \ ] + call s:test('veterror/veterror.go', expected) +endfunc + +func! Test_GoTestTestCompilerError() abort + let expected = [ + \ {'lnum': 10, 'bufnr': 8, 'col': 16, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'cannot use r (type struct {}) as type io.Reader in argument to ioutil.ReadAll:'}, + \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'struct {} does not implement io.Reader (missing Read method)'} + \ ] + + call s:test('testcompilerror/testcompilerror_test.go', expected) endfunc func! s:test(file, expected, ...) abort - if has('nvim') - " nvim mostly shows test errors correctly, but the the expected errors are - " slightly different; buffer numbers are not the same and stderr doesn't - " seem to be redirected to the job, so the lines from the panic aren't in - " the output to be parsed, and hence are not in the quickfix lists. Once - " those two issues are resolved, this early return should be removed so - " the tests will run for Neovim, too. - return - endif let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test' silent exe 'e ' . $GOPATH . '/src/' . a:file @@ -94,7 +105,7 @@ func! s:test(file, expected, ...) abort endif " run the tests - call call(function('go#test#Test'), args) + silent call call(function('go#test#Test'), args) let actual = getqflist() let start = reltime() @@ -118,4 +129,8 @@ func! s:normalize_durations(str) abort return substitute(a:str, '[0-9]\+\(\.[0-9]\+\)\?s', '0.000s', 'g') endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/textobj.vim b/pack/acp/start/vim-go/autoload/go/textobj.vim index e8a23d3..9a38c95 100644 --- a/pack/acp/start/vim-go/autoload/go/textobj.vim +++ b/pack/acp/start/vim-go/autoload/go/textobj.vim @@ -1,48 +1,132 @@ -if !exists("g:go_textobj_enabled") - let g:go_textobj_enabled = 1 -endif - -if !exists("g:go_textobj_include_function_doc") - let g:go_textobj_include_function_doc = 1 -endif - -if !exists("g:go_textobj_include_variable") - let g:go_textobj_include_variable = 1 -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim " ( ) motions " { } motions " s for sentence -" p for parapgrah +" p for paragraph " < > " t for tag -" Select a function in visual mode. -function! go#textobj#Function(mode) abort - let offset = go#util#OffsetCursor() +function! go#textobj#Comment(mode) abort + let l:fname = expand('%:p') - let fname = shellescape(expand("%:p")) - if &modified - " Write current unsaved buffer to a temp file and use the modified content - let l:tmpname = tempname() - call writefile(go#util#GetLines(), l:tmpname) - let fname = l:tmpname - endif + try + if &modified + let l:tmpname = tempname() + call writefile(go#util#GetLines(), l:tmpname) + let l:fname = l:tmpname + endif - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) + let l:cmd = ['motion', + \ '-format', 'json', + \ '-file', l:fname, + \ '-offset', go#util#OffsetCursor(), + \ '-mode', 'comment', + \ ] + + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err + call go#util#EchoError(l:out) + return + endif + finally + if exists("l:tmpname") + call delete(l:tmpname) + endif + endtry + + let l:result = json_decode(l:out) + if type(l:result) != 4 || !has_key(l:result, 'comment') return endif - let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) - let command .= " -mode enclosing" + let l:info = l:result.comment + call cursor(l:info.startLine, l:info.startCol) - if g:go_textobj_include_function_doc - let command .= " -parse-comments" + " Adjust cursor to exclude start comment markers. Try to be a little bit + " clever when using multi-line '/*' markers. + if a:mode is# 'i' + " trim whitespace so matching below works correctly + let l:line = substitute(getline('.'), '^\s*\(.\{-}\)\s*$', '\1', '') + + " //text + if l:line[:2] is# '// ' + call cursor(l:info.startLine, l:info.startCol+3) + " // text + elseif l:line[:1] is# '//' + call cursor(l:info.startLine, l:info.startCol+2) + " /* + " text + elseif l:line =~# '^/\* *$' + call cursor(l:info.startLine+1, 0) + " /* + " * text + if getline('.')[:2] is# ' * ' + call cursor(l:info.startLine+1, 4) + " /* + " *text + elseif getline('.')[:1] is# ' *' + call cursor(l:info.startLine+1, 3) + endif + " /* text + elseif l:line[:2] is# '/* ' + call cursor(l:info.startLine, l:info.startCol+3) + " /*text + elseif l:line[:1] is# '/*' + call cursor(l:info.startLine, l:info.startCol+2) + endif endif - let out = go#util#System(command) - if go#util#ShellError() != 0 + normal! v + + " Exclude trailing newline. + if a:mode is# 'i' + let l:info.endCol -= 1 + endif + + call cursor(l:info.endLine, l:info.endCol) + + " Exclude trailing '*/'. + if a:mode is# 'i' + let l:line = getline('.') + " text + " */ + if l:line =~# '^ *\*/$' + call cursor(l:info.endLine - 1, len(getline(l:info.endLine - 1))) + " text */ + elseif l:line[-3:] is# ' */' + call cursor(l:info.endLine, l:info.endCol - 3) + " text*/ + elseif l:line[-2:] is# '*/' + call cursor(l:info.endLine, l:info.endCol - 2) + endif + endif +endfunction + +" Select a function in visual mode. +function! go#textobj#Function(mode) abort + let l:fname = expand("%:p") + if &modified + let l:tmpname = tempname() + call writefile(go#util#GetLines(), l:tmpname) + let l:fname = l:tmpname + endif + + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-file', l:fname, + \ '-offset', go#util#OffsetCursor(), + \ '-mode', 'enclosing', + \ ] + + if go#config#TextobjIncludeFunctionDoc() + let l:cmd += ['-parse-comments'] + endif + + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err call go#util#EchoError(out) return endif @@ -63,9 +147,9 @@ function! go#textobj#Function(mode) abort if a:mode == 'a' " anonymous functions doesn't have associated doc. Also check if the user " want's to include doc comments for function declarations - if has_key(info, 'doc') && g:go_textobj_include_function_doc + if has_key(info, 'doc') && go#config#TextobjIncludeFunctionDoc() call cursor(info.doc.line, info.doc.col) - elseif info['sig']['name'] == '' && g:go_textobj_include_variable + elseif info['sig']['name'] == '' && go#config#TextobjIncludeVariable() " one liner anonymous functions if info.lbrace.line == info.rbrace.line " jump to first nonblack char, to get the correct column @@ -101,36 +185,28 @@ endfunction " Get the location of the previous or next function. function! go#textobj#FunctionLocation(direction, cnt) abort - let offset = go#util#OffsetCursor() - - let fname = shellescape(expand("%:p")) + let l:fname = expand("%:p") if &modified " Write current unsaved buffer to a temp file and use the modified content let l:tmpname = tempname() call writefile(go#util#GetLines(), l:tmpname) - let fname = l:tmpname + let l:fname = l:tmpname endif - let bin_path = go#path#CheckBinPath('motion') - if empty(bin_path) - return + let l:cmd = ['motion', + \ '-format', 'vim', + \ '-file', l:fname, + \ '-offset', go#util#OffsetCursor(), + \ '-shift', a:cnt, + \ '-mode', a:direction, + \ ] + + if go#config#TextobjIncludeFunctionDoc() + let l:cmd += ['-parse-comments'] endif - let command = printf("%s -format vim -file %s -offset %s", bin_path, fname, offset) - let command .= ' -shift ' . a:cnt - - if a:direction == 'next' - let command .= ' -mode next' - else " 'prev' - let command .= ' -mode prev' - endif - - if g:go_textobj_include_function_doc - let command .= " -parse-comments" - endif - - let out = go#util#System(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#Exec(l:cmd) + if l:err call go#util#EchoError(out) return endif @@ -190,7 +266,7 @@ function! go#textobj#FunctionJump(mode, direction) abort endif if a:mode == 'v' && a:direction == 'prev' - if has_key(info, 'doc') && g:go_textobj_include_function_doc + if has_key(info, 'doc') && go#config#TextobjIncludeFunctionDoc() keepjumps call cursor(info.doc.line, 1) else keepjumps call cursor(info.func.line, 1) @@ -201,4 +277,8 @@ function! go#textobj#FunctionJump(mode, direction) abort keepjumps call cursor(info.func.line, 1) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/tool.vim b/pack/acp/start/vim-go/autoload/go/tool.vim index d163600..fdeaaf1 100644 --- a/pack/acp/start/vim-go/autoload/go/tool.vim +++ b/pack/acp/start/vim-go/autoload/go/tool.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " From "go list -h". function! go#tool#ValidFiles(...) let l:list = ["GoFiles", "CgoFiles", "IgnoredGoFiles", "CFiles", "CXXFiles", @@ -36,8 +40,8 @@ function! go#tool#Files(...) abort endif endfor - let out = go#tool#ExecuteInDir('go list -f ' . shellescape(combined)) - return split(out, '\n') + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:combined]) + return split(l:out, '\n') endfunction function! go#tool#Deps() abort @@ -46,9 +50,8 @@ function! go#tool#Deps() abort else let format = "{{range $f := .Deps}}{{$f}}\n{{end}}" endif - let command = 'go list -f '.shellescape(format) - let out = go#tool#ExecuteInDir(command) - return split(out, '\n') + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format]) + return split(l:out, '\n') endfunction function! go#tool#Imports() abort @@ -58,188 +61,79 @@ function! go#tool#Imports() abort else let format = "{{range $f := .Imports}}{{$f}}{{printf \"\\n\"}}{{end}}" endif - let command = 'go list -f '.shellescape(format) - let out = go#tool#ExecuteInDir(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', l:format]) + if l:err != 0 echo out return imports endif for package_path in split(out, '\n') - let cmd = "go list -f '{{.Name}}' " . shellescape(package_path) - let package_name = substitute(go#tool#ExecuteInDir(cmd), '\n$', '', '') + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}', l:package_path]) + if l:err != 0 + echo out + return imports + endif + let package_name = substitute(l:out, '\n$', '', '') let imports[package_name] = package_path endfor return imports endfunction -function! go#tool#Info(auto) abort - let l:mode = get(g:, 'go_info_mode', 'gocode') +function! go#tool#Info(showstatus) abort + let l:mode = go#config#InfoMode() if l:mode == 'gocode' - call go#complete#Info(a:auto) + call go#complete#Info(a:showstatus) elseif l:mode == 'guru' - call go#guru#DescribeInfo() + call go#guru#DescribeInfo(a:showstatus) + elseif l:mode == 'gopls' + call go#lsp#Info(a:showstatus) else - call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [gocode, guru]') + call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [gocode, guru, gopls]') endif endfunction function! go#tool#PackageName() abort - let command = "go list -f \"{{.Name}}\"" - let out = go#tool#ExecuteInDir(command) - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', '-tags', go#config#BuildTags(), '-f', '{{.Name}}']) + if l:err != 0 return -1 endif return split(out, '\n')[0] endfunction -function! go#tool#ParseErrors(lines) abort - let errors = [] - - for line in a:lines - let fatalerrors = matchlist(line, '^\(fatal error:.*\)$') - let tokens = matchlist(line, '^\s*\(.\{-}\):\(\d\+\):\s*\(.*\)') - - if !empty(fatalerrors) - call add(errors, {"text": fatalerrors[1]}) - elseif !empty(tokens) - " strip endlines of form ^M - let out = substitute(tokens[3], '\r$', '', '') - - call add(errors, { - \ "filename" : fnamemodify(tokens[1], ':p'), - \ "lnum" : tokens[2], - \ "text" : out, - \ }) - elseif !empty(errors) - " Preserve indented lines. - " This comes up especially with multi-line test output. - if match(line, '^\s') >= 0 - call add(errors, {"text": line}) - endif - endif - endfor - - return errors -endfunction - -"FilterValids filters the given items with only items that have a valid -"filename. Any non valid filename is filtered out. -function! go#tool#FilterValids(items) abort - " Remove any nonvalid filename from the location list to avoid opening an - " empty buffer. See https://github.com/fatih/vim-go/issues/287 for - " details. - let filtered = [] - let is_readable = {} - - for item in a:items - if has_key(item, 'bufnr') - let filename = bufname(item.bufnr) - elseif has_key(item, 'filename') - let filename = item.filename - else - " nothing to do, add item back to the list - call add(filtered, item) - continue - endif - - if !has_key(is_readable, filename) - let is_readable[filename] = filereadable(filename) - endif - if is_readable[filename] - call add(filtered, item) - endif - endfor - - for k in keys(filter(is_readable, '!v:val')) - echo "vim-go: " | echohl Identifier | echon "[run] Dropped " | echohl Constant | echon '"' . k . '"' - echohl Identifier | echon " from location list (nonvalid filename)" | echohl None - endfor - - return filtered -endfunction - -function! go#tool#ExecuteInDir(cmd) abort - " Verify that the directory actually exists. If the directory does not - " exist, then assume that the a:cmd should not be executed. Callers expect - " to check v:shell_error (via go#util#ShellError()), so execute a command - " that will return an error as if a:cmd was run and exited with an error. - " This helps avoid errors when working with plugins that use virtual files - " that don't actually exist on the file system (e.g. vim-fugitive's - " GitDiff). - if !isdirectory(expand("%:p:h")) - let [out, err] = go#util#Exec(["false"]) - return '' - endif - - let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' - let dir = getcwd() - try - execute cd . fnameescape(expand("%:p:h")) - let out = go#util#System(a:cmd) - finally - execute cd . fnameescape(dir) - endtry - return out -endfunction - " Exists checks whether the given importpath exists or not. It returns 0 if " the importpath exists under GOPATH. function! go#tool#Exists(importpath) abort - let command = "go list ". a:importpath - let out = go#tool#ExecuteInDir(command) - - if go#util#ShellError() != 0 + let [l:out, l:err] = go#util#ExecInDir(['go', 'list', a:importpath]) + if l:err != 0 return -1 endif return 0 endfunction -" following two functions are from: https://github.com/mattn/gist-vim -" thanks @mattn -function! s:get_browser_command() abort - let go_play_browser_command = get(g:, 'go_play_browser_command', '') - if go_play_browser_command == '' - if go#util#IsWin() - let go_play_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%' - elseif go#util#IsMac() - let go_play_browser_command = 'open %URL%' - elseif executable('xdg-open') - let go_play_browser_command = 'xdg-open %URL%' - elseif executable('firefox') - let go_play_browser_command = 'firefox %URL% &' - elseif executable('chromium') - let go_play_browser_command = 'chromium %URL% &' - else - let go_play_browser_command = '' - endif - endif - return go_play_browser_command +function! go#tool#DescribeBalloon() + let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p') + call go#lsp#Hover(l:fname, v:beval_lnum, v:beval_col, funcref('s:balloon', [])) + return '' endfunction -function! go#tool#OpenBrowser(url) abort - let cmd = s:get_browser_command() - if len(cmd) == 0 - redraw - echohl WarningMsg - echo "It seems that you don't have general web browser. Open URL below." - echohl None - echo a:url - return - endif - if cmd =~ '^!' - let cmd = substitute(cmd, '%URL%', '\=escape(shellescape(a:url),"#")', 'g') - silent! exec cmd - elseif cmd =~ '^:[A-Z]' - let cmd = substitute(cmd, '%URL%', '\=escape(a:url,"#")', 'g') - exec cmd +function! s:balloon(msg) + let l:msg = a:msg + if has('balloon_eval') + if has('balloon_multiline') + let l:msg = join(a:msg, "\n") else - let cmd = substitute(cmd, '%URL%', '\=shellescape(a:url)', 'g') - call go#util#System(cmd) + let l:msg = substitute(join(map(deepcopy(a:msg), 'substitute(v:val, "\t", "", "")'), '; '), '{;', '{', '') endif + endif + + call balloon_show(l:msg) endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/tool_test.vim b/pack/acp/start/vim-go/autoload/go/tool_test.vim index 1af3b7b..42b8630 100644 --- a/pack/acp/start/vim-go/autoload/go/tool_test.vim +++ b/pack/acp/start/vim-go/autoload/go/tool_test.vim @@ -1,8 +1,12 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + func! Test_ExecuteInDir() abort let l:tmp = gotest#write_file('a/a.go', ['package a']) try - let l:out = go#tool#ExecuteInDir("pwd") - call assert_equal(l:tmp . "/src/a\n", l:out) + let l:out = go#util#ExecInDir(['pwd']) + call assert_equal([l:tmp . "/src/a\n", 0], l:out) finally call delete(l:tmp, 'rf') endtry @@ -13,11 +17,15 @@ func! Test_ExecuteInDir_nodir() abort exe ':e ' . l:tmp . '/new-dir/a' try - let l:out = go#tool#ExecuteInDir("pwd") - call assert_equal('', l:out) + let l:out = go#util#ExecInDir(['pwd']) + call assert_equal(['', 1], l:out) finally call delete(l:tmp, 'rf') endtry endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/ui.vim b/pack/acp/start/vim-go/autoload/go/ui.vim index 3f61257..9d5b249 100644 --- a/pack/acp/start/vim-go/autoload/go/ui.vim +++ b/pack/acp/start/vim-go/autoload/go/ui.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + let s:buf_nr = -1 "OpenWindow opens a new scratch window and put's the content into the window @@ -111,4 +115,8 @@ function! go#ui#OpenDefinition(filter) abort norm! zz endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/go/uri.vim b/pack/acp/start/vim-go/autoload/go/uri.vim new file mode 100644 index 0000000..e8f38ab --- /dev/null +++ b/pack/acp/start/vim-go/autoload/go/uri.vim @@ -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 diff --git a/pack/acp/start/vim-go/autoload/go/util.vim b/pack/acp/start/vim-go/autoload/go/util.vim index 7b43460..bccb934 100644 --- a/pack/acp/start/vim-go/autoload/go/util.vim +++ b/pack/acp/start/vim-go/autoload/go/util.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " PathSep returns the appropriate OS specific path separator. function! go#util#PathSep() abort if go#util#IsWin() @@ -48,7 +52,7 @@ function! go#util#IsMac() abort return has('mac') || \ has('macunix') || \ has('gui_macvim') || - \ go#util#System('uname') =~? '^darwin' + \ go#util#Exec(['uname'])[0] =~? '^darwin' endfunction " Checks if using: @@ -59,7 +63,20 @@ function! go#util#IsUsingCygwinShell() return go#util#IsWin() && executable('cygpath') && &shell =~ '.*sh.*' endfunction -function! go#util#has_job() abort +" Check if Vim jobs API is supported. +" +" The (optional) first parameter can be added to indicate the 'cwd' or 'env' +" parameters will be used, which wasn't added until a later version. +function! go#util#has_job(...) abort + if has('nvim') + return 1 + endif + + " cwd and env parameters to job_start was added in this version. + if a:0 > 0 && a:1 is 1 + return has('job') && has("patch-8.0.0902") + endif + " job was introduced in 7.4.xxx however there are multiple bug fixes and one " of the latest is 8.0.0087 which is required for a stable async API. return has('job') && has("patch-8.0.0087") @@ -93,27 +110,34 @@ endfunction " goarch returns 'go env GOARCH'. This is an internal function and shouldn't " be used. Instead use 'go#util#env("goarch")' function! go#util#goarch() abort - return substitute(go#util#System('go env GOARCH'), '\n', '', 'g') + return substitute(s:exec(['go', 'env', 'GOARCH'])[0], '\n', '', 'g') endfunction " goos returns 'go env GOOS'. This is an internal function and shouldn't " be used. Instead use 'go#util#env("goos")' function! go#util#goos() abort - return substitute(go#util#System('go env GOOS'), '\n', '', 'g') + return substitute(s:exec(['go', 'env', 'GOOS'])[0], '\n', '', 'g') endfunction " goroot returns 'go env GOROOT'. This is an internal function and shouldn't " be used. Instead use 'go#util#env("goroot")' function! go#util#goroot() abort - return substitute(go#util#System('go env GOROOT'), '\n', '', 'g') + return substitute(s:exec(['go', 'env', 'GOROOT'])[0], '\n', '', 'g') endfunction " gopath returns 'go env GOPATH'. This is an internal function and shouldn't " be used. Instead use 'go#util#env("gopath")' function! go#util#gopath() abort - return substitute(go#util#System('go env GOPATH'), '\n', '', 'g') + return substitute(s:exec(['go', 'env', 'GOPATH'])[0], '\n', '', 'g') endfunction +" gomod returns 'go env GOMOD'. gomod changes depending on the folder. Don't +" use go#util#env as it caches the value. +function! go#util#gomod() abort + return substitute(s:exec(['go', 'env', 'GOMOD'])[0], '\n', '', 'g') +endfunction + + function! go#util#osarch() abort return go#util#env("goos") . '_' . go#util#env("goarch") endfunction @@ -124,12 +148,13 @@ endfunction " so that we always use a standard POSIX-compatible Bourne shell (and not e.g. " csh, fish, etc.) See #988 and #1276. function! s:system(cmd, ...) abort - " Preserve original shell and shellredir values + " Preserve original shell, shellredir and shellcmdflag values let l:shell = &shell let l:shellredir = &shellredir + let l:shellcmdflag = &shellcmdflag if !go#util#IsWin() && executable('/bin/sh') - set shell=/bin/sh shellredir=>%s\ 2>&1 + set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c endif try @@ -138,6 +163,7 @@ function! s:system(cmd, ...) abort " Restore original values let &shell = l:shell let &shellredir = l:shellredir + let &shellcmdflag = l:shellcmdflag endtry endfunction @@ -153,16 +179,47 @@ endfunction function! go#util#Exec(cmd, ...) abort if len(a:cmd) == 0 call go#util#EchoError("go#util#Exec() called with empty a:cmd") - return + return ['', 1] endif + let l:bin = a:cmd[0] + + " Lookup the full path, respecting settings such as 'go_bin_path'. On errors, " CheckBinPath will show a warning for us. - let l:bin = go#path#CheckBinPath(a:cmd[0]) + let l:bin = go#path#CheckBinPath(l:bin) if empty(l:bin) - return ["", 1] + return ['', 1] endif - let l:out = call('s:system', [go#util#Shelljoin([l:bin] + a:cmd[1:])] + a:000) + " Finally execute the command using the full, resolved path. Do not pass the + " unmodified command as the correct program might not exist in $PATH. + return call('s:exec', [[l:bin] + a:cmd[1:]] + a:000) +endfunction + +function! go#util#ExecInDir(cmd, ...) abort + if !isdirectory(expand("%:p:h")) + return ['', 1] + endif + + let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let dir = getcwd() + try + execute cd . fnameescape(expand("%:p:h")) + let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000) + finally + execute cd . fnameescape(l:dir) + endtry + return [l:out, l:err] +endfunction + +function! s:exec(cmd, ...) abort + let l:bin = a:cmd[0] + let l:cmd = go#util#Shelljoin([l:bin] + a:cmd[1:]) + if go#util#HasDebug('shell-commands') + call go#util#EchoInfo('shell command: ' . l:cmd) + endif + + let l:out = call('s:system', [l:cmd] + a:000) return [l:out, go#util#ShellError()] endfunction @@ -259,7 +316,7 @@ endfunction " snippetcase converts the given word to given preferred snippet setting type " case. function! go#util#snippetcase(word) abort - let l:snippet_case = get(g:, 'go_addtags_transform', "snakecase") + let l:snippet_case = go#config#AddtagsTransform() if l:snippet_case == "snakecase" return go#util#snakecase(a:word) elseif l:snippet_case == "camelcase" @@ -397,7 +454,73 @@ endfunction " Report if the user enabled a debug flag in g:go_debug. function! go#util#HasDebug(flag) - return index(get(g:, 'go_debug', []), a:flag) >= 0 + return index(go#config#Debug(), a:flag) >= 0 endfunction +function! go#util#OpenBrowser(url) abort + let l:cmd = go#config#PlayBrowserCommand() + if len(l:cmd) == 0 + redraw + echohl WarningMsg + echo "It seems that you don't have general web browser. Open URL below." + echohl None + echo a:url + return + endif + + " if setting starts with a !. + if l:cmd =~ '^!' + let l:cmd = substitute(l:cmd, '%URL%', '\=escape(shellescape(a:url), "#")', 'g') + silent! exec l:cmd + elseif cmd =~ '^:[A-Z]' + let l:cmd = substitute(l:cmd, '%URL%', '\=escape(a:url,"#")', 'g') + exec l:cmd + else + let l:cmd = substitute(l:cmd, '%URL%', '\=shellescape(a:url)', 'g') + call go#util#System(l:cmd) + endif +endfunction + +function! go#util#ParseErrors(lines) abort + let errors = [] + + for line in a:lines + let fatalerrors = matchlist(line, '^\(fatal error:.*\)$') + let tokens = matchlist(line, '^\s*\(.\{-}\):\(\d\+\):\s*\(.*\)') + + if !empty(fatalerrors) + call add(errors, {"text": fatalerrors[1]}) + elseif !empty(tokens) + " strip endlines of form ^M + let out = substitute(tokens[3], '\r$', '', '') + + call add(errors, { + \ "filename" : fnamemodify(tokens[1], ':p'), + \ "lnum" : tokens[2], + \ "text" : out, + \ }) + elseif !empty(errors) + " Preserve indented lines. + " This comes up especially with multi-line test output. + if match(line, '^\s') >= 0 + call add(errors, {"text": substitute(line, '\r$', '', '')}) + endif + endif + endfor + + return errors +endfunction + +function! go#util#ShowInfo(info) + if empty(a:info) + return + endif + + echo "vim-go: " | echohl Function | echon a:info | echohl None +endfunction + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/gotest.vim b/pack/acp/start/vim-go/autoload/gotest.vim index 8053b4b..e010d52 100644 --- a/pack/acp/start/vim-go/autoload/gotest.vim +++ b/pack/acp/start/vim-go/autoload/gotest.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + " Write a Go file to a temporary directory and append this directory to $GOPATH. " " The file will written to a:path, which is relative to the temporary directory, @@ -127,4 +131,8 @@ func! gotest#assert_quickfix(got, want) abort endwhile endfunc +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/autoload/unite/sources/decls.vim b/pack/acp/start/vim-go/autoload/unite/sources/decls.vim index b4d5da8..3113808 100644 --- a/pack/acp/start/vim-go/autoload/unite/sources/decls.vim +++ b/pack/acp/start/vim-go/autoload/unite/sources/decls.vim @@ -28,7 +28,7 @@ function! s:source.gather_candidates(args, context) abort return [] endif - let l:include = get(g:, 'go_decls_includes', 'func,type') + let l:include = go#config#DeclsIncludes() let l:command = printf('%s -format vim -mode decls -include %s -%s %s', l:bin_path, l:include, l:mode, shellescape(l:path)) let l:candidates = [] try diff --git a/pack/acp/start/vim-go/compiler/go.vim b/pack/acp/start/vim-go/compiler/go.vim index 523cb28..5f978bb 100644 --- a/pack/acp/start/vim-go/compiler/go.vim +++ b/pack/acp/start/vim-go/compiler/go.vim @@ -9,6 +9,10 @@ if exists("g:current_compiler") endif let g:current_compiler = "go" +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + if exists(":CompilerSet") != 2 command -nargs=* CompilerSet setlocal endif @@ -38,4 +42,8 @@ CompilerSet errorformat+=%-G%.%# " All lines not matching a let &cpo = s:save_cpo unlet s:save_cpo +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/doc/vim-go.txt b/pack/acp/start/vim-go/doc/vim-go.txt index ae63eba..3dd3798 100644 --- a/pack/acp/start/vim-go/doc/vim-go.txt +++ b/pack/acp/start/vim-go/doc/vim-go.txt @@ -38,11 +38,11 @@ and individual features can be toggled easily. vim-go leverages a number of tools developed by the Go community to provide a seamless Vim experience. * Compile your package with |:GoBuild|, install it with |:GoInstall| or - test it with |:GoTest|. Run a single tests with |:GoTestFunc|). + test it with |:GoTest|. Run a single test with |:GoTestFunc|). * Quickly execute your current file(s) with |:GoRun|. * Improved syntax highlighting and folding. * Debug programs with integrated `delve` support with |:GoDebugStart|. - * Completion support via `gocode`. + * Completion support via `gocode` and `gopls`. * `gofmt` or `goimports` on save keeps the cursor position and undo history. * Go to symbol/declaration with |:GoDef|. * Look up documentation with |:GoDoc| or |:GoDocBrowser|. @@ -76,6 +76,12 @@ tools developed by the Go community to provide a seamless Vim experience. ============================================================================== INSTALL *go-install* +vim-go requires at least Vim 7.4.2009 or Neovim 0.3.1. On macOS, if you are +still using your system version of vim, you can use homebrew to keep your +version of Vim up-to-date with the following terminal command: +> + brew install vim + The latest stable release, https://github.com/fatih/vim-go/releases/latest, is the recommended version to use. If you choose to use the master branch instead, please do so with caution; it is a _development_ branch. @@ -106,12 +112,13 @@ manager's install command. < * https://github.com/gmarik/vundle > - Plugin 'fatih/vim-go' + Plugin 'fatih/vim-go', { 'do': ':GoUpdateBinaries' } < * Manual (not recommended) > Copy all of the files into your `~/.vim` directory < + You will also need to install all the necessary binaries. vim-go makes it easy to install all of them by providing a command, |:GoInstallBinaries|, to `go get` all the required binaries. The binaries will be installed to $GOBIN @@ -131,8 +138,12 @@ The following plugins are supported for use with vim-go: https://github.com/Shougo/neocomplete.vim * Real-time completion (Neovim and Vim 8): - https://github.com/Shougo/deoplete.nvim and - https://github.com/zchee/deoplete-go + https://github.com/Shougo/deoplete.nvim + + Add the following line to your vimrc. This instructs deoplete to use omni + completion for Go files. + + call deoplete#custom#option('omni_patterns', { 'go': '[^. *\t]\.\w*' }) * Display source code navigation in a sidebar: https://github.com/majutsushi/tagbar @@ -142,12 +153,6 @@ The following plugins are supported for use with vim-go: https://github.com/SirVer/ultisnips or https://github.com/joereynolds/vim-minisnip -* For a better documentation viewer check out: - https://github.com/garyburd/go-explorer - -* Integration with `delve` (Neovim only): - https://github.com/jodosha/vim-godebug - * Interactive |:GoDecls| and |:GoDeclsDir|: https://github.com/ctrlpvim/ctrlp.vim or https://github.com/junegunn/fzf.vim or @@ -157,6 +162,11 @@ The following plugins are supported for use with vim-go: ============================================================================== COMMANDS *go-commands* + *:GoReportGitHubIssue* +:GoReportGitHubIssue + GoReportGitHubIssue opens the default browser and starts a new bug report + with useful system information. + *:GoPath* :GoPath [path] @@ -187,11 +197,13 @@ COMMANDS *go-commands* displayed and the buffer will be untouched. *:GoLint* -:GoLint [packages] +:GoLint! [packages] Run golint for the directory under your current file, or for the given packages. + If [!] is not given the first error is jumped to. + *:GoDoc* :GoDoc [word] @@ -249,7 +261,7 @@ CTRL-] g - 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 , g are enabled to invoke :GoDef for the identifier under the cursor. See |'g:go_def_mapping_enabled'| to disable them. No explicit @@ -261,6 +273,14 @@ g list of file locations you have visited with :GoDef that is retained to help you navigate software. + The per-window location stack is shared with |:GoDefType|. + + *:GoDefType* +:GoDefType + + Go to type definition for the identifier under the cursor. + + The per-window location stack is shared with |:GoDef|. *:GoDefStack* :GoDefStack [number] @@ -455,7 +475,7 @@ CTRL-t If [!] is not given the first error is jumped to. *:GoErrCheck* -:GoErrCheck [options] +:GoErrCheck! [options] Check for unchecked errors in you current package. Errors are populated in the quickfix window. @@ -463,6 +483,8 @@ CTRL-t You may optionally pass any valid errcheck flags/options. See `errcheck -h` for a full list. + If [!] is not given the first error is jumped to. + *:GoFiles* :GoFiles [source_files] @@ -629,7 +651,7 @@ CTRL-t disabled it clears and stops automatic highlighting. *:GoMetaLinter* -:GoMetaLinter [path] +:GoMetaLinter! [path] Calls the underlying `gometalinter` tool and displays all warnings and errors in the |quickfix| window. By default the following linters are @@ -638,16 +660,18 @@ CTRL-t use the variable |'g:go_metalinter_command'|. To override the maximum linters execution time use |'g:go_metalinter_deadline'| variable. + If [!] is not given the first error is jumped to. + *:GoBuildTags* :GoBuildTags [tags] Changes the build tags for various commands. If you have any file that - uses a custom build tag, such as `//+build integration` , this command can - be used to pass it to all tools that accepts tags, such as guru, gorename, - etc.. + uses a custom build tag, such as `// +build integration` , this command + can be used to pass it to all tools that accepts tags, such as guru, + gorename, etc. - The build tags is cleared (unset) if `""` is given. If no arguments is - given it prints the current custom build tags. + The build tags is cleared (unset) if `""` is given. If no arguments are + given it prints the current build tags. *:AsmFmt* :AsmFmt @@ -676,6 +700,12 @@ CTRL-t \| command! -bang AS call go#alternate#Switch(0, 'split') augroup END < + + *:GoPointsTo* +:GoPointsTo + + Show all variables to which the pointer under the cursor may point to. + *:GoWhicherrs* :GoWhicherrs @@ -762,7 +792,7 @@ CTRL-t *:GoRemoveTags* :[range]GoRemoveTags [key],[option] [key1],[option1] ... - Rmove field tags for the fields of a struct. If called inside a struct it + Remove field tags for the fields of a struct. If called inside a struct it automatically remove all field tags. An error message is given if it's called outside a struct definition or if the file is not correctly formatted @@ -789,6 +819,11 @@ CTRL-t Toggles |'g:go_fmt_autosave'|. + *:GoModFmtAutoSaveToggle* +:GoModFmtAutoSaveToggle + + Toggles |'g:go_mod_fmt_autosave'|. + *:GoAsmFmtAutoSaveToggle* :GoAsmFmtAutoSaveToggle @@ -842,6 +877,34 @@ CTRL-t } < + *:GoIfErr* +:GoIfErr + + Generate if err != nil { return ... } automatically which infer the type + of return values and the numbers. + + For example: +> + func doSomething() (string, error) { + f, err := os.Open("file") + } +< + Becomes: +> + func doSomething() (string, error) { + f, err := os.Open("file") + if err != nil { + return "", err + } + } +< + *:GoModFmt* +:GoModFmt + + Filter the current go.mod buffer through "go mod edit -fmt" command. It + tries to preserve cursor position and avoids replacing the buffer with + stderr output. + ============================================================================== MAPPINGS *go-mappings* @@ -1030,6 +1093,10 @@ Show send/receive corresponding to selected channel op Show all refs to entity denoted by selected identifier + *(go-pointsto)* + +Show all variables to which the pointer under the cursor may point to. + *(go-metalinter)* Calls `go-metalinter` for the current directory @@ -1050,6 +1117,14 @@ Alternates between the implementation and test code in a new vertical split Calls `:GoImport` for the current package + *(go-iferr)* + +Generate if err != nil { return ... } automatically which infer the type of +return values and the numbers. + + *(go-mod-fmt)* + +Calls |:GoModFmt| for the current buffer ============================================================================== TEXT OBJECTS *go-text-objects* @@ -1070,6 +1145,12 @@ if "inside a function", select contents of a function, excluding the function definition and the closing bracket. This text-object also supports literal functions + *go-v_ac* *go-ac* +ac "a comment", select contents of the current comment block. + + *go-v_ic* *go-ic* +ic "inner comment", select contents of the current comment block, + excluding the start and end comment markers. vim-go also defines the following text motion objects: @@ -1090,10 +1171,10 @@ FUNCTIONS *go-functions* *go#statusline#Show()* Shows the status of a job running asynchronously. Can be used to plug into the -statusline. It works to show the status per package instead of per -file. Assume you have three files open, all belonging to the same package, if -the package build (`:GoBuild`) is successful, all statusline's will show -`success`, if you it fails all file's statusline will show `failed`. +statusline. It works to show the status per package instead of per file. +Assume you have three files open, all belonging to the same package, if the +package build (`:GoBuild`) is successful, all statuslines will show `success`, +if it fails all windows' statuslines will show `failed`. To avoid always showing old status information, the status information is cleaned for each package after `60` seconds. This can be changed with the @@ -1104,6 +1185,21 @@ cleaned for each package after `60` seconds. This can be changed with the Returns the description of the identifer under the cursor. Can be used to plug into the statusline. + *go#complete#Complete()* + +Uses `gopls` for autocompletion. By default, it is hooked up to |'omnifunc'| +for Vim8 and Neovim. + + *go#complete#GocodeComplete()* + +Uses `gocode` for autocompletion. By default, it is hooked up to |'omnifunc'| +for Vim 7.4. + + *go#tool#DescribeBalloon()* + +Suitable to be used as an expression to show the evaluation balloon. See `help +balloonexpr`. + ============================================================================== SETTINGS *go-settings* @@ -1156,8 +1252,8 @@ updated. By default it's disabled. The delay can be configured with the Use this option to define the command to be used for |:GoInfo|. By default `gocode` is being used as it's the fastest option. But one might also use -`guru` as it's covers more cases and is more accurate. Current valid options -are: `[gocode, guru]` > +`gopls` or `guru` as they cover more cases and are more accurate. Current +valid options are: `[gocode, guru, gopls]` > let g:go_info_mode = 'gocode' < @@ -1231,11 +1327,19 @@ fails. By default the location list is shown. > Use this option to enable fmt's experimental mode. This experimental mode is superior to the current mode as it fully saves the undo history, so undo/redo -doesn't break. However it's slows (creates/deletes a file for every save) and +doesn't break. However, it's slow (creates/deletes a file for every save) and it's causing problems on some Vim versions. By default it's disabled. > let g:go_fmt_experimental = 0 + < + *'g:go_mod_fmt_autosave'* + +Use this option to auto |:GoModFmt| on save. By default it's enabled > + + let g:go_mod_fmt_autosave = 1 +< + *'g:go_doc_keywordprg_enabled'* Use this option to run `godoc` on words under the cursor with |K|; this will @@ -1264,7 +1368,7 @@ a private internal service. Default is 'https://godoc.org'. Use this option to define the command to be used for |:GoDef|. By default `guru` is being used as it covers all edge cases. But one might also use -`godef` as it's faster. Current valid options are: `[guru, godef]` > +`godef` as it's faster. Current valid options are: `[guru, godef, gopls]` > let g:go_def_mode = 'guru' < @@ -1284,21 +1388,26 @@ mappings of |:GoDef|. By default it's disabled. > let g:go_def_reuse_buffer = 0 < - *'g:go_doc_command'* - -Command to use for |:GoDoc|; only used when invoked with a package name. The -`gogetdoc` command is always used when |:GoDoc| is used on the identifier -under the cursor (i.e. without argument or from |K|). > - - let g:go_doc_command = ["godoc"] - -< *'g:go_bin_path'* + *'g:go_bin_path'* Use this option to change default path for vim-go tools when using |:GoInstallBinaries| and |:GoUpdateBinaries|. If not set `$GOBIN` or `$GOPATH/bin` is used. > let g:go_bin_path = "" +< + *'g:go_search_bin_path_first'* + +This option lets |'g:go_bin_path'| (or its default value) take precedence over +$PATH when invoking a tool command such as |:GoFmt| or |:GoImports|. + +Enabling this option ensures that the binaries installed via +|:GoInstallBinaries| and |:GoUpdateBinaries| are the same ones that are +invoked via the tool commands. + +By default it is enabled. > + + let g:go_search_bin_path_first = 1 < *'g:go_snippet_engine'* @@ -1336,10 +1445,10 @@ By default it's not set, so the relevant commands defaults are being used. < *'g:go_build_tags'* -These options that will be automatically passed to the `-tags` option of -various tools, such as `guru`, `gorename`, etc... This is a permanent -setting. A more useful way is to use |:GoBuildTags| to dynamically change or -remove build tags. By default it's not set. +Space-separated list of build tags passed to the `-tags` flag of tools that +support it. +There is also the |:GoBuildTags| convenience command to change or remove build +tags. > let g:go_build_tags = '' < @@ -1407,11 +1516,12 @@ it's empty < *'g:go_metalinter_command'* -Overrides the command to be executed when |:GoMetaLinter| is called. This is -an advanced settings and is for users who want to have a complete control -over how `gometalinter` should be executed. By default it's empty. +Overrides the command to be executed when |:GoMetaLinter| is called. By +default it's `gometalinter`. `golangci-lint` is also supported. It can also be +used as an advanced setting for users who want to have more control over +the metalinter. > - let g:go_metalinter_command = "" + let g:go_metalinter_command = "gometalinter" < *'g:go_metalinter_deadline'* @@ -1445,10 +1555,10 @@ that was called. Supported values are "", "quickfix", and "locationlist". Specifies the type of list to use for command outputs (such as errors from builds, results from static analysis commands, etc...). When an expected key is not present in the dictionary, |'g:go_list_type'| will be used instead. -Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoInstall", "GoLint", -"GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for both -:GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". Supported -values for each command are "quickfix" and "locationlist". +Supported keys are "GoBuild", "GoErrCheck", "GoFmt", "GoModFmt", "GoInstall", +"GoLint", "GoMetaLinter", "GoMetaLinterAutoSave", "GoModifyTags" (used for +both :GoAddTags and :GoRemoveTags), "GoRename", "GoRun", and "GoTest". +Supported values for each command are "quickfix" and "locationlist". > let g:go_list_type_commands = {} < @@ -1522,14 +1632,6 @@ same. let g:go_gorename_prefill = 'expand("") =~# "^[A-Z]"' . \ '? go#util#pascalcase(expand(""))' . \ ': go#util#camelcase(expand(""))' -< - *'g:go_gocode_autobuild'* - -Specifies whether `gocode` should automatically build out-of-date packages -when their source fields are modified, in order to obtain the freshest -autocomplete results for them. By default it is enabled. -> - let g:go_gocode_autobuild = 1 < *'g:go_gocode_propose_builtins'* @@ -1537,6 +1639,14 @@ Specifies whether `gocode` should add built-in types, functions and constants to an autocompletion proposals. By default it is enabled. > let g:go_gocode_propose_builtins = 1 +< + *'g:go_gocode_propose_source'* + +Specifies whether `gocode` should use source files instead of binary packages +for autocompletion proposals. When disabled, only identifiers from the current +package and packages that have been installed will proposed. +> + let g:go_gocode_propose_source = 0 < *'g:go_gocode_unimported_packages'* @@ -1666,9 +1776,11 @@ A list of options to debug; useful for development and/or reporting bugs. Currently accepted values: + shell-commands Echo all shell commands that vim-go runs. debugger-state Expose debugger state in 'g:go_debug_diag'. debugger-commands Echo communication between vim-go and `dlv`; requests and responses are recorded in `g:go_debug_commands`. + lsp Record lsp requests and responses in g:go_lsp_log. > let g:go_debug = [] < @@ -1750,13 +1862,13 @@ Highlight function and method declarations. > let g:go_highlight_functions = 0 < - *'g:go_highlight_function_arguments'* + *'g:go_highlight_function_parameters'* -Highlight the variable names in arguments and return values in function -declarations. Setting this implies the functionality from +Highlight the variable names in parameters (including named return parameters) +in function declarations. Setting this implies the functionality from |'g:go_highlight_functions'|. > - let g:go_highlight_function_arguments = 0 + let g:go_highlight_function_parameters = 0 < *'g:go_highlight_function_calls'* @@ -1828,6 +1940,13 @@ filetype. The `gohtmltmpl` filetype is automatically set for `*.tmpl` files; the `gotexttmpl` is never automatically set and needs to be set manually. +============================================================================== + *gomod* *ft-gomod-syntax* +go.mod file syntax~ + +The `gomod` 'filetype' provides syntax highlighting for Go's module file +`go.mod` + ============================================================================== DEBUGGER *go-debug* @@ -1847,10 +1966,10 @@ features: * Toggle breakpoint. * Stack operation continue/next/step out. -This feature requires Vim 8.0.0087 or newer with the |+job| feature. Neovim -does _not_ work (yet). -This requires Delve 1.0.0 or newer, and it is recommended to use Go 1.10 or -newer, as its new caching will speed up recompiles. +This feature requires either Vim 8.0.0087 or newer with the |+job| feature or +Neovim. This features also requires Delve 1.0.0 or newer, and it is +recommended to use Go 1.10 or newer, as its new caching will speed up +recompiles. *go-debug-intro* GETTING STARTED WITH THE DEBUGGER~ @@ -2035,6 +2154,24 @@ Defaults to `127.0.0.1:8181`: let g:go_debug_address = '127.0.0.1:8181' < + *'g:go_debug_log_output'* + +Specifies log output options for `dlv`. Value should be a single string +of comma-separated options suitable for passing to `dlv`. An empty string +(`''`) will suppress logging entirely. +Default: `'debugger, rpc'`: +> + let g:go_debug_log = 'debugger, rpc' +< + + *'g:go_highlight_debug'* + +Highlight the current line and breakpoints in the debugger. + +> + let g:go_highlight_debug = 1 +< + ============================================================================== FAQ TROUBLESHOOTING *go-troubleshooting* @@ -2067,7 +2204,7 @@ Many vim-go commands use the `guru` commandline tool to get information. Some a reasonable amount of performance `guru` limits this analysis to a selected list of packages. This is known as the "guru scope". -The default is to use the package the curent buffer belongs to, but this may +The default is to use the package the current buffer belongs to, but this may not always be correct. For example for the file `guthub.com/user/pkg/a/a.go` the scope will be set to `github.com/user/pkg/a`, but you probably want `github.com/user/pkg` @@ -2093,11 +2230,22 @@ Also see |:GoGuruScope| and |'g:go_guru_scope'|. Vim becomes slow while editing Go files~ -This is usually caused by `g:go_highlight_*` options. Try disabling them if -you've enabled some of them. +The most common cause for this is using an older version of Vim that doesn't +support asynchronous jobs. |'g:go_auto_sameids'| and |'g:go_auto_type_info'| +run jobs that can cause noticable delays when used with vim74. The problem is +most pronounced on vim74, but can occur on vim8 and nvim. On vim8 and nvim, +the problem should be restricted to a short period when the first buffer in a +package is first loaded. -Other common culprits are |'g:go_auto_sameids'| and |go#statusline#Show()|. +If you see unexpected characters rendered in the current window, the problem +is most likely due to |'g:go_auto_sameids'| or |'g:go_auto_type_info'|. First, +try using another mode for |'g:go_info_mode'|. If that doesn't work, try +disabling |'g:go_auto_sameids'| and |'g:go_auto_type_info'|. +To a lesser extent, this can be caused by `g:go_highlight_*` options. If Vim +is just slower than normal, but doesn't render unexpected characters in the +currrent window, then the problem is most likely the `g:go_highlight_*` +options. Try disabling them if you've enabled some of them. I get errors when using GoInstallBinaries~ @@ -2165,9 +2313,9 @@ Using with NeoVim~ Note: Neovim currently is not a first class citizen for vim-go. You are free to open bug, however I'm not using Neovim so it's hard for me to test it. -vim-go might not work well as good as in Vim. I'm happy to accept pull -requests or very detailed bug reports. If you're interested to improve the -state of Neovim in vim-go you're always welcome! +vim-go might not work as well in Neovim as it does in Vim. I'm happy to accept +pull requests or very detailed bug reports. If you're interested to improve +the state of Neovim in vim-go you're always welcome! Run `:GoRun` in a new tab, horizontal split or vertical split terminal > @@ -2180,6 +2328,18 @@ By default new terminals are opened in a vertical split. To change it let g:go_term_mode = "split" > +How can I customize the highlighting?~ + +All the highlight groups used by vim-go are prefixed with `go` (e.g. +`goType`) and are defined in the files in the `syntax` directory. To change +the highlighting for any group, add a `highlight` command for the group to +your vimrc. To turn off the highlighting for any group, add `highlight link +group-name NONE` (where `group-name` is the name of the group whose highlight +you'd like to turn off) to your vimrc. + +Some people may wish to highlight Go's builtins as keywords. To do so, one +should simply add `highlight link goBuiltins Keyword` to the `vimrc` file. + ============================================================================== DEVELOPMENT *go-development* @@ -2215,14 +2375,14 @@ You can install and test all Vim versions by running `make`. DONATION *go-donation* People have asked for this for a long time, now you can be a fully supporter -by being a patreon at: https://www.patreon.com/fatih +by being a patreon at: https://www.patreon.com/bhcleek By being a patron, you are enabling vim-go to grow and mature, helping me to invest in bug fixes, new documentation, and improving both current and future features. It's completely optional and is just a direct way to support vim-go's ongoing development. Thanks! -Check it out: https://www.patreon.com/fatih +Check it out: https://www.patreon.com/bhcleek ============================================================================== diff --git a/pack/acp/start/vim-go/ftdetect/gofiletype.vim b/pack/acp/start/vim-go/ftdetect/gofiletype.vim index d3662f4..7051f37 100644 --- a/pack/acp/start/vim-go/ftdetect/gofiletype.vim +++ b/pack/acp/start/vim-go/ftdetect/gofiletype.vim @@ -1,34 +1,40 @@ " vint: -ProhibitAutocmdWithNoGroup -" We take care to preserve the user's fileencodings and fileformats, -" because those settings are global (not buffer local), yet we want -" to override them for loading Go files, which are defined to be UTF-8. -let s:current_fileformats = '' -let s:current_fileencodings = '' - -" define fileencodings to open as utf-8 encoding even if it's ascii. -function! s:gofiletype_pre(type) - let s:current_fileformats = &g:fileformats - let s:current_fileencodings = &g:fileencodings - set fileencodings=utf-8 fileformats=unix - let &l:filetype = a:type -endfunction - -" restore fileencodings as others -function! s:gofiletype_post() - let &g:fileformats = s:current_fileformats - let &g:fileencodings = s:current_fileencodings -endfunction +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim " Note: should not use augroup in ftdetect (see :help ftdetect) -au BufNewFile *.go setfiletype go | setlocal fileencoding=utf-8 fileformat=unix -au BufRead *.go call s:gofiletype_pre("go") -au BufReadPost *.go call s:gofiletype_post() +au BufRead,BufNewFile *.go setfiletype go +au BufRead,BufNewFile *.s setfiletype asm +au BufRead,BufNewFile *.tmpl setfiletype gohtmltmpl -au BufNewFile *.s setfiletype asm | setlocal fileencoding=utf-8 fileformat=unix -au BufRead *.s call s:gofiletype_pre("asm") -au BufReadPost *.s call s:gofiletype_post() +" remove the autocommands for modsim3, and lprolog files so that their +" highlight groups, syntax, etc. will not be loaded. *.MOD is included, so +" that on case insensitive file systems the module2 autocmds will not be +" executed. +au! BufRead,BufNewFile *.mod,*.MOD +" Set the filetype if the first non-comment and non-blank line starts with +" 'module '. +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 diff --git a/pack/acp/start/vim-go/ftplugin/asm.vim b/pack/acp/start/vim-go/ftplugin/asm.vim index 99996aa..9271d22 100644 --- a/pack/acp/start/vim-go/ftplugin/asm.vim +++ b/pack/acp/start/vim-go/ftplugin/asm.vim @@ -5,7 +5,12 @@ if exists("b:did_ftplugin") endif let b:did_ftplugin = 1 -let b:undo_ftplugin = "setl fo< com< cms<" +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + +let b:undo_ftplugin = "setl fo< com< cms< + \ | exe 'au! vim-go-asm-buffer * '" setlocal formatoptions-=t @@ -16,4 +21,17 @@ setlocal noexpandtab command! -nargs=0 AsmFmt call go#asmfmt#Format() +" Autocommands +" ============================================================================ + +augroup vim-go-asm-buffer + autocmd! * + + autocmd BufWritePre 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 diff --git a/pack/acp/start/vim-go/ftplugin/go.vim b/pack/acp/start/vim-go/ftplugin/go.vim index a0ecd32..233a47a 100644 --- a/pack/acp/start/vim-go/ftplugin/go.vim +++ b/pack/acp/start/vim-go/ftplugin/go.vim @@ -9,7 +9,12 @@ if exists("b:did_ftplugin") endif let b:did_ftplugin = 1 -let b:undo_ftplugin = "setl fo< com< cms<" +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + +let b:undo_ftplugin = "setl fo< com< cms< + \ | exe 'au! vim-go-buffer * '" setlocal formatoptions-=t @@ -20,8 +25,11 @@ setlocal noexpandtab compiler go -" Set gocode completion +" Set autocompletion setlocal omnifunc=go#complete#Complete +if !go#util#has_job() + setlocal omnifunc=go#complete#GocodeComplete +endif if get(g:, "go_doc_keywordprg_enabled", 1) " keywordprg doesn't allow to use vim commands, override it @@ -35,18 +43,24 @@ if get(g:, "go_def_mapping_enabled", 1) nnoremap :GoDef nnoremap :GoDef nnoremap g :GoDef - nnoremap :call go#def#Jump("split") - nnoremap ] :call go#def#Jump("split") + nnoremap :call go#def#Jump("split", 0) + nnoremap ] :call go#def#Jump("split", 0) nnoremap :call go#def#StackPop(v:count1) endif if get(g:, "go_textobj_enabled", 1) onoremap af :call go#textobj#Function('a') - onoremap if :call go#textobj#Function('i') - xnoremap af :call go#textobj#Function('a') + + onoremap if :call go#textobj#Function('i') xnoremap if :call go#textobj#Function('i') + onoremap ac :call go#textobj#Comment('a') + xnoremap ac :call go#textobj#Comment('a') + + onoremap ic :call go#textobj#Comment('i') + xnoremap ic :call go#textobj#Comment('i') + " Remap ]] and [[ to jump betweeen functions as they are useless in Go nnoremap ]] :call go#textobj#FunctionJump('n', 'next') nnoremap [[ :call go#textobj#FunctionJump('n', 'prev') @@ -58,76 +72,60 @@ if get(g:, "go_textobj_enabled", 1) xnoremap [[ :call go#textobj#FunctionJump('v', 'prev') endif -if get(g:, "go_auto_type_info", 0) || get(g:, "go_auto_sameids", 0) +if go#config#AutoTypeInfo() || go#config#AutoSameids() let &l:updatetime= get(g:, "go_updatetime", 800) endif -" NOTE(arslan): experimental, disabled by default, doesn't work well. No -" documentation as well. If anyone feels adventurous, enable the following and -" try to search for Go identifiers ;) +" Autocommands +" ============================================================================ " -" if get(g:, "go_sameid_search_enabled", 0) -" autocmd FileType go nnoremap * :call Sameids_search(0) -" autocmd FileType go nnoremap # :call Sameids_search(1) -" autocmd FileType go nnoremap n :call Sameids_repeat(0) -" autocmd FileType go nnoremap N :call Sameids_repeat(1) -" autocmd FileType go cabbrev nohlsearch =Sameids_nohlsearch() -" endif +augroup vim-go-buffer + autocmd! * -" " mode 0: next 1: prev -" function! Sameids_repeat(mode) -" let matches = getmatches() -" if empty(matches) -" return -" endif -" let cur_offset = go#util#OffsetCursor() + " The file is registered (textDocument/DidOpen) with gopls in in + " plugin/go.vim on the FileType event. + " TODO(bc): handle all the other events that may be of interest to gopls, + " too (e.g. BufFilePost , CursorHold , CursorHoldI, FileReadPost, + " StdinReadPre, BufWritePost, TextChange, TextChangedI) + if go#util#has_job() + autocmd BufWritePost call go#lsp#DidChange(expand(':p')) + autocmd FileChangedShell call go#lsp#DidChange(expand(':p')) + autocmd BufDelete call go#lsp#DidClose(expand(':p')) + endif -" " reverse list to make it easy to find the prev occurrence -" if a:mode -" call reverse(matches) -" endif + autocmd CursorHold call go#auto#auto_type_info() + autocmd CursorHold call go#auto#auto_sameids() -" for m in matches -" if !has_key(m, "group") -" return -" endif + " Echo the identifier information when completion is done. Useful to see + " the signature of a function, etc... + if exists('##CompleteDone') + autocmd CompleteDone call go#auto#echo_go_info() + endif -" if m.group != "goSameId" -" return -" endif + autocmd BufWritePre call go#auto#fmt_autosave() + autocmd BufWritePost call go#auto#metalinter_autosave() -" let offset = go#util#Offset(m.pos1[0], m.pos1[1]) + " clear SameIds when the buffer is unloaded so that loading another buffer + " in the same window doesn't highlight the most recently matched + " identifier's positions. + autocmd BufWinEnter call go#guru#ClearSameIds() -" if a:mode && cur_offset > offset -" call cursor(m.pos1[0], m.pos1[1]) -" return -" elseif !a:mode && cur_offset < offset -" call cursor(m.pos1[0], m.pos1[1]) -" return -" endif -" endfor + autocmd BufEnter + \ if go#config#AutodetectGopath() && !exists('b:old_gopath') + \| let b:old_gopath = exists('$GOPATH') ? $GOPATH : -1 + \| let $GOPATH = go#path#Detect() + \| endif + autocmd BufLeave + \ if exists('b:old_gopath') + \| if b:old_gopath isnot -1 + \| let $GOPATH = b:old_gopath + \| endif + \| unlet b:old_gopath + \| endif +augroup end -" " reached start/end, jump to the end/start -" let initial_match = matches[0] -" if !has_key(initial_match, "group") -" return -" endif - -" if initial_match.group != "goSameId" -" return -" endif - -" call cursor(initial_match.pos1[0], initial_match.pos1[1]) -" endfunction - -" function! Sameids_search(mode) -" call go#guru#SameIds() -" call Sameids_repeat(a:mode) -" endfunction - -" function! Sameids_nohlsearch() -" call go#guru#ClearSameIds() -" return "nohlsearch" -" endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/ftplugin/go/commands.vim b/pack/acp/start/vim-go/ftplugin/go/commands.vim index 8b74ba6..726b944 100644 --- a/pack/acp/start/vim-go/ftplugin/go/commands.vim +++ b/pack/acp/start/vim-go/ftplugin/go/commands.vim @@ -4,6 +4,7 @@ command! -nargs=? -complete=customlist,go#rename#Complete GoRename call go#renam " -- guru command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope() command! -range=% GoImplements call go#guru#Implements() +command! -range=% GoPointsTo call go#guru#PointsTo() command! -range=% GoWhicherrs call go#guru#Whicherrs() command! -range=% GoCallees call go#guru#Callees() command! -range=% GoDescribe call go#guru#Describe() @@ -13,19 +14,22 @@ command! -range=% GoFreevars call go#guru#Freevars() command! -range=% GoChannelPeers call go#guru#ChannelPeers() command! -range=% GoReferrers call go#guru#Referrers() -command! -range=0 GoSameIds call go#guru#SameIds() +command! -range=0 GoSameIds call go#guru#SameIds(1) command! -range=0 GoSameIdsClear call go#guru#ClearSameIds() command! -range=0 GoSameIdsToggle call go#guru#ToggleSameIds() -command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToogleSameIds() +command! -range=0 GoSameIdsAutoToggle call go#guru#AutoToggleSameIds() " -- tags command! -nargs=* -range GoAddTags call go#tags#Add(, , , ) command! -nargs=* -range GoRemoveTags call go#tags#Remove(, , , ) +" -- mod +command! -nargs=0 -range GoModFmt call go#mod#Format() + " -- tool command! -nargs=* -complete=customlist,go#tool#ValidFiles GoFiles echo go#tool#Files() command! -nargs=0 GoDeps echo go#tool#Deps() -command! -nargs=* GoInfo call go#tool#Info(0) +command! -nargs=0 GoInfo call go#tool#Info(1) command! -nargs=0 GoAutoTypeInfoToggle call go#complete#ToggleAutoTypeInfo() " -- cmd @@ -50,7 +54,8 @@ command! -nargs=* -bang GoCoverageBrowser call go#coverage#Browser(0, , , ) " -- 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() command! -nargs=? GoDefStack :call go#def#Stack() command! -nargs=? GoDefStackClear :call go#def#StackClear() @@ -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, , '') " -- linters -command! -nargs=* GoMetaLinter call go#lint#Gometa(0, ) +command! -nargs=* -bang GoMetaLinter call go#lint#Gometa(0, 0, ) command! -nargs=0 GoMetaLinterAutoSaveToggle call go#lint#ToggleMetaLinterAutoSave() -command! -nargs=* GoLint call go#lint#Golint() +command! -nargs=* -bang GoLint call go#lint#Golint(0, ) command! -nargs=* -bang GoVet call go#lint#Vet(0, ) -command! -nargs=* -complete=customlist,go#package#Complete GoErrCheck call go#lint#Errcheck() +command! -nargs=* -bang -complete=customlist,go#package#Complete GoErrCheck call go#lint#Errcheck(0, ) " -- alternate command! -bang GoAlternate call go#alternate#Switch(0, '') @@ -105,4 +110,10 @@ if !exists(':GoDebugStart') command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint() 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 diff --git a/pack/acp/start/vim-go/ftplugin/go/mappings.vim b/pack/acp/start/vim-go/ftplugin/go/mappings.vim index 4cb8278..f1f069e 100644 --- a/pack/acp/start/vim-go/ftplugin/go/mappings.vim +++ b/pack/acp/start/vim-go/ftplugin/go/mappings.vim @@ -31,7 +31,7 @@ nnoremap (go-coverage-browser) :call go#coverage#Browser(!g: nnoremap (go-files) :call go#tool#Files() nnoremap (go-deps) :call go#tool#Deps() -nnoremap (go-info) :call go#tool#Info(0) +nnoremap (go-info) :call go#tool#Info(1) nnoremap (go-import) :call go#import#SwitchImport(1, '', expand(''), '') nnoremap (go-imports) :call go#fmt#Format(1) @@ -43,16 +43,25 @@ nnoremap (go-callstack) :call go#guru#Callstack(-1) xnoremap (go-freevars) :call go#guru#Freevars(0) nnoremap (go-channelpeers) :call go#guru#ChannelPeers(-1) nnoremap (go-referrers) :call go#guru#Referrers(-1) -nnoremap (go-sameids) :call go#guru#SameIds() +nnoremap (go-sameids) :call go#guru#SameIds(1) +nnoremap (go-pointsto) :call go#guru#PointsTo(-1) nnoremap (go-whicherrs) :call go#guru#Whicherrs(-1) nnoremap (go-sameids-toggle) :call go#guru#ToggleSameIds() nnoremap (go-rename) :call go#rename#Rename(!g:go_jump_to_error) -nnoremap (go-def) :call go#def#Jump('') -nnoremap (go-def-vertical) :call go#def#Jump("vsplit") -nnoremap (go-def-split) :call go#def#Jump("split") -nnoremap (go-def-tab) :call go#def#Jump("tab") +nnoremap (go-decls) :call go#decls#Decls(0, '') +nnoremap (go-decls-dir) :call go#decls#Decls(1, '') + +nnoremap (go-def) :call go#def#Jump('', 0) +nnoremap (go-def-vertical) :call go#def#Jump("vsplit", 0) +nnoremap (go-def-split) :call go#def#Jump("split", 0) +nnoremap (go-def-tab) :call go#def#Jump("tab", 0) + +nnoremap (go-def-type) :call go#def#Jump('', 1) +nnoremap (go-def-type-vertical) :call go#def#Jump("vsplit", 1) +nnoremap (go-def-type-split) :call go#def#Jump("split", 1) +nnoremap (go-def-type-tab) :call go#def#Jump("tab", 1) nnoremap (go-def-pop) :call go#def#StackPop() nnoremap (go-def-stack) :call go#def#Stack() @@ -64,12 +73,14 @@ nnoremap (go-doc-vertical) :call go#doc#Open("vnew", "vsplit nnoremap (go-doc-split) :call go#doc#Open("new", "split") nnoremap (go-doc-browser) :call go#doc#OpenBrowser() -nnoremap (go-metalinter) :call go#lint#Gometa(0) -nnoremap (go-lint) :call go#lint#Golint() +nnoremap (go-metalinter) :call go#lint#Gometa(!g:go_jump_to_error, 0) +nnoremap (go-lint) :call go#lint#Golint(!g:go_jump_to_error) nnoremap (go-vet) :call go#lint#Vet(!g:go_jump_to_error) nnoremap (go-alternate-edit) :call go#alternate#Switch(0, "edit") nnoremap (go-alternate-vertical) :call go#alternate#Switch(0, "vsplit") nnoremap (go-alternate-split) :call go#alternate#Switch(0, "split") +nnoremap (go-iferr) :call go#iferr#Generate() + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/ftplugin/go/snippets.vim b/pack/acp/start/vim-go/ftplugin/go/snippets.vim index f53de41..1fbc1ab 100644 --- a/pack/acp/start/vim-go/ftplugin/go/snippets.vim +++ b/pack/acp/start/vim-go/ftplugin/go/snippets.vim @@ -1,3 +1,7 @@ +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + if exists("g:go_loaded_gosnippets") finish endif @@ -47,7 +51,7 @@ function! s:GoMinisnip() abort endfunction -let s:engine = get(g:, 'go_snippet_engine', 'automatic') +let s:engine = go#config#SnippetEngine() if s:engine is? "automatic" if get(g:, 'did_plugin_ultisnips') is 1 call s:GoUltiSnips() @@ -64,4 +68,8 @@ elseif s:engine is? "minisnip" call s:GoMinisnip() endif +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/ftplugin/go/tagbar.vim b/pack/acp/start/vim-go/ftplugin/go/tagbar.vim index 2d22fd7..3ca244b 100644 --- a/pack/acp/start/vim-go/ftplugin/go/tagbar.vim +++ b/pack/acp/start/vim-go/ftplugin/go/tagbar.vim @@ -9,6 +9,10 @@ elseif globpath(&rtp, 'plugin/tagbar.vim') == "" finish endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + if !exists("g:go_gotags_bin") let g:go_gotags_bin = "gotags" endif @@ -54,4 +58,8 @@ endfunction call s:SetTagbar() +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/ftplugin/gomod.vim b/pack/acp/start/vim-go/ftplugin/gomod.vim new file mode 100644 index 0000000..b787170 --- /dev/null +++ b/pack/acp/start/vim-go/ftplugin/gomod.vim @@ -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 * '" + +setlocal formatoptions-=t + +setlocal comments=s1:/*,mb:*,ex:*/,:// +setlocal commentstring=//\ %s + +" Autocommands +" ============================================================================ + +augroup vim-go-gomod-buffer + autocmd! * + + autocmd BufWritePre 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 diff --git a/pack/acp/start/vim-go/ftplugin/gomod/commands.vim b/pack/acp/start/vim-go/ftplugin/gomod/commands.vim new file mode 100644 index 0000000..d6a7285 --- /dev/null +++ b/pack/acp/start/vim-go/ftplugin/gomod/commands.vim @@ -0,0 +1,3 @@ +command! -nargs=0 -range GoModFmt call go#mod#Format() + +command! -nargs=0 GoModFmtAutoSaveToggle call go#mod#ToggleModFmtAutoSave() diff --git a/pack/acp/start/vim-go/ftplugin/gomod/mappings.vim b/pack/acp/start/vim-go/ftplugin/gomod/mappings.vim new file mode 100644 index 0000000..c8664f6 --- /dev/null +++ b/pack/acp/start/vim-go/ftplugin/gomod/mappings.vim @@ -0,0 +1 @@ +nnoremap (go-mod-fmt) :call go#mod#Format() diff --git a/pack/acp/start/vim-go/gosnippets/UltiSnips/go.snippets b/pack/acp/start/vim-go/gosnippets/UltiSnips/go.snippets index 9a9f546..0a9b7ae 100644 --- a/pack/acp/start/vim-go/gosnippets/UltiSnips/go.snippets +++ b/pack/acp/start/vim-go/gosnippets/UltiSnips/go.snippets @@ -169,7 +169,7 @@ endsnippet # error multiple return snippet errn, "Error return with two return values" !b if err != nil { - return ${1:nil}, err + return ${1:nil}, ${2:err} } ${0} endsnippet @@ -363,6 +363,28 @@ func Test${1:Function}(t *testing.T) { } endsnippet +# test table snippet +snippet tt +var tests = []struct { + name string + expected string + given string +}{ + {"${1}", "${2}", "${3}",}, +} +for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T){ + actual := ${0:${VISUAL}}(tt.given) + if actual != tt.expected { + t.Errorf("$0(%s): expected %s, actual %s", tt.given, tt.expected, actual) + } + + }) +} +endsnippet + + snippet hf "http.HandlerFunc" !b func ${1:handler}(w http.ResponseWriter, r *http.Request) { ${0:fmt.Fprintf(w, "hello world")} diff --git a/pack/acp/start/vim-go/gosnippets/minisnip/_go_tt b/pack/acp/start/vim-go/gosnippets/minisnip/_go_tt new file mode 100644 index 0000000..602e017 --- /dev/null +++ b/pack/acp/start/vim-go/gosnippets/minisnip/_go_tt @@ -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) + } + + }) +} diff --git a/pack/acp/start/vim-go/gosnippets/snippets/go.snip b/pack/acp/start/vim-go/gosnippets/snippets/go.snip index 7a22cd2..3235fb4 100644 --- a/pack/acp/start/vim-go/gosnippets/snippets/go.snip +++ b/pack/acp/start/vim-go/gosnippets/snippets/go.snip @@ -315,6 +315,25 @@ abbr func TestXYZ(t *testing.T) { ... } func Test${1:Function}(t *testing.T) { ${0} } +# test table snippet +snippet tt +abbr var test = {...}{...} for {t.Run(){...}} + var tests = []struct { + name string + expected string + given string + }{ + {"${2}", "${3}", "${4}",}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T){ + actual := ${1:Function}(tt.given) + if actual != tt.expected { + t.Errorf("given(%s): expected %s, actual %s", tt.given, tt.expected, actual) + } + }) + } # test server snippet tsrv abbr ts := httptest.NewServer(...) diff --git a/pack/acp/start/vim-go/indent/go.vim b/pack/acp/start/vim-go/indent/go.vim index ba99d54..4c3ea04 100644 --- a/pack/acp/start/vim-go/indent/go.vim +++ b/pack/acp/start/vim-go/indent/go.vim @@ -24,18 +24,11 @@ if exists("*GoIndent") finish endif -" use shiftwidth function only if it's available -if exists('*shiftwidth') - func s:sw() - return shiftwidth() - endfunc -else - func s:sw() - return &sw - endfunc -endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim -function! GoIndent(lnum) +function! GoIndent(lnum) abort let prevlnum = prevnonblank(a:lnum-1) if prevlnum == 0 " top of file @@ -49,19 +42,30 @@ function! GoIndent(lnum) let ind = previ + for synid in synstack(a:lnum, 1) + if synIDattr(synid, 'name') == 'goRawString' + if prevl =~ '\%(\%(:\?=\)\|(\|,\)\s*`[^`]*$' + " previous line started a multi-line raw string + return 0 + endif + " return -1 to keep the current indent. + return -1 + endif + endfor + if prevl =~ '[({]\s*$' " previous line opened a block - let ind += s:sw() + let ind += shiftwidth() endif if prevl =~# '^\s*\(case .*\|default\):$' " previous line is part of a switch statement - let ind += s:sw() + let ind += shiftwidth() endif " TODO: handle if the previous line is a label. if thisl =~ '^\s*[)}]' " this line closed a block - let ind -= s:sw() + let ind -= shiftwidth() endif " Colons are tricky. @@ -69,10 +73,14 @@ function! GoIndent(lnum) " We ignore trying to deal with jump labels because (a) they're rare, and " (b) they're hard to disambiguate from a composite literal key. if thisl =~# '^\s*\(case .*\|default\):$' - let ind -= s:sw() + let ind -= shiftwidth() endif return ind endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/indent/gohtmltmpl.vim b/pack/acp/start/vim-go/indent/gohtmltmpl.vim index 864913d..8cc544a 100644 --- a/pack/acp/start/vim-go/indent/gohtmltmpl.vim +++ b/pack/acp/start/vim-go/indent/gohtmltmpl.vim @@ -13,6 +13,10 @@ if exists("*GetGoHTMLTmplIndent") finish endif +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + function! GetGoHTMLTmplIndent(lnum) " Get HTML indent if exists('*HtmlIndent') @@ -43,4 +47,8 @@ function! GetGoHTMLTmplIndent(lnum) return ind endfunction +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/plugin/go.vim b/pack/acp/start/vim-go/plugin/go.vim index 513ce36..c68526e 100644 --- a/pack/acp/start/vim-go/plugin/go.vim +++ b/pack/acp/start/vim-go/plugin/go.vim @@ -4,49 +4,69 @@ if exists("g:go_loaded_install") endif let g:go_loaded_install = 1 -" Not using the has('patch-7.4.1689') syntax because that wasn't added until -" 7.4.237, and we want to be sure this works for everyone (this is also why -" we're not using utils#EchoError()). -" -" Version 7.4.1689 was chosen because that's what the most recent Ubuntu LTS -" release (16.04) uses. -if - \ get(g:, 'go_version_warning', 1) != 0 && - \ (v:version < 704 || (v:version == 704 && !has('patch1689'))) - \ && !has('nvim') - echohl Error - echom "vim-go requires Vim 7.4.1689 or Neovim, but you're using an older version." - echom "Please update your Vim for the best vim-go experience." - echom "If you really want to continue you can set this to make the error go away:" - echom " let g:go_version_warning = 0" - echom "Note that some features may error out or behave incorrectly." - echom "Please do not report bugs unless you're using Vim 7.4.1689 or newer." - echohl None +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim - " Make sure people see this. - sleep 2 -endif +function! s:checkVersion() abort + " Not using the has('patch-7.4.2009') syntax because that wasn't added until + " 7.4.237, and we want to be sure this works for everyone (this is also why + " we're not using utils#EchoError()). + " + " Version 7.4.2009 was chosen because that's greater than what the most recent Ubuntu LTS + " release (16.04) uses and has a couple of features we need (e.g. execute() + " and :message clear). + + let l:unsupported = 0 + if go#config#VersionWarning() != 0 + if has('nvim') + let l:unsupported = !has('nvim-0.3.1') + else + let l:unsupported = (v:version < 704 || (v:version == 704 && !has('patch2009'))) + endif + + if l:unsupported == 1 + echohl Error + echom "vim-go requires Vim 7.4.2009 or Neovim 0.3.1, but you're using an older version." + echom "Please update your Vim for the best vim-go experience." + echom "If you really want to continue you can set this to make the error go away:" + echom " let g:go_version_warning = 0" + echom "Note that some features may error out or behave incorrectly." + echom "Please do not report bugs unless you're using Vim 7.4.2009 or newer or Neovim 0.3.1." + echohl None + + " Make sure people see this. + sleep 2 + endif + endif +endfunction + +call s:checkVersion() " these packages are used by vim-go and can be automatically installed if -" needed by the user with GoInstallBinaries +" needed by the user with GoInstallBinaries. let s:packages = { \ 'asmfmt': ['github.com/klauspost/asmfmt/cmd/asmfmt'], - \ 'dlv': ['github.com/derekparker/delve/cmd/dlv'], + \ 'dlv': ['github.com/go-delve/delve/cmd/dlv'], \ 'errcheck': ['github.com/kisielk/errcheck'], \ 'fillstruct': ['github.com/davidrjenni/reftools/cmd/fillstruct'], - \ 'gocode': ['github.com/nsf/gocode', {'windows': '-ldflags -H=windowsgui'}], + \ 'gocode': ['github.com/mdempsky/gocode', {'windows': ['-ldflags', '-H=windowsgui']}], + \ 'gocode-gomod': ['github.com/stamblerre/gocode'], \ 'godef': ['github.com/rogpeppe/godef'], \ 'gogetdoc': ['github.com/zmb3/gogetdoc'], \ 'goimports': ['golang.org/x/tools/cmd/goimports'], - \ 'golint': ['github.com/golang/lint/golint'], + \ 'golint': ['golang.org/x/lint/golint'], + \ 'gopls': ['golang.org/x/tools/cmd/gopls'], \ 'gometalinter': ['github.com/alecthomas/gometalinter'], + \ 'golangci-lint': ['github.com/golangci/golangci-lint/cmd/golangci-lint'], \ 'gomodifytags': ['github.com/fatih/gomodifytags'], \ 'gorename': ['golang.org/x/tools/cmd/gorename'], \ 'gotags': ['github.com/jstemmer/gotags'], \ 'guru': ['golang.org/x/tools/cmd/guru'], \ 'impl': ['github.com/josharian/impl'], - \ 'keyify': ['github.com/dominikh/go-tools/cmd/keyify'], + \ 'keyify': ['honnef.co/go/tools/cmd/keyify'], \ 'motion': ['github.com/fatih/motion'], + \ 'iferr': ['github.com/koron/iferr'], \ } " These commands are available on any filetypes @@ -97,16 +117,9 @@ function! s:GoInstallBinaries(updateBinaries, ...) set noshellslash endif - let cmd = "go get -v " + let l:dl_cmd = ['go', 'get', '-v', '-d'] if get(g:, "go_get_update", 1) != 0 - let cmd .= "-u " - endif - - let s:go_version = matchstr(go#util#System("go version"), '\d.\d.\d') - - " https://github.com/golang/go/issues/10791 - if s:go_version > "1.4.0" && s:go_version < "1.5.0" - let cmd .= "-f " + let l:dl_cmd += ['-u'] endif " Filter packages from arguments (if any). @@ -131,13 +144,22 @@ function! s:GoInstallBinaries(updateBinaries, ...) for [binary, pkg] in items(l:packages) let l:importPath = pkg[0] - let l:goGetFlags = len(pkg) > 1 ? get(pkg[1], l:platform, '') : '' - let binname = "go_" . binary . "_bin" + let l:run_cmd = copy(l:dl_cmd) + if len(l:pkg) > 1 && get(l:pkg[1], l:platform, '') isnot '' + let l:run_cmd += get(l:pkg[1], l:platform, '') + endif - let bin = binary - if exists("g:{binname}") - let bin = g:{binname} + let bin_setting_name = "go_" . binary . "_bin" + + if exists("g:{bin_setting_name}") + let bin = g:{bin_setting_name} + else + if go#util#IsWin() + let bin = binary . '.exe' + else + let bin = binary + endif endif if !executable(bin) || a:updateBinaries == 1 @@ -147,9 +169,17 @@ function! s:GoInstallBinaries(updateBinaries, ...) echo "vim-go: ". binary ." not found. Installing ". importPath . " to folder " . go_bin_path endif - let out = go#util#System(printf('%s %s %s', cmd, l:goGetFlags, shellescape(importPath))) - if go#util#ShellError() != 0 - echom "Error installing " . importPath . ": " . out + " first download the binary + let [l:out, l:err] = go#util#Exec(l:run_cmd + [l:importPath]) + if l:err + echom "Error downloading " . l:importPath . ": " . l:out + endif + + " and then build and install it + let l:build_cmd = ['go', 'build', '-o', go_bin_path . go#util#PathSep() . bin, l:importPath] + let [l:out, l:err] = go#util#Exec(l:build_cmd) + if l:err + echom "Error installing " . l:importPath . ": " . l:out endif endif endfor @@ -159,6 +189,12 @@ function! s:GoInstallBinaries(updateBinaries, ...) if resetshellslash set shellslash endif + + if a:updateBinaries == 1 + call go#util#EchoInfo('updating finished!') + else + call go#util#EchoInfo('installing finished!') + endif endfunction " CheckBinaries checks if the necessary binaries to install the Go tool @@ -178,102 +214,53 @@ endfunction " Autocommands " ============================================================================ " -function! s:echo_go_info() - if !get(g:, "go_echo_go_info", 1) + +" We take care to preserve the user's fileencodings and fileformats, +" because those settings are global (not buffer local), yet we want +" to override them for loading Go files, which are defined to be UTF-8. +let s:current_fileformats = '' +let s:current_fileencodings = '' + +" define fileencodings to open as utf-8 encoding even if it's ascii. +function! s:gofiletype_pre() + let s:current_fileformats = &g:fileformats + let s:current_fileencodings = &g:fileencodings + set fileencodings=utf-8 fileformats=unix +endfunction + +" restore fileencodings as others +function! s:gofiletype_post() + let &g:fileformats = s:current_fileformats + let &g:fileencodings = s:current_fileencodings +endfunction + +function! s:register() + if !(&modifiable && expand('') ==# 'go') 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! s:auto_type_info() - " GoInfo automatic update - if get(g:, "go_auto_type_info", 0) - call go#tool#Info(1) - endif -endfunction - -function! s:auto_sameids() - " GoSameId automatic update - if get(g:, "go_auto_sameids", 0) - call go#guru#SameIds() - endif -endfunction - -function! s:fmt_autosave() - " Go code formatting on save - if get(g:, "go_fmt_autosave", 1) - call go#fmt#Format(-1) - endif -endfunction - -function! s:asmfmt_autosave() - " Go asm formatting on save - if get(g:, "go_asmfmt_autosave", 0) - call go#asmfmt#Format() - endif -endfunction - -function! s:metalinter_autosave() - " run gometalinter on save - if get(g:, "go_metalinter_autosave", 0) - call go#lint#Gometa(1) - endif -endfunction - -function! s:template_autocreate() - " create new template from scratch - if get(g:, "go_template_autocreate", 1) - call go#template#create() - endif + call go#lsp#DidOpen(expand(':p')) endfunction augroup vim-go autocmd! - autocmd CursorHold *.go call s:auto_type_info() - autocmd CursorHold *.go call s:auto_sameids() + autocmd BufNewFile *.go if &modifiable | setlocal fileencoding=utf-8 fileformat=unix | endif + autocmd BufNewFile *.go call go#auto#template_autocreate() + autocmd BufRead *.go call s:gofiletype_pre() + autocmd BufReadPost *.go call s:gofiletype_post() - " Echo the identifier information when completion is done. Useful to see - " the signature of a function, etc... - if exists('##CompleteDone') - autocmd CompleteDone *.go call s:echo_go_info() + autocmd BufNewFile *.s if &modifiable | setlocal fileencoding=utf-8 fileformat=unix | endif + autocmd BufRead *.s call s:gofiletype_pre() + autocmd BufReadPost *.s call s:gofiletype_post() + + if go#util#has_job() + autocmd FileType * call s:register() endif - - autocmd BufWritePre *.go call s:fmt_autosave() - autocmd BufWritePre *.s call s:asmfmt_autosave() - autocmd BufWritePost *.go call s:metalinter_autosave() - autocmd BufNewFile *.go call s:template_autocreate() - " 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 *.go call go#guru#ClearSameIds() - - autocmd BufEnter *.go - \ if get(g:, 'go_autodetect_gopath', 0) && !exists('b:old_gopath') - \| let b:old_gopath = exists('$GOPATH') ? $GOPATH : -1 - \| let $GOPATH = go#path#Detect() - \| endif - autocmd BufLeave *.go - \ if exists('b:old_gopath') - \| if b:old_gopath isnot -1 - \| let $GOPATH = b:old_gopath - \| endif - \| unlet b:old_gopath - \| endif augroup end +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/scripts/bench-syntax b/pack/acp/start/vim-go/scripts/bench-syntax new file mode 100644 index 0000000..25e89ee --- /dev/null +++ b/pack/acp/start/vim-go/scripts/bench-syntax @@ -0,0 +1,44 @@ +#!/bin/sh +# +# Benchmark the syntax/go.vim file. +# +# The first argument is the Vim version to test (as in run-vim), the rest of the +# agument are g:go_highlight_* settings (without that prefix). You can use "ALL" +# to set all the options. +# + +set -euC +vimgodir=$(cd -P "$(dirname "$0")/.." > /dev/null && pwd) +cd "$vimgodir" + +if [ -z "${1:-}" ]; then + echo "unknown version: '${1:-}'" + echo "First argument must be 'vim-7.4', 'vim-8.0', or 'nvim'." + exit 1 +fi + +if [ -z "${2:-}" ]; then + echo "file not set" + echo "Second argument must be a Go file to benchmark, as 'filename:linenr'" + exit 1 +fi + +vim=$1 +file="$(echo "$2" | cut -d : -f 1)" +line="$(echo "$2" | cut -d : -f 2)" +if [ -z "$line" -o "$line" = "$file" ]; then + echo "Second argument must be a Go file to benchmark, as 'filename:linenr'" + exit 1 +fi + +shift; shift +export RUNBENCH_SETTINGS=$@ +export RUNBENCH_OUT="$(mktemp -p "${TMPDIR:-/tmp}" vimgo-bench.XXXXX)" + +"$vimgodir/scripts/run-vim" $vim \ + +"silent e $file" \ + +"normal! ${line}G" \ + -S ./scripts/runbench.vim + +echo "Report written to:" +echo "$RUNBENCH_OUT" diff --git a/pack/acp/start/vim-go/scripts/docker-test b/pack/acp/start/vim-go/scripts/docker-test old mode 100755 new mode 100644 index 2b56ad8..e984f61 --- a/pack/acp/start/vim-go/scripts/docker-test +++ b/pack/acp/start/vim-go/scripts/docker-test @@ -8,6 +8,8 @@ vimgodir=$(cd -P "$(dirname "$0")/.." > /dev/null && pwd) cd "$vimgodir" docker build --tag vim-go-test . -docker run --rm vim-go-test +# seccomp=confined is required for dlv to run in a container, hence it's +# required for vim-go's debug tests. +docker run --rm --security-opt="seccomp=unconfined" vim-go-test # vim:ts=2:sts=2:sw=2:et diff --git a/pack/acp/start/vim-go/scripts/install-vim b/pack/acp/start/vim-go/scripts/install-vim old mode 100755 new mode 100644 index 7b4e7a4..709fcca --- a/pack/acp/start/vim-go/scripts/install-vim +++ b/pack/acp/start/vim-go/scripts/install-vim @@ -16,8 +16,7 @@ vim=${1:-} case "$vim" in "vim-7.4") - # This is what the most recent Ubuntu LTS (16.04) ships with. - tag="v7.4.1689" + tag="v7.4.2009" giturl="https://github.com/vim/vim" ;; @@ -25,13 +24,13 @@ case "$vim" in # This follows the version in Arch Linux. Vim's master branch isn't always # stable, and we don't want to have the build fail because Vim introduced a # bug. - tag="v8.0.1176" + tag="v8.0.1542" giturl="https://github.com/vim/vim" ;; "nvim") # Use latest stable version. - tag="v0.2.0" + tag="v0.3.1" giturl="https://github.com/neovim/neovim" ;; @@ -92,6 +91,7 @@ fi # Make sure all Go tools and other dependencies are installed. echo "Installing Go binaries" export GOPATH=$installdir +export GO111MODULE=off export PATH=${GOPATH}/bin:$PATH "$vimgodir/scripts/run-vim" $vim +':silent :GoUpdateBinaries' +':qa' diff --git a/pack/acp/start/vim-go/scripts/lint b/pack/acp/start/vim-go/scripts/lint old mode 100755 new mode 100644 diff --git a/pack/acp/start/vim-go/scripts/run-vim b/pack/acp/start/vim-go/scripts/run-vim old mode 100755 new mode 100644 index f020ea9..1887807 --- a/pack/acp/start/vim-go/scripts/run-vim +++ b/pack/acp/start/vim-go/scripts/run-vim @@ -23,6 +23,7 @@ fi dir="/tmp/vim-go-test/$1-install" export GOPATH=$dir +export GO111MODULE=auto export PATH=${GOPATH}/bin:$PATH shift @@ -34,13 +35,13 @@ fi if [ $coverage -eq 1 ]; then covimerage -q run --report-file /tmp/vim-go-test/cov-profile.txt --append \ $dir/bin/vim --noplugin -u NONE -N \ - +"set shm+=WAFI rtp=$dir/share/vim/vimgo packpath=$dir/share/vim/vimgo,$vimgodir" \ + +"set shm+=WAFI rtp^=$vimgodir packpath=$dir/share/vim/vimgo" \ +'filetype plugin indent on' \ +'packloadall!' \ "$@" else $dir/bin/vim --noplugin -u NONE -N \ - +"set shm+=WAFI rtp=$dir/share/vim/vimgo packpath=$dir/share/vim/vimgo,$vimgodir" \ + +"set shm+=WAFI rtp^=$vimgodir packpath=$dir/share/vim/vimgo" \ +'filetype plugin indent on' \ +'packloadall!' \ "$@" diff --git a/pack/acp/start/vim-go/scripts/runbench.vim b/pack/acp/start/vim-go/scripts/runbench.vim new file mode 100644 index 0000000..e1c6c88 --- /dev/null +++ b/pack/acp/start/vim-go/scripts/runbench.vim @@ -0,0 +1,37 @@ +" vint: -ProhibitSetNoCompatible + +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + +set nocompatible nomore shellslash encoding=utf-8 shortmess+=WIF +lang mess C + +if $RUNBENCH_SETTINGS is? 'all' + let $RUNBENCH_SETTINGS = join(['array_whitespace_error', 'build_constraints', + \ 'chan_whitespace_error', 'extra_types', 'fields', 'format_strings', + \ 'function_arguments', 'function_calls', 'functions', 'generate_tags', + \ 'operators', 'space_tab_error', 'string_spellcheck', + \ 'trailing_whitespace_error', 'types', 'variable_assignments', + \ 'variable_declarations'], ' ') +endif + +for s:s in split($RUNBENCH_SETTINGS, ' ') + call execute('let g:go_highlight_' . s:s . ' = 1') +endfor + +filetype plugin indent on +syntax on + +syntime on +redraw! +let s:report = execute('syntime report') +execute ':e ' . fnameescape($RUNBENCH_OUT) +call setline('.', split(s:report, '\n')) +wq + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/scripts/runtest.vim b/pack/acp/start/vim-go/scripts/runtest.vim index 23d9186..9d20b76 100644 --- a/pack/acp/start/vim-go/scripts/runtest.vim +++ b/pack/acp/start/vim-go/scripts/runtest.vim @@ -2,6 +2,11 @@ " English. " vint: -ProhibitSetNoCompatible + +" don't spam the user when Vim is started in Vi compatibility mode +let s:cpo_save = &cpo +set cpo&vim + set nocompatible nomore shellslash encoding=utf-8 shortmess+=WIF lang mess C @@ -14,6 +19,16 @@ let s:gopath = $GOPATH if !exists('g:test_verbose') let g:test_verbose = 0 endif +let g:go_echo_command_info = 0 + +function! s:logmessages() abort + " Add all messages (usually errors). + redir => s:mess + silent messages + redir END + let s:logs = s:logs + filter(split(s:mess, "\n"), 'v:val !~ "^Messages maintainer"') + silent messages clear +endfunction " Source the passed test file. source % @@ -21,7 +36,8 @@ source % " cd into the folder of the test file. let s:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let s:testfile = expand('%:t') -execute s:cd . expand('%:p:h') +let s:dir = expand('%:p:h') +execute s:cd . s:dir " Export root path to vim-go dir. let g:vim_go_root = fnamemodify(getcwd(), ':p') @@ -30,12 +46,14 @@ let g:vim_go_root = fnamemodify(getcwd(), ':p') redir @q silent function /^Test_ redir END -let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g')) +let s:tests = split(substitute(@q, 'function \(\k\+()\)', '\1', 'g')) +" log any messages already accumulated. +call s:logmessages() " Iterate over all tests and execute them. for s:test in sort(s:tests) - " Since we extract the tests from a regexp the "abort" keyword is also in the - " list, which is not a test name :-) + " Since we extract the tests from a regexp the "abort" keyword is also in + " the list, which is not a test name :-) if s:test == 'abort' continue endif @@ -52,10 +70,14 @@ for s:test in sort(s:tests) " Restore GOPATH after each test. let $GOPATH = s:gopath + " Restore the working directory after each test. + execute s:cd . s:dir let s:elapsed_time = substitute(reltimestr(reltime(s:started)), '^\s*\(.\{-}\)\s*$', '\1', '') let s:done += 1 + call s:logmessages() + if len(v:errors) > 0 let s:fail += 1 call add(s:logs, printf("--- FAIL %s (%ss)", s:test[:-3], s:elapsed_time)) @@ -78,12 +100,6 @@ endif let s:total_elapsed_time = substitute(reltimestr(reltime(s:total_started)), '^\s*\(.\{-}\)\s*$', '\1', '') -" Add all messages (usually errors). -redir => s:mess - silent messages -redir END -let s:logs = s:logs + filter(split(s:mess, "\n"), 'v:val !~ "^Messages maintainer"') - " Also store all internal messages from s:logs as well. silent! split /tmp/vim-go-test/test.tmp call append(line('$'), s:logs) @@ -100,4 +116,8 @@ silent! write " Our work here is done. qall! +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + " vim:ts=2:sts=2:sw=2:et diff --git a/pack/acp/start/vim-go/scripts/test b/pack/acp/start/vim-go/scripts/test old mode 100755 new mode 100644 diff --git a/pack/acp/start/vim-go/syntax/go.vim b/pack/acp/start/vim-go/syntax/go.vim index 8769dff..04af07b 100644 --- a/pack/acp/start/vim-go/syntax/go.vim +++ b/pack/acp/start/vim-go/syntax/go.vim @@ -9,102 +9,6 @@ if exists("b:current_syntax") finish endif -" Set settings to default values. -if !exists("g:go_highlight_array_whitespace_error") - let g:go_highlight_array_whitespace_error = 0 -endif - -if !exists("g:go_highlight_chan_whitespace_error") - let g:go_highlight_chan_whitespace_error = 0 -endif - -if !exists("g:go_highlight_extra_types") - let g:go_highlight_extra_types = 0 -endif - -if !exists("g:go_highlight_space_tab_error") - let g:go_highlight_space_tab_error = 0 -endif - -if !exists("g:go_highlight_trailing_whitespace_error") - let g:go_highlight_trailing_whitespace_error = 0 -endif - -if !exists("g:go_highlight_operators") - let g:go_highlight_operators = 0 -endif - -if !exists("g:go_highlight_functions") - let g:go_highlight_functions = 0 -endif - -if !exists("g:go_highlight_function_arguments") - let g:go_highlight_function_arguments = 0 -endif - -if !exists("g:go_highlight_function_calls") - let g:go_highlight_function_calls = 0 -endif - -if !exists("g:go_highlight_fields") - let g:go_highlight_fields = 0 -endif - -if !exists("g:go_highlight_types") - let g:go_highlight_types = 0 -endif - -if !exists("g:go_highlight_build_constraints") - let g:go_highlight_build_constraints = 0 -endif - -if !exists("g:go_highlight_string_spellcheck") - let g:go_highlight_string_spellcheck = 1 -endif - -if !exists("g:go_highlight_format_strings") - let g:go_highlight_format_strings = 1 -endif - -if !exists("g:go_highlight_generate_tags") - let g:go_highlight_generate_tags = 0 -endif - -if !exists("g:go_highlight_variable_assignments") - let g:go_highlight_variable_assignments = 0 -endif - -if !exists("g:go_highlight_variable_declarations") - let g:go_highlight_variable_declarations = 0 -endif - -let s:fold_block = 1 -let s:fold_import = 1 -let s:fold_varconst = 1 -let s:fold_package_comment = 1 -let s:fold_comment = 0 - -if exists("g:go_fold_enable") - " Enabled by default. - if index(g:go_fold_enable, 'block') == -1 - let s:fold_block = 0 - endif - if index(g:go_fold_enable, 'import') == -1 - let s:fold_import = 0 - endif - if index(g:go_fold_enable, 'varconst') == -1 - let s:fold_varconst = 0 - endif - if index(g:go_fold_enable, 'package_comment') == -1 - let s:fold_package_comment = 0 - endif - - " Disabled by default. - if index(g:go_fold_enable, 'comment') > -1 - let s:fold_comment = 1 - endif -endif - syn case match syn keyword goPackage package @@ -142,14 +46,13 @@ hi def link goUnsignedInts Type hi def link goFloats Type hi def link goComplexes Type - " Predefined functions and values -syn match goBuiltins /\<\v(append|cap|close|complex|copy|delete|imag|len)\ze\(/ -syn match goBuiltins /\<\v(make|new|panic|print|println|real|recover)\ze\(/ +syn keyword goBuiltins append cap close complex copy delete imag len +syn keyword goBuiltins make new panic print println real recover syn keyword goBoolean true false syn keyword goPredefinedIdentifiers nil iota -hi def link goBuiltins Keyword +hi def link goBuiltins Identifier hi def link goBoolean Boolean hi def link goPredefinedIdentifiers goBoolean @@ -158,7 +61,7 @@ syn keyword goTodo contained TODO FIXME XXX BUG syn cluster goCommentGroup contains=goTodo syn region goComment start="//" end="$" contains=goGenerate,@goCommentGroup,@Spell -if s:fold_comment +if go#config#FoldEnable('comment') syn region goComment start="/\*" end="\*/" contains=@goCommentGroup,@Spell fold syn match goComment "\v(^\s*//.*\n)+" contains=goGenerate,@goCommentGroup,@Spell fold else @@ -168,8 +71,8 @@ endif hi def link goComment Comment hi def link goTodo Todo -if g:go_highlight_generate_tags != 0 - syn match goGenerateVariables contained /\(\$GOARCH\|\$GOOS\|\$GOFILE\|\$GOLINE\|\$GOPACKAGE\|\$DOLLAR\)\>/ +if go#config#HighlightGenerateTags() + syn match goGenerateVariables contained /\%(\$GOARCH\|\$GOOS\|\$GOFILE\|\$GOLINE\|\$GOPACKAGE\|\$DOLLAR\)\>/ syn region goGenerate start="^\s*//go:generate" end="$" contains=goGenerateVariables hi def link goGenerate PreProc hi def link goGenerateVariables Special @@ -193,7 +96,7 @@ hi def link goEscapeError Error " Strings and their contents syn cluster goStringGroup contains=goEscapeOctal,goEscapeC,goEscapeX,goEscapeU,goEscapeBigU,goEscapeError -if g:go_highlight_string_spellcheck != 0 +if go#config#HighlightStringSpellcheck() syn region goString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@goStringGroup,@Spell syn region goRawString start=+`+ end=+`+ contains=@Spell else @@ -201,7 +104,7 @@ else syn region goRawString start=+`+ end=+`+ endif -if g:go_highlight_format_strings != 0 +if go#config#HighlightFormatStrings() " [n] notation is valid for specifying explicit argument indexes " 1. Match a literal % not preceded by a %. " 2. Match any number of -, #, 0, space, or + @@ -210,7 +113,7 @@ if g:go_highlight_format_strings != 0 " 5. Match [n] or nothing before a verb " 6. Match a formatting verb syn match goFormatSpecifier /\ - \([^%]\(%%\)*\)\ + \%([^%]\%(%%\)*\)\ \@<=%[-#0 +]*\ \%(\%(\%(\[\d\+\]\)\=\*\)\|\d\+\)\=\ \%(\.\%(\%(\%(\[\d\+\]\)\=\*\)\|\d\+\)\=\)\=\ @@ -229,34 +132,34 @@ hi def link goCharacter Character " Regions syn region goParen start='(' end=')' transparent -if s:fold_block +if go#config#FoldEnable('block') syn region goBlock start="{" end="}" transparent fold else syn region goBlock start="{" end="}" transparent endif " import -if s:fold_import +if go#config#FoldEnable('import') syn region goImport start='import (' end=')' transparent fold contains=goImport,goString,goComment else syn region goImport start='import (' end=')' transparent contains=goImport,goString,goComment endif " var, const -if s:fold_varconst +if go#config#FoldEnable('varconst') syn region goVar start='var (' end='^\s*)$' transparent fold - \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goArgumentName,goArgumentType,goSimpleArguments + \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goParamName,goParamType,goSimpleParams,goPointerOperator syn region goConst start='const (' end='^\s*)$' transparent fold - \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goArgumentName,goArgumentType,goSimpleArguments + \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goParamName,goParamType,goSimpleParams,goPointerOperator else syn region goVar start='var (' end='^\s*)$' transparent - \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goArgumentName,goArgumentType,goSimpleArguments + \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goParamName,goParamType,goSimpleParams,goPointerOperator syn region goConst start='const (' end='^\s*)$' transparent - \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goArgumentName,goArgumentType,goSimpleArguments + \ contains=ALLBUT,goParen,goBlock,goFunction,goTypeName,goReceiverType,goReceiverVar,goParamName,goParamType,goSimpleParams,goPointerOperator endif " Single-line var, const, and import. -syn match goSingleDecl /\(import\|var\|const\) [^(]\@=/ contains=goImport,goVar,goConst +syn match goSingleDecl /\%(import\|var\|const\) [^(]\@=/ contains=goImport,goVar,goConst " Integers syn match goDecimalInt "\<-\=\d\+\%([Ee][-+]\=\d\+\)\=\>" @@ -286,43 +189,44 @@ hi def link goImaginary Number hi def link goImaginaryFloat Float " Spaces after "[]" -if g:go_highlight_array_whitespace_error != 0 - syn match goSpaceError display "\(\[\]\)\@<=\s\+" +if go#config#HighlightArrayWhitespaceError() + syn match goSpaceError display "\%(\[\]\)\@<=\s\+" endif " Spacing errors around the 'chan' keyword -if g:go_highlight_chan_whitespace_error != 0 +if go#config#HighlightChanWhitespaceError() " receive-only annotation on chan type " " \(\\)\@\)\@\)\@=" + syn match goSpaceError display "\%(\%(\\)\@\)\@=" " send-only annotation on chan type " " \(<-\)\@ (only pick chan when it doesn't come after an arrow) " this prevents picking up '<-chan <-chan' but not 'chan <-' - syn match goSpaceError display "\(\(<-\)\@\)\@<=\s\+\(<-\)\@=" + syn match goSpaceError display "\%(\%(<-\)\@\)\@<=\s\+\%(<-\)\@=" " value-ignoring receives in a few contexts - syn match goSpaceError display "\(\(^\|[={(,;]\)\s*<-\)\@<=\s\+" + syn match goSpaceError display "\%(\%(^\|[={(,;]\)\s*<-\)\@<=\s\+" endif " Extra types commonly seen -if g:go_highlight_extra_types != 0 - syn match goExtraType /\/ - syn match goExtraType /\/ - syn match goExtraType /\/ +if go#config#HighlightExtraTypes() + syn match goExtraType /\/ + syn match goExtraType /\/ + syn match goExtraType /\/ + syn match goExtraType /\/ syn match goExtraType /\/ endif " Space-tab error -if g:go_highlight_space_tab_error != 0 +if go#config#HighlightSpaceTabError() syn match goSpaceError display " \+\t"me=e-1 endif " Trailing white space error -if g:go_highlight_trailing_whitespace_error != 0 +if go#config#HighlightTrailingWhitespaceError() syn match goSpaceError display excludenl "\s\+$" endif @@ -340,7 +244,7 @@ hi def link goTodo Todo syn match goVarArgs /\.\.\./ " Operators; -if g:go_highlight_operators != 0 +if go#config#HighlightOperators() " match single-char operators: - + % < > ! & | ^ * = " and corresponding two-char operators: -= += %= <= >= != &= |= ^= *= == syn match goOperator /[-+%<>!&|^*=]=\?/ @@ -359,44 +263,58 @@ endif hi def link goOperator Operator " Functions; -if g:go_highlight_functions isnot 0 || g:go_highlight_function_arguments isnot 0 - syn match goDeclaration /\/ nextgroup=goReceiver,goFunction,goSimpleArguments skipwhite skipnl - syn match goReceiverVar /\w\+\ze\s\+\(\w\|\*\)/ nextgroup=goPointerOperator,goReceiverType skipwhite skipnl contained +if go#config#HighlightFunctions() || go#config#HighlightFunctionParameters() + syn match goDeclaration /\/ nextgroup=goReceiver,goFunction,goSimpleParams skipwhite skipnl + syn match goReceiverVar /\w\+\ze\s\+\%(\w\|\*\)/ nextgroup=goPointerOperator,goReceiverType skipwhite skipnl contained syn match goPointerOperator /\*/ nextgroup=goReceiverType contained skipwhite skipnl - syn match goFunction /\w\+/ nextgroup=goSimpleArguments contained skipwhite skipnl + syn match goFunction /\w\+/ nextgroup=goSimpleParams contained skipwhite skipnl syn match goReceiverType /\w\+/ contained -if g:go_highlight_function_arguments isnot 0 - syn match goSimpleArguments /(\(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goArgumentName nextgroup=goSimpleArguments skipwhite skipnl - syn match goArgumentName /\w\+\(\s*,\s*\w\+\)*\ze\s\+\(\w\|\.\|\*\|\[\)/ contained nextgroup=goArgumentType skipwhite skipnl - syn match goArgumentType /\([^,)]\|\_s\)\+,\?/ contained nextgroup=goArgumentName skipwhite skipnl - \ contains=goVarArgs,goType,goSignedInts,goUnsignedInts,goFloats,goComplexes,goDeclType,goBlock - hi def link goReceiverVar goArgumentName - hi def link goArgumentName Identifier -endif - syn match goReceiver /(\s*\w\+\(\s\+\*\?\s*\w\+\)\?\s*)\ze\s*\w/ contained nextgroup=goFunction contains=goReceiverVar skipwhite skipnl + if go#config#HighlightFunctionParameters() + syn match goSimpleParams /(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goParamName,goType nextgroup=goFunctionReturn skipwhite skipnl + syn match goFunctionReturn /(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ contained contains=goParamName,goType skipwhite skipnl + syn match goParamName /\w\+\%(\s*,\s*\w\+\)*\ze\s\+\%(\w\|\.\|\*\|\[\)/ contained nextgroup=goParamType skipwhite skipnl + syn match goParamType /\%([^,)]\|\_s\)\+,\?/ contained nextgroup=goParamName skipwhite skipnl + \ contains=goVarArgs,goType,goSignedInts,goUnsignedInts,goFloats,goComplexes,goDeclType,goBlock + hi def link goReceiverVar goParamName + hi def link goParamName Identifier + endif + syn match goReceiver /(\s*\w\+\%(\s\+\*\?\s*\w\+\)\?\s*)\ze\s*\w/ contained nextgroup=goFunction contains=goReceiverVar skipwhite skipnl else syn keyword goDeclaration func endif hi def link goFunction Function " Function calls; -if g:go_highlight_function_calls != 0 +if go#config#HighlightFunctionCalls() syn match goFunctionCall /\w\+\ze(/ contains=goBuiltins,goDeclaration endif hi def link goFunctionCall Type " Fields; -if g:go_highlight_fields != 0 - syn match goField /\.\w\+\([.\ \n\r\:\)\[,]\)\@=/hs=s+1 +if go#config#HighlightFields() + " 1. Match a sequence of word characters coming after a '.' + " 2. Require the following but dont match it: ( \@= see :h E59) + " - The symbols: / - + * % OR + " - The symbols: [] {} <> ) OR + " - The symbols: \n \r space OR + " - The symbols: , : . + " 3. Have the start of highlight (hs) be the start of matched + " pattern (s) offsetted one to the right (+1) (see :h E401) + syn match goField /\.\w\+\ + \%(\%([\/\-\+*%]\)\|\ + \%([\[\]{}<\>\)]\)\|\ + \%([\!=\^|&]\)\|\ + \%([\n\r\ ]\)\|\ + \%([,\:.]\)\)\@=/hs=s+1 endif hi def link goField Identifier " Structs & Interfaces; -if g:go_highlight_types != 0 +if go#config#HighlightTypes() syn match goTypeConstructor /\<\w\+{\@=/ syn match goTypeDecl /\/ nextgroup=goTypeName skipwhite skipnl syn match goTypeName /\w\+/ contained nextgroup=goDeclType skipwhite skipnl - syn match goDeclType /\<\(interface\|struct\)\>/ skipwhite skipnl + syn match goDeclType /\<\%(interface\|struct\)\>/ skipwhite skipnl hi def link goReceiverType Type else syn keyword goDeclType struct interface @@ -408,19 +326,19 @@ hi def link goTypeDecl Keyword hi def link goDeclType Keyword " Variable Assignments -if g:go_highlight_variable_assignments != 0 +if go#config#HighlightVariableAssignments() syn match goVarAssign /\v[_.[:alnum:]]+(,\s*[_.[:alnum:]]+)*\ze(\s*([-^+|^\/%&]|\*|\<\<|\>\>|\&\^)?\=[^=])/ hi def link goVarAssign Special endif " Variable Declarations -if g:go_highlight_variable_declarations != 0 +if go#config#HighlightVariableDeclarations() syn match goVarDefs /\v\w+(,\s*\w+)*\ze(\s*:\=)/ hi def link goVarDefs Special endif " Build Constraints -if g:go_highlight_build_constraints != 0 +if go#config#HighlightBuildConstraints() syn match goBuildKeyword display contained "+build" " Highlight the known values of GOOS, GOARCH, and other +build options. syn keyword goBuildDirectives contained @@ -442,7 +360,7 @@ if g:go_highlight_build_constraints != 0 hi def link goBuildKeyword PreProc endif -if g:go_highlight_build_constraints != 0 || s:fold_package_comment +if go#config#HighlightBuildConstraints() || go#config#FoldEnable('package_comment') " One or more line comments that are followed immediately by a "package" " declaration are treated like package documentation, so these must be " matched as comments to avoid looking like working build constraints. @@ -451,11 +369,11 @@ if g:go_highlight_build_constraints != 0 || s:fold_package_comment exe 'syn region goPackageComment start=/\v(\/\/.*\n)+\s*package/' \ . ' end=/\v\n\s*package/he=e-7,me=e-7,re=e-7' \ . ' contains=@goCommentGroup,@Spell' - \ . (s:fold_package_comment ? ' fold' : '') - exe 'syn region goPackageComment start=/\v\/\*.*\n(.*\n)*\s*\*\/\npackage/' + \ . (go#config#FoldEnable('package_comment') ? ' fold' : '') + exe 'syn region goPackageComment start=/\v^\s*\/\*.*\n(.*\n)*\s*\*\/\npackage/' \ . ' end=/\v\*\/\n\s*package/he=e-7,me=e-7,re=e-7' \ . ' contains=@goCommentGroup,@Spell' - \ . (s:fold_package_comment ? ' fold' : '') + \ . (go#config#FoldEnable('package_comment') ? ' fold' : '') hi def link goPackageComment Comment endif @@ -468,6 +386,12 @@ function! s:hi() " :GoCoverage commands hi def goCoverageCovered ctermfg=green guifg=#A6E22E hi def goCoverageUncover ctermfg=red guifg=#F92672 + + " :GoDebug commands + if go#config#HighlightDebug() + hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black + hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White + endif endfunction augroup vim-go-hi diff --git a/pack/acp/start/vim-go/syntax/gohtmltmpl.vim b/pack/acp/start/vim-go/syntax/gohtmltmpl.vim index fd2612a..ea3dbf0 100644 --- a/pack/acp/start/vim-go/syntax/gohtmltmpl.vim +++ b/pack/acp/start/vim-go/syntax/gohtmltmpl.vim @@ -10,6 +10,8 @@ runtime! syntax/gotexttmpl.vim runtime! syntax/html.vim unlet b:current_syntax +syn cluster htmlPreproc add=gotplAction,goTplComment + let b:current_syntax = "gohtmltmpl" " vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/syntax/gomod.vim b/pack/acp/start/vim-go/syntax/gomod.vim new file mode 100644 index 0000000..5e1e3a1 --- /dev/null +++ b/pack/acp/start/vim-go/syntax/gomod.vim @@ -0,0 +1,85 @@ +" gomod.vim: Vim syntax file for go.mod file +" +" Quit when a (custom) syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +syntax case match + +" match keywords +syntax keyword gomodModule module +syntax keyword gomodRequire require +syntax keyword gomodExclude exclude +syntax keyword gomodReplace replace + +" require, exclude and replace can be also grouped into block +syntax region gomodRequire start='require (' end=')' transparent contains=gomodRequire,gomodVersion +syntax region gomodExclude start='exclude (' end=')' transparent contains=gomodExclude,gomodVersion +syntax region gomodReplace start='replace (' end=')' transparent contains=gomodReplace,gomodVersion + +" set highlights +highlight default link gomodModule Keyword +highlight default link gomodRequire Keyword +highlight default link gomodExclude Keyword +highlight default link gomodReplace Keyword + +" comments are always in form of // ... +syntax region gomodComment start="//" end="$" contains=@Spell +highlight default link gomodComment Comment + +" make sure quoted import paths are higlighted +syntax region gomodString start=+"+ skip=+\\\\\|\\"+ end=+"+ +highlight default link gomodString String + +" replace operator is in the form of '=>' +syntax match gomodReplaceOperator "\v\=\>" +highlight default link gomodReplaceOperator Operator + + +" highlight versions: +" * vX.Y.Z-pre +" * vX.Y.Z +" * vX.0.0-yyyyymmddhhmmss-abcdefabcdef +" * vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef +" * vX.Y.(Z+1)-0.yyyymmddhhss-abcdefabcdef +" see https://godoc.org/golang.org/x/tools/internal/semver for more +" information about how semantic versions are parsed and +" https://golang.org/cmd/go/ for how pseudo-versions and +incompatible +" are applied. + + +" match vX.Y.Z and their prereleases +syntax match gomodVersion "v\d\+\.\d\+\.\d\+\%(-\%([0-9A-Za-z-]\+\)\%(\.[0-9A-Za-z-]\+\)*\)\?\%(+\%([0-9A-Za-z-]\+\)\(\.[0-9A-Za-z-]\+\)*\)\?" +" ^--- version ---^^------------ pre-release ---------------------^^--------------- metadata ---------------------^ +" ^--------------------------------------- semantic version -------------------------------------------------------^ + +" match pseudo versions +" without a major version before the commit (e.g. vX.0.0-yyyymmddhhmmss-abcdefabcdef) +syntax match gomodVersion "v\d\+\.0\.0-\d\{14\}-\x\+" +" when most recent version before target is a pre-release +syntax match gomodVersion "v\d\+\.\d\+\.\d\+-\%([0-9A-Za-z-]\+\)\%(\.[0-9A-Za-z-]\+\)*\%(+\%([0-9A-Za-z-]\+\)\(\.[0-9A-Za-z-]\+\)*\)\?\.0\.\d\{14}-\x\+" +" ^--- version ---^^--- ------ pre-release -----------------^^--------------- metadata ---------------------^ +" ^------------------------------------- semantic version --------------------------------------------------^ +" most recent version before the target is X.Y.Z +syntax match gomodVersion "v\d\+\.\d\+\.\d\+\%(+\%([0-9A-Za-z-]\+\)\(\.[0-9A-Za-z-]\+\)*\)\?-0\.\d\{14}-\x\+" +" ^--- version ---^^--------------- metadata ---------------------^ + +" match incompatible vX.Y.Z and their prereleases +syntax match gomodVersion "v[2-9]\{1}\d*\.\d\+\.\d\+\%(-\%([0-9A-Za-z-]\+\)\%(\.[0-9A-Za-z-]\+\)*\)\?\%(+\%([0-9A-Za-z-]\+\)\(\.[0-9A-Za-z-]\+\)*\)\?+incompatible" +" ^------- version -------^^------------- pre-release ---------------------^^--------------- metadata ---------------------^ +" ^------------------------------------------- semantic version -----------------------------------------------------------^ + +" match incompatible pseudo versions +" incompatible without a major version before the commit (e.g. vX.0.0-yyyymmddhhmmss-abcdefabcdef) +syntax match gomodVersion "v[2-9]\{1}\d*\.0\.0-\d\{14\}-\x\++incompatible" +" when most recent version before target is a pre-release +syntax match gomodVersion "v[2-9]\{1}\d*\.\d\+\.\d\+-\%([0-9A-Za-z-]\+\)\%(\.[0-9A-Za-z-]\+\)*\%(+\%([0-9A-Za-z-]\+\)\(\.[0-9A-Za-z-]\+\)*\)\?\.0\.\d\{14}-\x\++incompatible" +" ^------- version -------^^---------- pre-release -----------------^^--------------- metadata ---------------------^ +" ^---------------------------------------- semantic version ------------------------------------------------------^ +" most recent version before the target is X.Y.Z +syntax match gomodVersion "v[2-9]\{1}\d*\.\d\+\.\d\+\%(+\%([0-9A-Za-z-]\+\)\%(\.[0-9A-Za-z-]\+\)*\)\?-0\.\d\{14}-\x\++incompatible" +" ^------- version -------^^---------------- metadata ---------------------^ +highlight default link gomodVersion Identifier + +let b:current_syntax = "gomod" diff --git a/pack/acp/start/vim-go/test/gopath_test.vim b/pack/acp/start/vim-go/test/gopath_test.vim index fef9d50..5036a59 100644 --- a/pack/acp/start/vim-go/test/gopath_test.vim +++ b/pack/acp/start/vim-go/test/gopath_test.vim @@ -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 + fun! Test_Detect_Gopath() abort let l:gopath = $GOPATH try @@ -53,3 +57,9 @@ fun! Test_Detect_Gopath_disabled() abort call delete(l:tmp2, 'rf') endtry endfun + +" restore Vi compatibility settings +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: sw=2 ts=2 et diff --git a/pack/acp/start/vim-go/test/parse.go b/pack/acp/start/vim-go/test/parse.go new file mode 100644 index 0000000..7e296a7 --- /dev/null +++ b/pack/acp/start/vim-go/test/parse.go @@ -0,0 +1,2096 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// golang.org/x/net/html/parse.go + +package html + +import ( + "errors" + "fmt" + "io" + "strings" + + a "golang.org/x/net/html/atom" +) + +// A parser implements the HTML5 parsing algorithm: +// https://html.spec.whatwg.org/multipage/syntax.html#tree-construction +type parser struct { + // tokenizer provides the tokens for the parser. + tokenizer *Tokenizer + // tok is the most recently read token. + tok Token + // Self-closing tags like
are treated as start tags, except that + // hasSelfClosingToken is set while they are being processed. + hasSelfClosingToken bool + // doc is the document root element. + doc *Node + // The stack of open elements (section 12.2.3.2) and active formatting + // elements (section 12.2.3.3). + oe, afe nodeStack + // Element pointers (section 12.2.3.4). + head, form *Node + // Other parsing state flags (section 12.2.3.5). + scripting, framesetOK bool + // im is the current insertion mode. + im insertionMode + // originalIM is the insertion mode to go back to after completing a text + // or inTableText insertion mode. + originalIM insertionMode + // fosterParenting is whether new elements should be inserted according to + // the foster parenting rules (section 12.2.5.3). + fosterParenting bool + // quirks is whether the parser is operating in "quirks mode." + quirks bool + // fragment is whether the parser is parsing an HTML fragment. + fragment bool + // context is the context element when parsing an HTML fragment + // (section 12.4). + context *Node +} + +func (p *parser) top() *Node { + if n := p.oe.top(); n != nil { + return n + } + return p.doc +} + +// Stop tags for use in popUntil. These come from section 12.2.3.2. +var ( + defaultScopeStopTags = map[string][]a.Atom{ + "": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template}, + "math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext}, + "svg": {a.Desc, a.ForeignObject, a.Title}, + } +) + +type scope int + +const ( + defaultScope scope = iota + listItemScope + buttonScope + tableScope + tableRowScope + tableBodyScope + selectScope +) + +// popUntil pops the stack of open elements at the highest element whose tag +// is in matchTags, provided there is no higher element in the scope's stop +// tags (as defined in section 12.2.3.2). It returns whether or not there was +// such an element. If there was not, popUntil leaves the stack unchanged. +// +// For example, the set of stop tags for table scope is: "html", "table". If +// the stack was: +// ["html", "body", "font", "table", "b", "i", "u"] +// then popUntil(tableScope, "font") would return false, but +// popUntil(tableScope, "i") would return true and the stack would become: +// ["html", "body", "font", "table", "b"] +// +// If an element's tag is in both the stop tags and matchTags, then the stack +// will be popped and the function returns true (provided, of course, there was +// no higher element in the stack that was also in the stop tags). For example, +// popUntil(tableScope, "table") returns true and leaves: +// ["html", "body", "font"] +func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool { + if i := p.indexOfElementInScope(s, matchTags...); i != -1 { + p.oe = p.oe[:i] + return true + } + return false +} + +// indexOfElementInScope returns the index in p.oe of the highest element whose +// tag is in matchTags that is in scope. If no matching element is in scope, it +// returns -1. +func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + if p.oe[i].Namespace == "" { + for _, t := range matchTags { + if t == tagAtom { + return i + } + } + switch s { + case defaultScope: + // No-op. + case listItemScope: + if tagAtom == a.Ol || tagAtom == a.Ul { + return -1 + } + case buttonScope: + if tagAtom == a.Button { + return -1 + } + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + return -1 + } + case selectScope: + if tagAtom != a.Optgroup && tagAtom != a.Option { + return -1 + } + default: + panic("unreachable") + } + } + switch s { + case defaultScope, listItemScope, buttonScope: + for _, t := range defaultScopeStopTags[p.oe[i].Namespace] { + if t == tagAtom { + return -1 + } + } + } + } + return -1 +} + +// elementInScope is like popUntil, except that it doesn't modify the stack of +// open elements. +func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool { + return p.indexOfElementInScope(s, matchTags...) != -1 +} + +// clearStackToContext pops elements off the stack of open elements until a +// scope-defined element is found. +func (p *parser) clearStackToContext(s scope) { + for i := len(p.oe) - 1; i >= 0; i-- { + tagAtom := p.oe[i].DataAtom + switch s { + case tableScope: + if tagAtom == a.Html || tagAtom == a.Table { + p.oe = p.oe[:i+1] + return + } + case tableRowScope: + if tagAtom == a.Html || tagAtom == a.Tr { + p.oe = p.oe[:i+1] + return + } + case tableBodyScope: + if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead { + p.oe = p.oe[:i+1] + return + } + default: + panic("unreachable") + } + } +} + +// generateImpliedEndTags pops nodes off the stack of open elements as long as +// the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt. +// If exceptions are specified, nodes with that name will not be popped off. +func (p *parser) generateImpliedEndTags(exceptions ...string) { + var i int +loop: + for i = len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if n.Type == ElementNode { + switch n.DataAtom { + case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt: + for _, except := range exceptions { + if n.Data == except { + break loop + } + } + continue + } + } + break + } + + p.oe = p.oe[:i+1] +} + +// addChild adds a child node n to the top element, and pushes n onto the stack +// of open elements if it is an element node. +func (p *parser) addChild(n *Node) { + if p.shouldFosterParent() { + p.fosterParent(n) + } else { + p.top().AppendChild(n) + } + + if n.Type == ElementNode { + p.oe = append(p.oe, n) + } +} + +// shouldFosterParent returns whether the next node to be added should be +// foster parented. +func (p *parser) shouldFosterParent() bool { + if p.fosterParenting { + switch p.top().DataAtom { + case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: + return true + } + } + return false +} + +// fosterParent adds a child node according to the foster parenting rules. +// Section 12.2.5.3, "foster parenting". +func (p *parser) fosterParent(n *Node) { + var table, parent, prev *Node + var i int + for i = len(p.oe) - 1; i >= 0; i-- { + if p.oe[i].DataAtom == a.Table { + table = p.oe[i] + break + } + } + + if table == nil { + // The foster parent is the html element. + parent = p.oe[0] + } else { + parent = table.Parent + } + if parent == nil { + parent = p.oe[i-1] + } + + if table != nil { + prev = table.PrevSibling + } else { + prev = parent.LastChild + } + if prev != nil && prev.Type == TextNode && n.Type == TextNode { + prev.Data += n.Data + return + } + + parent.InsertBefore(n, table) +} + +// addText adds text to the preceding node if it is a text node, or else it +// calls addChild with a new text node. +func (p *parser) addText(text string) { + if text == "" { + return + } + + if p.shouldFosterParent() { + p.fosterParent(&Node{ + Type: TextNode, + Data: text, + }) + return + } + + t := p.top() + if n := t.LastChild; n != nil && n.Type == TextNode { + n.Data += text + return + } + p.addChild(&Node{ + Type: TextNode, + Data: text, + }) +} + +// addElement adds a child element based on the current token. +func (p *parser) addElement() { + p.addChild(&Node{ + Type: ElementNode, + DataAtom: p.tok.DataAtom, + Data: p.tok.Data, + Attr: p.tok.Attr, + }) +} + +// Section 12.2.3.3. +func (p *parser) addFormattingElement() { + tagAtom, attr := p.tok.DataAtom, p.tok.Attr + p.addElement() + + // Implement the Noah's Ark clause, but with three per family instead of two. + identicalElements := 0 +findIdenticalElements: + for i := len(p.afe) - 1; i >= 0; i-- { + n := p.afe[i] + if n.Type == scopeMarkerNode { + break + } + if n.Type != ElementNode { + continue + } + if n.Namespace != "" { + continue + } + if n.DataAtom != tagAtom { + continue + } + if len(n.Attr) != len(attr) { + continue + } + compareAttributes: + for _, t0 := range n.Attr { + for _, t1 := range attr { + if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { + // Found a match for this attribute, continue with the next attribute. + continue compareAttributes + } + } + // If we get here, there is no attribute that matches a. + // Therefore the element is not identical to the new one. + continue findIdenticalElements + } + + identicalElements++ + if identicalElements >= 3 { + p.afe.remove(n) + } + } + + p.afe = append(p.afe, p.top()) +} + +// Section 12.2.3.3. +func (p *parser) clearActiveFormattingElements() { + for { + n := p.afe.pop() + if len(p.afe) == 0 || n.Type == scopeMarkerNode { + return + } + } +} + +// Section 12.2.3.3. +func (p *parser) reconstructActiveFormattingElements() { + n := p.afe.top() + if n == nil { + return + } + if n.Type == scopeMarkerNode || p.oe.index(n) != -1 { + return + } + i := len(p.afe) - 1 + for n.Type != scopeMarkerNode && p.oe.index(n) == -1 { + if i == 0 { + i = -1 + break + } + i-- + n = p.afe[i] + } + for { + i++ + clone := p.afe[i].clone() + p.addChild(clone) + p.afe[i] = clone + if i == len(p.afe)-1 { + break + } + } +} + +// Section 12.2.4. +func (p *parser) acknowledgeSelfClosingTag() { + p.hasSelfClosingToken = false +} + +// An insertion mode (section 12.2.3.1) is the state transition function from +// a particular state in the HTML5 parser's state machine. It updates the +// parser's fields depending on parser.tok (where ErrorToken means EOF). +// It returns whether the token was consumed. +type insertionMode func(*parser) bool + +// setOriginalIM sets the insertion mode to return to after completing a text or +// inTableText insertion mode. +// Section 12.2.3.1, "using the rules for". +func (p *parser) setOriginalIM() { + if p.originalIM != nil { + panic("html: bad parser state: originalIM was set twice") + } + p.originalIM = p.im +} + +// Section 12.2.3.1, "reset the insertion mode". +func (p *parser) resetInsertionMode() { + for i := len(p.oe) - 1; i >= 0; i-- { + n := p.oe[i] + if i == 0 && p.context != nil { + n = p.context + } + + switch n.DataAtom { + case a.Select: + p.im = inSelectIM + case a.Td, a.Th: + p.im = inCellIM + case a.Tr: + p.im = inRowIM + case a.Tbody, a.Thead, a.Tfoot: + p.im = inTableBodyIM + case a.Caption: + p.im = inCaptionIM + case a.Colgroup: + p.im = inColumnGroupIM + case a.Table: + p.im = inTableIM + case a.Head: + p.im = inBodyIM + case a.Body: + p.im = inBodyIM + case a.Frameset: + p.im = inFramesetIM + case a.Html: + p.im = beforeHeadIM + default: + continue + } + return + } + p.im = inBodyIM +} + +const whitespace = " \t\r\n\f" + +// Section 12.2.5.4.1. +func initialIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + n, quirks := parseDoctype(p.tok.Data) + p.doc.AppendChild(n) + p.quirks = quirks + p.im = beforeHTMLIM + return true + } + p.quirks = true + p.im = beforeHTMLIM + return false +} + +// Section 12.2.5.4.2. +func beforeHTMLIM(p *parser) bool { + switch p.tok.Type { + case DoctypeToken: + // Ignore the token. + return true + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + if p.tok.DataAtom == a.Html { + p.addElement() + p.im = beforeHeadIM + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.doc.AppendChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + } + p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) + return false +} + +// Section 12.2.5.4.3. +func beforeHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) + if len(p.tok.Data) == 0 { + // It was all whitespace, so ignore it. + return true + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Head: + p.addElement() + p.head = p.top() + p.im = inHeadIM + return true + case a.Html: + return inBodyIM(p) + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head, a.Body, a.Html, a.Br: + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.4. +func inHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta: + p.addElement() + p.oe.pop() + p.acknowledgeSelfClosingTag() + return true + case a.Script, a.Title, a.Noscript, a.Noframes, a.Style: + p.addElement() + p.setOriginalIM() + p.im = textIM + return true + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Head: + n := p.oe.pop() + if n.DataAtom != a.Head { + panic("html: bad parser state: element not found, in the in-head insertion mode") + } + p.im = afterHeadIM + return true + case a.Body, a.Html, a.Br: + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) + return false +} + +// Section 12.2.5.4.6. +func afterHeadIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + s := strings.TrimLeft(p.tok.Data, whitespace) + if len(s) < len(p.tok.Data) { + // Add the initial whitespace to the current node. + p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) + if s == "" { + return true + } + p.tok.Data = s + } + case StartTagToken: + switch p.tok.DataAtom { + case a.Html: + return inBodyIM(p) + case a.Body: + p.addElement() + p.framesetOK = false + p.im = inBodyIM + return true + case a.Frameset: + p.addElement() + p.im = inFramesetIM + return true + case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title: + p.oe = append(p.oe, p.head) + defer p.oe.remove(p.head) + return inHeadIM(p) + case a.Head: + // Ignore the token. + return true + } + case EndTagToken: + switch p.tok.DataAtom { + case a.Body, a.Html, a.Br: + // Drop down to creating an implied tag. + default: + // Ignore the token. + return true + } + case CommentToken: + p.addChild(&Node{ + Type: CommentNode, + Data: p.tok.Data, + }) + return true + case DoctypeToken: + // Ignore the token. + return true + } + + p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) + p.framesetOK = true + return false +} + +// copyAttributes copies attributes of src not found on dst to dst. +func copyAttributes(dst *Node, src Token) { + if len(src.Attr) == 0 { + return + } + attr := map[string]string{} + for _, t := range dst.Attr { + attr[t.Key] = t.Val + } + for _, t := range src.Attr { + if _, ok := attr[t.Key]; !ok { + dst.Attr = append(dst.Attr, t) + attr[t.Key] = t.Val + } + } +} + +// Section 12.2.5.4.7. +func inBodyIM(p *parser) bool { + switch p.tok.Type { + case TextToken: + d := p.tok.Data + switch n := p.oe.top(); n.DataAtom { + case a.Pre, a.Listing: + if n.FirstChild == nil { + // Ignore a newline at the start of a
 block.
+				if d != "" && d[0] == '\r' {
+					d = d[1:]
+				}
+				if d != "" && d[0] == '\n' {
+					d = d[1:]
+				}
+			}
+		}
+		d = strings.Replace(d, "\x00", "", -1)
+		if d == "" {
+			return true
+		}
+		p.reconstructActiveFormattingElements()
+		p.addText(d)
+		if p.framesetOK && strings.TrimLeft(d, whitespace) != "" {
+			// There were non-whitespace characters inserted.
+			p.framesetOK = false
+		}
+	case StartTagToken:
+		switch p.tok.DataAtom {
+		case a.Html:
+			copyAttributes(p.oe[0], p.tok)
+		case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
+			return inHeadIM(p)
+		case a.Body:
+			if len(p.oe) >= 2 {
+				body := p.oe[1]
+				if body.Type == ElementNode && body.DataAtom == a.Body {
+					p.framesetOK = false
+					copyAttributes(body, p.tok)
+				}
+			}
+		case a.Frameset:
+			if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body {
+				// Ignore the token.
+				return true
+			}
+			body := p.oe[1]
+			if body.Parent != nil {
+				body.Parent.RemoveChild(body)
+			}
+			p.oe = p.oe[:1]
+			p.addElement()
+			p.im = inFramesetIM
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(buttonScope, a.P)
+			switch n := p.top(); n.DataAtom {
+			case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+				p.oe.pop()
+			}
+			p.addElement()
+		case a.Pre, a.Listing:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			// The newline, if any, will be dealt with by the TextToken case.
+			p.framesetOK = false
+		case a.Form:
+			if p.form == nil {
+				p.popUntil(buttonScope, a.P)
+				p.addElement()
+				p.form = p.top()
+			}
+		case a.Li:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Li:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Dd, a.Dt:
+			p.framesetOK = false
+			for i := len(p.oe) - 1; i >= 0; i-- {
+				node := p.oe[i]
+				switch node.DataAtom {
+				case a.Dd, a.Dt:
+					p.oe = p.oe[:i]
+				case a.Address, a.Div, a.P:
+					continue
+				default:
+					if !isSpecialElement(node) {
+						continue
+					}
+				}
+				break
+			}
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Plaintext:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+		case a.Button:
+			p.popUntil(defaultScope, a.Button)
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+		case a.A:
+			for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
+				if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A {
+					p.inBodyEndTagFormatting(a.A)
+					p.oe.remove(n)
+					p.afe.remove(n)
+					break
+				}
+			}
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.reconstructActiveFormattingElements()
+			p.addFormattingElement()
+		case a.Nobr:
+			p.reconstructActiveFormattingElements()
+			if p.elementInScope(defaultScope, a.Nobr) {
+				p.inBodyEndTagFormatting(a.Nobr)
+				p.reconstructActiveFormattingElements()
+			}
+			p.addFormattingElement()
+		case a.Applet, a.Marquee, a.Object:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.afe = append(p.afe, &scopeMarker)
+			p.framesetOK = false
+		case a.Table:
+			if !p.quirks {
+				p.popUntil(buttonScope, a.P)
+			}
+			p.addElement()
+			p.framesetOK = false
+			p.im = inTableIM
+			return true
+		case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			if p.tok.DataAtom == a.Input {
+				for _, t := range p.tok.Attr {
+					if t.Key == "type" {
+						if strings.ToLower(t.Val) == "hidden" {
+							// Skip setting framesetOK = false
+							return true
+						}
+					}
+				}
+			}
+			p.framesetOK = false
+		case a.Param, a.Source, a.Track:
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+		case a.Hr:
+			p.popUntil(buttonScope, a.P)
+			p.addElement()
+			p.oe.pop()
+			p.acknowledgeSelfClosingTag()
+			p.framesetOK = false
+		case a.Image:
+			p.tok.DataAtom = a.Img
+			p.tok.Data = a.Img.String()
+			return false
+		case a.Isindex:
+			if p.form != nil {
+				// Ignore the token.
+				return true
+			}
+			action := ""
+			prompt := "This is a searchable index. Enter search keywords: "
+			attr := []Attribute{{Key: "name", Val: "isindex"}}
+			for _, t := range p.tok.Attr {
+				switch t.Key {
+				case "action":
+					action = t.Val
+				case "name":
+					// Ignore the attribute.
+				case "prompt":
+					prompt = t.Val
+				default:
+					attr = append(attr, t)
+				}
+			}
+			p.acknowledgeSelfClosingTag()
+			p.popUntil(buttonScope, a.P)
+			p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
+			if action != "" {
+				p.form.Attr = []Attribute{{Key: "action", Val: action}}
+			}
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(StartTagToken, a.Label, a.Label.String())
+			p.addText(prompt)
+			p.addChild(&Node{
+				Type:     ElementNode,
+				DataAtom: a.Input,
+				Data:     a.Input.String(),
+				Attr:     attr,
+			})
+			p.oe.pop()
+			p.parseImpliedToken(EndTagToken, a.Label, a.Label.String())
+			p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
+			p.parseImpliedToken(EndTagToken, a.Form, a.Form.String())
+		case a.Textarea:
+			p.addElement()
+			p.setOriginalIM()
+			p.framesetOK = false
+			p.im = textIM
+		case a.Xmp:
+			p.popUntil(buttonScope, a.P)
+			p.reconstructActiveFormattingElements()
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Iframe:
+			p.framesetOK = false
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Noembed, a.Noscript:
+			p.addElement()
+			p.setOriginalIM()
+			p.im = textIM
+		case a.Select:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+			p.framesetOK = false
+			p.im = inSelectIM
+			return true
+		case a.Optgroup, a.Option:
+			if p.top().DataAtom == a.Option {
+				p.oe.pop()
+			}
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		case a.Rp, a.Rt:
+			if p.elementInScope(defaultScope, a.Ruby) {
+				p.generateImpliedEndTags()
+			}
+			p.addElement()
+		case a.Math, a.Svg:
+			p.reconstructActiveFormattingElements()
+			if p.tok.DataAtom == a.Math {
+				adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
+			} else {
+				adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
+			}
+			adjustForeignAttributes(p.tok.Attr)
+			p.addElement()
+			p.top().Namespace = p.tok.Data
+			if p.hasSelfClosingToken {
+				p.oe.pop()
+				p.acknowledgeSelfClosingTag()
+			}
+			return true
+		case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
+			// Ignore the token.
+		default:
+			p.reconstructActiveFormattingElements()
+			p.addElement()
+		}
+	case EndTagToken:
+		switch p.tok.DataAtom {
+		case a.Body:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.im = afterBodyIM
+			}
+		case a.Html:
+			if p.elementInScope(defaultScope, a.Body) {
+				p.parseImpliedToken(EndTagToken, a.Body, a.Body.String())
+				return false
+			}
+			return true
+		case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.Form:
+			node := p.form
+			p.form = nil
+			i := p.indexOfElementInScope(defaultScope, a.Form)
+			if node == nil || i == -1 || p.oe[i] != node {
+				// Ignore the token.
+				return true
+			}
+			p.generateImpliedEndTags()
+			p.oe.remove(node)
+		case a.P:
+			if !p.elementInScope(buttonScope, a.P) {
+				p.parseImpliedToken(StartTagToken, a.P, a.P.String())
+			}
+			p.popUntil(buttonScope, a.P)
+		case a.Li:
+			p.popUntil(listItemScope, a.Li)
+		case a.Dd, a.Dt:
+			p.popUntil(defaultScope, p.tok.DataAtom)
+		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
+			p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6)
+		case a.A, a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.Nobr, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
+			p.inBodyEndTagFormatting(p.tok.DataAtom)
+		case a.Applet, a.Marquee, a.Object:
+			if p.popUntil(defaultScope, p.tok.DataAtom) {
+				p.clearActiveFormattingElements()
+			}
+		case a.Br:
+			p.tok.Type = StartTagToken
+			return false
+		default:
+			p.inBodyEndTagOther(p.tok.DataAtom)
+		}
+	case CommentToken:
+		p.addChild(&Node{
+			Type: CommentNode,
+			Data: p.tok.Data,
+		})
+	}
+
+	return true
+}
+
+func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
+	// This is the "adoption agency" algorithm, described at
+	// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
+
+	// TODO: this is a fairly literal line-by-line translation of that algorithm.
+	// Once the code successfully parses the comprehensive test suite, we should
+	// refactor this code to be more idiomatic.
+
+	// Steps 1-4. The outer loop.
+	for i := 0; i < 8; i++ {
+		// Step 5. Find the formatting element.
+		var formattingElement *Node
+		for j := len(p.afe) - 1; j >= 0; j-- {
+			if p.afe[j].Type == scopeMarkerNode {
+				break
+			}
+			if p.afe[j].DataAtom == tagAtom {
+				formattingElement = p.afe[j]
+				break
+			}
+		}
+		if formattingElement == nil {
+			p.inBodyEndTagOther(tagAtom)
+			return
+		}
+		feIndex := p.oe.index(formattingElement)
+		if feIndex == -1 {
+			p.afe.remove(formattingElement)
+			return
+		}
+		if !p.elementInScope(defaultScope, tagAtom) {
+			// Ignore the tag.
+			return
+		}
+
+		// Steps 9-10. Find the furthest block.
+		var furthestBlock *Node
+		for _, e := range p.oe[feIndex:] {
+			if isSpecialElement(e) {
+				furthestBlock = e
+				break
+			}
+		}
+		if furthestBlock == nil {
+			e := p.oe.pop()
+			for e != formattingElement {
+				e = p.oe.pop()
+			}
+			p.afe.remove(e)
+			return
+		}
+
+		// Steps 11-12. Find the common ancestor and bookmark node.
+		commonAncestor := p.oe[feIndex-1]
+		bookmark := p.afe.index(formattingElement)
+
+		// Step 13. The inner loop. Find the lastNode to reparent.
+		lastNode := furthestBlock
+		node := furthestBlock
+		x := p.oe.index(node)
+		// Steps 13.1-13.2
+		for j := 0; j < 3; j++ {
+			// Step 13.3.
+			x--
+			node = p.oe[x]
+			// Step 13.4 - 13.5.
+			if p.afe.index(node) == -1 {
+				p.oe.remove(node)
+				continue
+			}
+			// Step 13.6.
+			if node == formattingElement {
+				break
+			}
+			// Step 13.7.
+			clone := node.clone()
+			p.afe[p.afe.index(node)] = clone
+			p.oe[p.oe.index(node)] = clone
+			node = clone
+			// Step 13.8.
+			if lastNode == furthestBlock {
+				bookmark = p.afe.index(node) + 1
+			}
+			// Step 13.9.
+			if lastNode.Parent != nil {
+				lastNode.Parent.RemoveChild(lastNode)
+			}
+			node.AppendChild(lastNode)
+			// Step 13.10.
+			lastNode = node
+		}
+
+		// Step 14. Reparent lastNode to the common ancestor,
+		// or for misnested table nodes, to the foster parent.
+		if lastNode.Parent != nil {
+			lastNode.Parent.RemoveChild(lastNode)
+		}
+		switch commonAncestor.DataAtom {
+		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
+			p.fosterParent(lastNode)
+		default:
+			commonAncestor.AppendChild(lastNode)
+		}
+
+		// Steps 15-17. Reparent nodes from the furthest block's children
+		// to a clone of the formatting element.
+		clone := formattingElement.clone()
+		reparentChildren(clone, furthestBlock)
+		furthestBlock.AppendChild(clone)
+
+		// Step 18. Fix up the list of active formatting elements.
+		if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
+			// Move the bookmark with the rest of the list.
+			bookmark--
+		}
+		p.afe.remove(formattingElement)
+		p.afe.insert(bookmark, clone)
+
+		// Step 19. Fix up the stack of open elements.
+		p.oe.remove(formattingElement)
+		p.oe.insert(p.oe.index(furthestBlock)+1, clone)
+	}
+}
+
+// inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
+// "Any other end tag" handling from 12.2.5.5 The rules for parsing tokens in foreign content
+// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
+func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
+	for i := len(p.oe) - 1; i >= 0; i-- {
+		if p.oe[i].DataAtom == tagAtom {
+			p.oe = p.oe[:i]
+			break
+		}
+		if isSpecialElement(p.oe[i]) {
+			break
+		}
+	}
+}
+
+// Section 12.2.5.4.8.
+func textIM(p *parser) bool {
+	switch p.tok.Type {
+	case ErrorToken:
+		p.oe.pop()
+	case TextToken:
+		d := p.tok.Data
+		if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil {
+			// Ignore a newline at the start of a