Updated vim-go to v1.24

This commit is contained in:
Anthony Rose 2020-10-28 13:35:24 +00:00
parent 35a4e11c1a
commit 075a943af7
106 changed files with 6983 additions and 1637 deletions

View file

@ -4,3 +4,5 @@
.dlv/ .dlv/
.git/ .git/
.viminfo .viminfo
issues/
autoload/go/**/pkg/

View file

@ -0,0 +1 @@
patreon: bhcleek

View file

@ -1,21 +1,47 @@
### What did you do? (required. The issue will be **closed** when not provided.) <!--
Before filing an issue, please check if vim-go's help addresses your problem (see `:help go-troubleshooting`).
Consider executing `:GoReportGitHubIssue` to populate much of this information automatically.
-->
### What did you do? (required: The issue will be **closed** when not provided)
<!--
If possible, please provide clear steps for reproducing the problem.
-->
### What did you expect to happen? ### What did you expect to happen?
### What happened instead? ### What happened instead?
### Configuration (**MUST** fill this out): ### Configuration (**MUST** fill this out):
* vim-go 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): #### `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`): If this is not provided or is obviously incomplete, the issue may be unceremoniously closed.
-->
<!-- vimrc -->
<details><summary>vimrc</summary><br><pre>
* Go version (`go version`): </pre></details>
* Go environment (`go env`): #### Vim version (first three lines from `:version`):
<!-- :version -->
#### Go version (`go version`):
<!-- go version -->
#### Go environment
<details><summary><code>go env</code> Output:</summary><br><pre>
<!-- go env -->
</pre></details>
#### gopls version
<details><summary><code>gopls version</code> Output:</summary><br><pre>
<!-- gopls version -->
</pre></details>

View file

@ -0,0 +1,60 @@
name: test
on: [push, pull_request]
jobs:
lint:
name: lint
runs-on: ubuntu-18.04
steps:
- name: set up python
uses: actions/setup-python@v1.2.0
with:
python-version: 3.6
- name: install vim-vint
run: |
python -m pip install --upgrade pip
pip install vim-vint pathlib
- name: checkout
uses: actions/checkout@v2.1.0
- name: install vim
run: $GITHUB_WORKSPACE/scripts/install-vim vim-8.2
- name: install tools
run: $GITHUB_WORKSPACE/scripts/install-tools vim-8.2
- name: lint
run: $GITHUB_WORKSPACE/scripts/lint vim-8.2
test:
name: test
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
go: ['1.14','1.15']
vim: ['vim-8.0', 'vim-8.2', 'nvim']
steps:
- name: setup Go
uses: actions/setup-go@v2.0.3
with:
go-version: ${{ matrix.go }}
- name: set up python
uses: actions/setup-python@v1.2.0
with:
python-version: 3.6
- name: install covimerage
run: |
python -m pip install --upgrade pip
pip install covimerage==0.2.1 codecov pathlib
- name: checkout
uses: actions/checkout@v2.1.0
- name: install vim
run: $GITHUB_WORKSPACE/scripts/install-vim ${{ matrix.vim }}
- name: install tools
run: $GITHUB_WORKSPACE/scripts/install-tools ${{ matrix.vim }}
- name: test
run: $GITHUB_WORKSPACE/scripts/test -c ${{ matrix.vim }}
- uses: codecov/codecov-action@v1
with:
# token is not required for public repos
#token: ${{ secrets.CODECOV_TOKEN }}
file: $GITHUB_WORKSPACE/coverage.xml
flags: unittests
name: vim-go
fail_ci_if_error: false

View file

@ -1,5 +1,11 @@
.DS_Store
/doc/tags
/.coverage.covimerage
/coverage.xml
*.pyc *.pyc
.DS_Store
/.bash_history
/.cache
/.config
/.coverage.covimerage
/.local
/.viminfo
/coverage.xml
/doc/tags
/issues

View file

@ -1,23 +0,0 @@
language: go
go:
- 1.12.1
notifications:
email: false
matrix:
include:
- 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: ENV=vimlint SCRIPT=lint VIM_VERSION=vim-8.0
language: python
python: 3.6
install:
- ./scripts/install-vim $VIM_VERSION
- |
if [ "$ENV" = "vimlint" ]; then
pip install vim-vint covimerage codecov pathlib
else
pip install --user vim-vint covimerage codecov pathlib
fi
script:
- ./scripts/$SCRIPT $VIM_VERSION

View file

@ -1,5 +1,529 @@
## unplanned ## unplanned
## v1.24 - (September 15, 2020)
IMPROVEMENTS:
* Clarify how `g:go_imports_autosave` and `g:go_fmt_autosave` interact.
[[GH-2893]](https://github.com/fatih/vim-go/pull/2893)
* Document what the working directory will be for `:GoRun`.
[[GH-2898]](https://github.com/fatih/vim-go/pull/2898)
* Add Ultisnip snippet for wrapping errors.
[[GH-2883]](https://github.com/fatih/vim-go/pull/2883)
* Beautify the godoc pop up window border.
[[GH-2900]](https://github.com/fatih/vim-go/pull/2900)
* Default `g:go_doc_url` to https://pkg.go.dev.
[[GH-2884]](https://github.com/fatih/vim-go/pull/2884)
* Default `g:go_gopls_options` to `[-remote=auto]` to share gopls instances
with other plugins and multiple instances of Vim.
[[GH-2905]](https://github.com/fatih/vim-go/pull/2905)
* Use the module root as the working directory when renaming so that all
references to the symbol will be renamed when in module aware mode and
`g:go_rename_command` is set to `gopls`.
[[GH-2917]](https://github.com/fatih/vim-go/pull/2917)
* Change `g:go_rename_command`'s default to `gopls`.
[[GH-2922]](https://github.com/fatih/vim-go/pull/2922)
* Do not send unnecessary textDocument/didChange notifications to `gopls`.
[[GH-2902]](https://github.com/fatih/vim-go/pull/2902)
[[GH-2930]](https://github.com/fatih/vim-go/pull/2930)
* Stop the debugger when the process being debugged exits.
[[GH-2921]](https://github.com/fatih/vim-go/pull/2921)
* Use the module package cache as a source of packages candidates when trying
to complete package names.
[[GH-2936]](https://github.com/fatih/vim-go/pull/2936)
[[GH-2939]](https://github.com/fatih/vim-go/pull/2939)
* Allow interaction with Vim while waiting for a breakpoint to be hit while
debugging.
[[GH-2932]](https://github.com/fatih/vim-go/pull/2932)
* Refactor Vim signs used for debugging breakpoints to avoid id collision with
other plugins.
[[GH-2943]](https://github.com/fatih/vim-go/pull/2943)
* Refactor debugger's rpc response handling to be asynchronous so that Vim will
be responsive while the program being debugged is executing.
[[GH-2948]](https://github.com/fatih/vim-go/pull/2948)
[[GH-2952]](https://github.com/fatih/vim-go/pull/2952)
* Warn when the debugger breaks in a file that has changed since debugging started.
[[GH-2950]](https://github.com/fatih/vim-go/pull/2950)
* Enable `go-run` mappings that use the terminal to work with Vim in addition to Neovim.
[[GH-2956]](https://github.com/fatih/vim-go/pull/2956)
* Use existing diagnostics for the file when the file hasn't changed and
`g:go_metalinter_command` is `gopls`.
[[GH-2960]](https://github.com/fatih/vim-go/pull/2960)
* Add a new option, `g:go_code_completion_icase`, to allow ignoring case when
filtering completion results.
[[GH-2961]](https://github.com/fatih/vim-go/pull/2961)
* Make sure tools are not cross-compiled with `:GoInstallBinaries` and
`:GoUpdateBinaries`.
[[GH-2982]](https://github.com/fatih/vim-go/pull/2982)
[[GH-2988]](https://github.com/fatih/vim-go/pull/2988)
* Add `:GoDebugHalt` to allow a program being debugged to be paused before it
hits a breakpoint.
[[GH-2983]](https://github.com/fatih/vim-go/pull/2983)
* Clear highlighting of the current line when after resuming when debugging.
[[GH-2984]](https://github.com/fatih/vim-go/pull/2984)
* Add `:GoDebugAttach` to debug a running process.
[[GH-2989]](https://github.com/fatih/vim-go/pull/2989)
* Add `g:go_term_reuse` option to allow the reuse of a terminal window.
[[GH-2990]](https://github.com/fatih/vim-go/pull/2990)
* Add official support for using `gopls`' `gofumpt` workspace setting via
`g:go_gopls_gofumpt`.
[[GH-2994]](https://github.com/fatih/vim-go/pull/2994)
[[GH-3005]](https://github.com/fatih/vim-go/pull/3005)
* Add support for using `gopls`' workspace settings that are otherwise not yet
officially supported by vim-go.
[[GH-2994]](https://github.com/fatih/vim-go/pull/2994)
BUG FIXES:
* Fix call to non-existent function in terminal mode edge case.
[[GH-2895]](https://github.com/fatih/vim-go/pull/2895)
* Do not show errors when adding a text property for highlighting fails.
[[GH-2892]](https://github.com/fatih/vim-go/pull/2892)
* Include `errcheck` in `g:go_metalinter_enabled`'s default.
[[GH-2903]](https://github.com/fatih/vim-go/pull/2903)
* Fix display of completion selection information on command-line when
`g:go_echo_go_info` is enabled.
[[GH-2907]](https://github.com/fatih/vim-go/pull/2907)
* Prevent `:GoDebugBreakpoint` from causing delve to exit.
[[GH-2908]](https://github.com/fatih/vim-go/pull/2908)
* Use the resolved directory name for `gopls`' working directory when `go.mod`
is in a symlinked path.
[[GH-2913]](https://github.com/fatih/vim-go/pull/2913)
* Fix buffer reuse with `:GoDef`.
[[GH-2928]](https://github.com/fatih/vim-go/pull/2928)
* Handle breakpoints that are already set before calling `:GoDebugStart` or
`:GoDebugTest` in some locales that cause the `sign place` output to vary.
[[GH-2921]](https://github.com/fatih/vim-go/pull/2921)
* Handle diagnostic errors at the end of a .go file.
[[GH-2942]](https://github.com/fatih/vim-go/pull/2942)
* Fix the `go-implements` mapping to use respect `g:go_implements_mode`.
[[GH-2944]](https://github.com/fatih/vim-go/pull/2944)
* Handle null results from `gopls` when getting definitions or type definitions
from virtual files.
[[GH-2951]](https://github.com/fatih/vim-go/pull/2951)
* Fix warning when Neovim is older than v0.4.0.
[[GH-2959]](https://github.com/fatih/vim-go/pull/2959)
* Correct documentation that referred to `g:go_imports_command` to refer to
`g:go_imports_mode` instead.
[[GH-2969]](https://github.com/fatih/vim-go/pull/2969)
* Remove reference to gocode in error message when `g:go_info_mode` is set to
an unsupported value.
[[GH-2978]](https://github.com/fatih/vim-go/pull/2978)
* Make sure debugging commands are configured when debugging a second time
within a single Vim session.
[[GH-2985]](https://github.com/fatih/vim-go/pull/2985)
* Correct documentation in for `:GoModifyTags` when adding a specific tag
value.
[[GH-3001]](https://github.com/fatih/vim-go/pull/3001)
* Fix the path given to `gopls` when `let g:go_metalinter='gopls'` and
`:GoMetaLinter` is called without any arguments.
[[GH-2992]](https://github.com/fatih/vim-go/pull/2992)
* Do not override a user's configuration for `GoDebugBreakpoint` or
`GoDebugCurrent` highlight groups.
[[GH-2998]](https://github.com/fatih/vim-go/pull/2998)
* Apply `gopls` text edits correctly that insert solitary newlines.
[[GH-3000]](https://github.com/fatih/vim-go/pull/3000)
## v1.23 - (May 16, 2020)
BACKWARDS INCOMPATIBILITIES:
* Remove support for gocode.
[[GH-2686]](https://github.com/fatih/vim-go/pull/2686)
* Require at least Neovim >= 0.4.0
[[GH-2853]](https://github.com/fatih/vim-go/pull/2853)
[[GH-2856]](https://github.com/fatih/vim-go/pull/2856)
[[GH-2863]](https://github.com/fatih/vim-go/pull/2863)
IMPROVEMENTS:
* Make signs for breakpoints configurable.
[[GH-2676]](https://github.com/fatih/vim-go/pull/2676)
[[GH-2690]](https://github.com/fatih/vim-go/pull/2690)
* Enable g:go_gopls_complete_unimported by default to stay aligned with gopls' defaults.
[[GH-2695]](https://github.com/fatih/vim-go/pull/2695)
* Document mappings that were recently added.
[[GH-2699]](https://github.com/fatih/vim-go/pull/2699)
* Handle null arrays better in gopls responses.
[[GH-2703]](https://github.com/fatih/vim-go/pull/2703)
* Use `gopls` defaults by default when they're not otherwise specified in vim-go options.
[[GH-2696]](https://github.com/fatih/vim-go/pull/2696)
* Add support for `gomodifytags --skip-unexported`
[[GH-2660]](https://github.com/fatih/vim-go/pull/2660)
* Show problems that prevent golangci-lint from running linters.
[[GH-2706]](https://github.com/fatih/vim-go/pull/2706)
[[GH-2720]](https://github.com/fatih/vim-go/pull/2720)
* Support golangci-lint config file by not using `--disable-all` when
`g:go_metalinter_enable` or `g:go_metalinter_autosave_enabled` is set to an
empty array.
[[GH-2655]](https://github.com/fatih/vim-go/pull/2655)
[[GH-2715]](https://github.com/fatih/vim-go/pull/2715)
* Add support for Vim8 terminals.
[[GH-2639]](https://github.com/fatih/vim-go/pull/2639)
[[GH-2736]](https://github.com/fatih/vim-go/pull/2736)
* Replace `g:go_gopls_fuzzy_matching` with `g:go_gopls_matcher` in response to
`gopls` deprecation of its `fuzzyMatching` option.
[[GH-2728]](https://github.com/fatih/vim-go/pull/2728)
* Set statuses and progress messages consistently for code quality tools.
[[GH-2727]](https://github.com/fatih/vim-go/pull/2727)
* Add a new supported value to `g:go_fmt_command` to format with `gopls`.
[[GH-2729]](https://github.com/fatih/vim-go/pull/2729)
[[GH-2752]](https://github.com/fatih/vim-go/pull/2752)
[[GH-2852]](https://github.com/fatih/vim-go/pull/2852)
* Handle changes to `go test -v` output.
[[GH-2743]](https://github.com/fatih/vim-go/pull/2743)
* Add `g:go_gopls_mod_tempfile` to configure `gopls`' `tempModfile`
configuration.
[[GH-2747]](https://github.com/fatih/vim-go/pull/2747)
* Add `g:go_gopls_options` to configure `gopls`' commandline options.
[[GH-2747]](https://github.com/fatih/vim-go/pull/2747)
* Improve readability of gopls logs.
[[GH-2773]](https://github.com/fatih/vim-go/pull/2773)
* Introduce `g:go_implements_mode` to allow `:GoImplements` to be satisfied
with `gopls`.
[[GH-2741]](https://github.com/fatih/vim-go/pull/2741)
[[GH-2799]](https://github.com/fatih/vim-go/pull/2799)
* Introduce `g:go_imports_mode` to allow `:GoImports` to be satisfied with
`gopls`.
[[GH-2791]](https://github.com/fatih/vim-go/pull/2791)
[[GH-2794]](https://github.com/fatih/vim-go/pull/2794)
[[GH-2796]](https://github.com/fatih/vim-go/pull/2796)
[[GH-2848]](https://github.com/fatih/vim-go/pull/2848)
* Send LSP synchronization messages to `gopls` when the file does not yet exist
on disk as long as its directory exists.
[[GH-2805]](https://github.com/fatih/vim-go/pull/2805)
* Run `gogetdoc` in the buffer's directory so that it will work regardless of
the user's working directory in module-aware mode.
[[GH-2804]](https://github.com/fatih/vim-go/pull/2804)
* Add `g:go_gopls_analyses` to support `gopls`' analyses options.
[[GH-2820]](https://github.com/fatih/vim-go/pull/2820)
* Add `g:go_gopls_local` to support `gopls`' local option to control how third
party imports are organized.
[[GH-2821]](https://github.com/fatih/vim-go/pull/2821)
* Use gopls to get documentation and documentation links for identifiers under
the cursor.
[[GH-2822]](https://github.com/fatih/vim-go/pull/2822)
[[GH-2839]](https://github.com/fatih/vim-go/pull/2839)
* Clarify documentation for terminal options.
[[GH-2843]](https://github.com/fatih/vim-go/pull/2843)
BUG FIXES:
* Use the discovered full path for gopls when renaming.
[[GH-2692]](https://github.com/fatih/vim-go/pull/2692)
* Execute commands correctly on windows when `'shell'` is not cmd.exe
[[GH-2713]](https://github.com/fatih/vim-go/pull/2713)
[[GH-2724]](https://github.com/fatih/vim-go/pull/2724)
* Always execute `errcheck` in the current package's directory.
[[GH-2726]](https://github.com/fatih/vim-go/pull/2726)
* Fix errors when highlighting diagnostics after a `:GoImports`.
[[GH-2746]](https://github.com/fatih/vim-go/pull/2746)
* Preserve errors from formatting when both formatting and metalinting happen
on save.
[[GH-2733]](https://github.com/fatih/vim-go/pull/2733)
[[GH-2810]](https://github.com/fatih/vim-go/pull/2810)
* Preserve ordering of gopls messages in the log.
[[GH-2753]](https://github.com/fatih/vim-go/pull/2753)
* Fix `:GoDef` on windows when `g:go_def_mode` is set to `gopls`.
[[GH-2768]](https://github.com/fatih/vim-go/pull/2768)
* Handle null values from `gopls`.
[[GH-2778]](https://github.com/fatih/vim-go/pull/2778)
* Preserve diagnostics highlights after formatting.
[[GH-2779]](https://github.com/fatih/vim-go/pull/2779)
* Fix the decoding and encoding of multi-byte file paths received from and sent
to `gopls`.
[[GH-2784]](https://github.com/fatih/vim-go/pull/2784)
* Fix `:GoRun` so that it works as expected when the current working directory
is neither in GOPATH nor within a module.
[[GH-2782]](https://github.com/fatih/vim-go/pull/2782)
[[GH-2818]](https://github.com/fatih/vim-go/pull/2818)
[[GH-2842]](https://github.com/fatih/vim-go/pull/2842)
* Use absolute file paths for `:GoRun`'s arguments in terminal mode.
[[GH-2844]](https://github.com/fatih/vim-go/pull/2844)
* Show the command executed by `:GoRun` when `g:go_debug` includes `'shell-commands'`.
[[GH-2785]](https://github.com/fatih/vim-go/pull/2785)
[[GH-2817]](https://github.com/fatih/vim-go/pull/2817)
* Clear the list for formatting errors when `g:go_fmt_command` is `gopls`.
[[GH-2790]](https://github.com/fatih/vim-go/pull/2790)
* Handle text edits from gopls that are only line insertions.
[[GH-2802]](https://github.com/fatih/vim-go/pull/2802)
[[GH-2803]](https://github.com/fatih/vim-go/pull/2803)
* Add `g:go_imports_autosave` so that imports can be adjusted on save when
`g:go_imports_mode` is set to `gopls`.
[[GH-2800]](https://github.com/fatih/vim-go/pull/2800)
[[GH-2858]](https://github.com/fatih/vim-go/pull/2858)
* Correct vim-go's help to correctly identify `g:go_referrer_mode`'s default.
[[GH-2832]](https://github.com/fatih/vim-go/pull/2832)
* Clear the quickfix list when `:GoLint` succeeds.
[[GH-2833]](https://github.com/fatih/vim-go/pull/2833)
* Respect arguments to `:GoDocBrowser`.
[[GH-2822]](https://github.com/fatih/vim-go/pull/2822)
* Use the correct path to documentation for struct fields with `:GoDocBrowser`.
[[GH-2822]](https://github.com/fatih/vim-go/pull/2822)
* Do not try parsing errors from terminal jobs when the working directory has
been removed.
[[GH-2824]](https://github.com/fatih/vim-go/pull/2824)
* Document that `g:go_jump_to_error` apples to running the metalinter on save,
too.
[[GH-2854]](https://github.com/fatih/vim-go/pull/2854)
* Ignore commented out import statements when executing `:GoImport`.
[[GH-2862]](https://github.com/fatih/vim-go/pull/2862)
* Interpret file paths in `go vet` errors relative to the current buffer's
directory.
[[GH-2882]](https://github.com/fatih/vim-go/pull/2882)
## v1.22 - (January 30, 2020)
BACKWARDS INCOMPATIBILITIES:
* Drop support for Vim 7.4. The minimum required version of Vim is now 8.0.1453.
[[GH-2495]](https://github.com/fatih/vim-go/pull/2495)
[[GH-2497]](https://github.com/fatih/vim-go/pull/2497)
* Drop support for `gometalinter`
[[GH-2494]](https://github.com/fatih/vim-go/pull/2494)
IMPROVEMENTS:
* Highlight the `go` keyword in go.mod files.
[[GH-2473]](https://github.com/fatih/vim-go/pull/2473)
* Use echo functions consistently.
[[GH-2458]](https://github.com/fatih/vim-go/pull/2458)
* Add support for managing goroutines in debugger.
[[GH-2463]](https://github.com/fatih/vim-go/pull/2463)
[[GH-2527]](https://github.com/fatih/vim-go/pull/2527)
* Document `g:go_doc_popup_window`.
[[GH-2506]](https://github.com/fatih/vim-go/pull/2506)
* Make `g:go_doc_popup_window=1` work for Neovim, too.
[[GH-2451]](https://github.com/fatih/vim-go/pull/2451)
[[GH-2512]](https://github.com/fatih/vim-go/pull/2512)
* Handle errors jumping to a definition in a file open in another Vim process
better.
[[GH-2518]](https://github.com/fatih/vim-go/pull/2518)
* Improve the UX when the gopls binary is missing.
[[GH-2522]](https://github.com/fatih/vim-go/pull/2522)
* Use gopls instead of guru for `:GoSameIds`.
[[GH-2519]](https://github.com/fatih/vim-go/pull/2519)
* Use gopls instead of guru for `:GoReferrers`.
[[GH-2535]](https://github.com/fatih/vim-go/pull/2535)
* Update documentation for `g:go_addtags_transform`.
[[GH-2541]](https://github.com/fatih/vim-go/pull/2541)
* Install most helper tools in module aware mode.
[[GH-2545]](https://github.com/fatih/vim-go/pull/2545)
* Add a new option, `g:go_referrers_mode` to allow the user to choose whether
to use gopls or guru for finding references.
[[GH-2566]](https://github.com/fatih/vim-go/pull/2566)
* Add options to control how gopls responds to completion requests.
[[GH-2567]](https://github.com/fatih/vim-go/pull/2567)
[[GH-2568]](https://github.com/fatih/vim-go/pull/2568)
* Add syntax highlighting for binary literals.
[[GH-2557]](https://github.com/fatih/vim-go/pull/2557)
* Improve highlighting of invalid numeric literals.
[[GH-2571]](https://github.com/fatih/vim-go/pull/2571)
[[GH-2587]](https://github.com/fatih/vim-go/pull/2587)
[[GH-2589]](https://github.com/fatih/vim-go/pull/2589)
[[GH-2584]](https://github.com/fatih/vim-go/pull/2584)
[[GH-2597]](https://github.com/fatih/vim-go/pull/2597)
[[GH-2599]](https://github.com/fatih/vim-go/pull/2599)
* Add highlighting of sections reported by gopls diagnostics' errors
and warnings.
[[GH-2569]](https://github.com/fatih/vim-go/pull/2569)
[[GH-2643]](https://github.com/fatih/vim-go/pull/2643)
* Make the highlighting of fzf decls configurable.
[[GH-2572]](https://github.com/fatih/vim-go/pull/2572)
[[GH-2579]](https://github.com/fatih/vim-go/pull/2579)
* Support renaming with gopls.
[[GH-2577]](https://github.com/fatih/vim-go/pull/2577)
[[GH-2618]](https://github.com/fatih/vim-go/pull/2618)
* Add an option, `g:go_gopls_enabled`, to allow gopls integration to be
disabled.
[[GH-2605]](https://github.com/fatih/vim-go/pull/2605)
[[GH-2609]](https://github.com/fatih/vim-go/pull/2609)
[[GH-2638]](https://github.com/fatih/vim-go/pull/2638)
[[GH-2640]](https://github.com/fatih/vim-go/pull/2640)
* Add a buffer level option, `b:go_fmt_options`, to control formatting options
per buffer.
[[GH-2613]](https://github.com/fatih/vim-go/pull/2613)
* Use build tags when running `:GoVet`.
[[GH-2615]](https://github.com/fatih/vim-go/pull/2615)
* Add new snippets for UltiSnips.
[[GH-2623]](https://github.com/fatih/vim-go/pull/2623)
[[GH-2627]](https://github.com/fatih/vim-go/pull/2627)
* Expand completions as snippets when `g:go_gopls_use_placeholders` is set.
[[GH-2624]](https://github.com/fatih/vim-go/pull/2624)
* Add a new function, `:GoDiagnostics` and an associated mapping for seeing
`gopls` diagnostics. Because of the performance implications on large
projects, `g:go_diagnostics_enabled` controls whether all diagnostics are
processed or only the diagnostics for the current buffer.
[[GH-2612]](https://github.com/fatih/vim-go/pull/2612)
* Explain how to find and detect multiple copies of vim-go in the FAQ.
[[GH-2632]](https://github.com/fatih/vim-go/pull/2632)
* Update the issue template to ask for the gopls version and
`:GoReportGitHubIssue` to provide it.
[[GH-2630]](https://github.com/fatih/vim-go/pull/2630)
* Use text properties when possible for some highlighting cases.
[[GH-2652]](https://github.com/fatih/vim-go/pull/2652)
[[GH-2662]](https://github.com/fatih/vim-go/pull/2662)
[[GH-2663]](https://github.com/fatih/vim-go/pull/2663)
[[GH-2672]](https://github.com/fatih/vim-go/pull/2672)
[[GH-2678]](https://github.com/fatih/vim-go/pull/2678)
BUG FIXES:
* Fix removal of missing directories from gopls workspaces.
[[GH-2507]](https://github.com/fatih/vim-go/pull/2507)
* Change to original window before trying to change directories when term job
ends.
[[GH-2508]](https://github.com/fatih/vim-go/pull/2508)
* Swallow errors when the hover info cannot be determined.
[[GH-2515]](https://github.com/fatih/vim-go/pull/2515)
* Fix errors when trying to debug lsp and hover.
[[GH-2516]](https://github.com/fatih/vim-go/pull/2516)
* Reset environment variables on Vim <= 8.0.1831 .
[[GH-2523]](https://github.com/fatih/vim-go/pull/2523)
* Handle empty results from delve.
[[GH-2526]](https://github.com/fatih/vim-go/pull/2526)
* Do not overwrite `updatetime` when `g:go_auto_sameids` or
`g:go_auto_type_info` is set.
[[GH-2529]](https://github.com/fatih/vim-go/pull/2529)
* Fix example for `g:go_debug_log_output` in docs.
[[GH-2547]](https://github.com/fatih/vim-go/pull/2547)
* Use FileChangedShellPost instead of FileChangedShell so that reload messages
are not hidden.
[[GH-2549]](https://github.com/fatih/vim-go/pull/2549)
* Restore cwd after `:GoTest` when `g:go_term_enabled` is set.
[[GH-2556]](https://github.com/fatih/vim-go/pull/2556)
* Expand struct variable correctly in the variables debug window.
[[GH-2574]](https://github.com/fatih/vim-go/pull/2574)
* Show output from errcheck when there are failures other than problems it can
report.
[[GH-2667]](https://github.com/fatih/vim-go/pull/2667)
## v1.21 - (September 11, 2019)
BACKWARDS INCOMPATIBILITIES:
* `g:go_metalinter_disabled` has been removed.
[[GH-2375]](https://github.com/fatih/vim-go/pull/2375)
IMPROVEMENTS:
* Add a new option, `g:go_code_completion_enabled`, to control whether omnifunc
is set.
[[GH-2229]](https://github.com/fatih/vim-go/pull/2229)
* Use build tags with golangci-lint.
[[GH-2261]](https://github.com/fatih/vim-go/pull/2261)
* Allow debugging of packages outside of GOPATH without a go.mod file.
[[GH-2269]](https://github.com/fatih/vim-go/pull/2269)
* Show which example failed when Example tests fail
[[GH-2277]](https://github.com/fatih/vim-go/pull/2277)
* Show function signature and return types in preview window when autocompleting functions and methods.
[[GH-2289]](https://github.com/fatih/vim-go/pull/2289)
* Improve the user experience when using null modules.
[[GH-2300]](https://github.com/fatih/vim-go/pull/2300)
* Modify `:GoReportGitHubIssue` to include vim-go configuration values
[[GH-2323]](https://github.com/fatih/vim-go/pull/2323)
* Respect `g:go_info_mode='gopls'` in go#complete#GetInfo.
[[GH-2313]](https://github.com/fatih/vim-go/pull/2313)
* Allow `:GoLint`, `:GoErrCheck`, and `:GoDebug` to work in null modules.
[[GH-2335]](https://github.com/fatih/vim-go/pull/2335)
* Change default value for `g:go_info_mode` and `g:go_def_mode` to `'gopls'`.
[[GH-2329]](https://github.com/fatih/vim-go/pull/2329)
* Add a new option, `g:go_doc_popup_window` to optionally use a popup window
for godoc in Vim 8.1.1513 and later.
[[GH-2347]](https://github.com/fatih/vim-go/pull/2347)
* Add `:GoAddWorkspace` function to support multiple workspaces with gopls.
[[GH-2356]](https://github.com/fatih/vim-go/pull/2356)
* Install gopls from its stable package.
[[GH-2360]](https://github.com/fatih/vim-go/pull/2360)
* Disambiguate progress message when initializing gopls.
[[GH-2369]](https://github.com/fatih/vim-go/pull/2369)
* Calculate LSP position correctly when on a line that contains multi-byte
characters before the position.
[[GH-2389]](https://github.com/fatih/vim-go/pull/2389)
* Calculate Vim position correctly from LSP text position.
[[GH-2395]](https://github.com/fatih/vim-go/pull/2395)
* Use the statusline to display gopls initialization status messages and only
echo the statuses when `g:go_echo_command_info` is set.
[[GH-2422]](https://github.com/fatih/vim-go/pull/2422)
* Send configuration to gopls so that build tags will be considered and hover
content won't have documentation.
[[GH-2429]](https://github.com/fatih/vim-go/pull/2429)
* Add a new option, `g:go_term_close_on_exit`, to control whether jobs run in a
terminal window will close the terminal window when the job exits.
[[GH-2409]](https://github.com/fatih/vim-go/pull/2409)
* Allow `g:go_template_file` and `g:go_template_test_files` to reside outside
of vim-go's template directory.
[[GH-2434]](https://github.com/fatih/vim-go/pull/2434)
* Add a new command, `:GoLSPDebugBrowser`, to open a browser to gopls debugging
view.
[[GH-2436]](https://github.com/fatih/vim-go/pull/2436)
* Restart gopls automatically when it is updated via `:GoUpdateBinaries`.
[[GH-2453]](https://github.com/fatih/vim-go/pull/2453)
* Reset `'more'` while installing binaries to avoid unnecessary more prompts.
[[GH-2457]](https://github.com/fatih/vim-go/pull/2457)
* Highlight `%w` as a format specifier (for Go 1.13).
[[GH-2433]](https://github.com/fatih/vim-go/pull/2433)
* Handle changes to Go 1.13's go vet output that gometalinter isn't expecting.
[[GH-2475]](https://github.com/fatih/vim-go/pull/2475)
* Make `golangci-lint` the default value for `g:go_metalinter_command`.
[[GH-2478]](https://github.com/fatih/vim-go/pull/2478)
* Parse compiler errors from Go 1.13 `go vet` correctly.
[[GH-2485]](https://github.com/fatih/vim-go/pull/2485)
BUG FIXES:
* display info about function and function types whose parameters are
`interface{}` without truncating the function signature.
[[GH-2244]](https://github.com/fatih/vim-go/pull/2244)
* install tools that require GOPATH mode when in module mode.
[[GH-2253]](https://github.com/fatih/vim-go/pull/2253)
* Detect GOPATH when starting `gopls`
[[GH-2255]](https://github.com/fatih/vim-go/pull/2255)
* Handle `gopls` responses in the same window from which the respective request
originated.
[[GH-2266]](https://github.com/fatih/vim-go/pull/2266)
* Show completion matches from gocode.
[[GH-2267]](https://github.com/fatih/vim-go/pull/2267)
* Show the completion preview window.
[[GH-2268]](https://github.com/fatih/vim-go/pull/2268)
* Set the anchor for method documentation correctly.
[[GH-2276]](https://github.com/fatih/vim-go/pull/2276)
* Respect the LSP information for determining where candidate matches start.
[[GH-2291]](https://github.com/fatih/vim-go/pull/2291)
* Restore environment variables with backslashes correctly.
[[GH-2292]](https://github.com/fatih/vim-go/pull/2292)
* Modify handling of gopls output for `:GoInfo` to ensure the value will be
displayed.
[[GH-2311]](https://github.com/fatih/vim-go/pull/2311)
* Run `:GoLint` successfully in null modules.
[[GH-2318]](https://github.com/fatih/vim-go/pull/2318)
* Ensure actions on save work in new buffers that have not yet been persisted to disk.
[[GH-2319]](https://github.com/fatih/vim-go/pull/2319)
* Restore population of information in `:GoReportGitHubIssue`.
[[GH-2312]](https://github.com/fatih/vim-go/pull/2312)
* Do not jump back to the originating window when jumping to definitions with
`g:go_def_mode='gopls'`.
[[GH-2327]](https://github.com/fatih/vim-go/pull/2327)
* Fix getting information about a valid identifier for which gopls returns no
information (e.g. calling `:GoInfo` on a package identifier).
[[GH-2339]](https://github.com/fatih/vim-go/pull/2339)
* Fix tab completion of package names on the cmdline in null modules.
[[GH-2342]](https://github.com/fatih/vim-go/pull/2342)
* Display identifier info correctly when the identifier has no godoc.
[[GH-2373]](https://github.com/fatih/vim-go/pull/2373)
* Fix false positives when saving a buffer and `g:go_metalinter_command` is
`golangci-lint`.
[[GH-2367]](https://github.com/fatih/vim-go/pull/2367)
* Fix `:GoDebugRestart`.
[[GH-2390]](https://github.com/fatih/vim-go/pull/2390)
* Do not execute tests twice in terminal mode.
[[GH-2397]](https://github.com/fatih/vim-go/pull/2397)
* Do not open a new buffer in Neovim when there are compilation errors and
terminal mode is enabled.
[[GH-2401]](https://github.com/fatih/vim-go/pull/2401)
* Fix error due to typo in implementation of `:GoAddWorkspace`.
[[GH-2415]](https://github.com/fatih/vim-go/pull/2401)
* Do not format the file automatically when `g:go_format_autosave` is set and
the file being written is not the current file.
[[GH-2442]](https://github.com/fatih/vim-go/pull/2442)
* Fix `go-debug-stepout` mapping.
[[GH-2464]](https://github.com/fatih/vim-go/pull/2464)
* Handle paths with spaces correctly when executing jobs.
[[GH-2472]](https://github.com/fatih/vim-go/pull/2472)
* Remove a space in the default value for `g:go_debug_log_output`, so that
Delve will start on Windows.
[[GH-2480]](https://github.com/fatih/vim-go/pull/2480)
## 1.20 - (April 22, 2019) ## 1.20 - (April 22, 2019)
FEATURES: FEATURES:
@ -13,7 +537,7 @@ FEATURES:
* New `:GoDefType` command to jump to a type definition from an instance of the * New `:GoDefType` command to jump to a type definition from an instance of the
type. type.
BACKWARDS INCOMPATABILITIES: BACKWARDS INCOMPATIBILITIES:
* `g:go_highlight_function_arguments` is renamed to `g:go_highlight_function_parameters` * `g:go_highlight_function_arguments` is renamed to `g:go_highlight_function_parameters`
[[GH-2117]](https://github.com/fatih/vim-go/pull/2117) [[GH-2117]](https://github.com/fatih/vim-go/pull/2117)

View file

@ -1,6 +1,6 @@
FROM golang:1.12.1 FROM golang:1.15.1
RUN apt-get update -y && \ RUN apt-get update -y --allow-insecure-repositories && \
apt-get install -y build-essential curl git libncurses5-dev python3-pip && \ apt-get install -y build-essential curl git libncurses5-dev python3-pip && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@ -10,11 +10,18 @@ RUN pip3 install vim-vint
RUN useradd -ms /bin/bash -d /vim-go vim-go RUN useradd -ms /bin/bash -d /vim-go vim-go
USER vim-go USER vim-go
COPY scripts/install-vim /vim-go/scripts/install-vim
WORKDIR /vim-go
RUN scripts/install-vim vim-8.0
RUN scripts/install-vim vim-8.2
RUN scripts/install-vim nvim
COPY . /vim-go/ COPY . /vim-go/
WORKDIR /vim-go WORKDIR /vim-go
RUN scripts/install-vim vim-7.4 RUN scripts/install-tools vim-8.0
RUN scripts/install-vim vim-8.0 RUN scripts/install-tools vim-8.2
RUN scripts/install-vim nvim RUN scripts/install-tools nvim
ENTRYPOINT ["make"] ENTRYPOINT ["make"]

View file

@ -1,4 +1,4 @@
VIMS ?= vim-7.4 vim-8.0 nvim VIMS ?= vim-8.0 vim-8.2 nvim
all: install lint test all: install lint test
@ -6,6 +6,7 @@ install:
@echo "==> Installing Vims: $(VIMS)" @echo "==> Installing Vims: $(VIMS)"
@for vim in $(VIMS); do \ @for vim in $(VIMS); do \
./scripts/install-vim $$vim; \ ./scripts/install-vim $$vim; \
./scripts/install-tools $$vim; \
done done
test: test:
@ -16,7 +17,7 @@ test:
lint: lint:
@echo "==> Running linting tools" @echo "==> Running linting tools"
@./scripts/lint vim-8.0 @./scripts/lint vim-8.2
docker: docker:
@echo "==> Building/starting Docker container" @echo "==> Building/starting Docker container"

View file

@ -1,4 +1,6 @@
# vim-go [![Build Status](http://img.shields.io/travis/fatih/vim-go.svg?style=flat-square)](https://travis-ci.org/fatih/vim-go) # vim-go [![Build Status](http://img.shields.io/travis/fatih/vim-go.svg?style=flat-square)](https://travis-ci.org/fatih/vim-go) [![GitHub Actions Status](https://github.com/fatih/vim-go/workflows/test/badge.svg)](https://github.com/fatih/vim-go/actions)
<p align="center"> <p align="center">
<img style="float: right;" src="assets/vim-go.png" alt="Vim-go logo"/> <img style="float: right;" src="assets/vim-go.png" alt="Vim-go logo"/>
@ -13,15 +15,15 @@ This plugin adds Go language support for Vim, with the following main features:
* Quickly execute your current file(s) with `:GoRun`. * Quickly execute your current file(s) with `:GoRun`.
* Improved syntax highlighting and folding. * Improved syntax highlighting and folding.
* Debug programs with integrated `delve` support with `:GoDebugStart`. * Debug programs with integrated `delve` support with `:GoDebugStart`.
* Completion support via `gocode` and `gopls`. * Completion and many other features support via `gopls`.
* `gofmt` or `goimports` on save keeps the cursor position and undo history. * formatting on save keeps the cursor position and undo history.
* Go to symbol/declaration with `:GoDef`. * Go to symbol/declaration with `:GoDef`.
* Look up documentation with `:GoDoc` or `:GoDocBrowser`. * Look up documentation with `:GoDoc` or `:GoDocBrowser`.
* Easily import packages via `:GoImport`, remove them via `:GoDrop`. * Easily import packages via `:GoImport`, remove them via `:GoDrop`.
* Precise type-safe renaming of identifiers with `:GoRename`. * Precise type-safe renaming of identifiers with `:GoRename`.
* See which code is covered by tests with `:GoCoverage`. * See which code is covered by tests with `:GoCoverage`.
* Add or remove tags on struct fields with `:GoAddTags` and `:GoRemoveTags`. * Add or remove tags on struct fields with `:GoAddTags` and `:GoRemoveTags`.
* Call `gometalinter` with `:GoMetaLinter` to invoke all possible linters * Call `golangci-lint` with `:GoMetaLinter` to invoke all possible linters
(`golint`, `vet`, `errcheck`, `deadcode`, etc.) and put the result in the (`golint`, `vet`, `errcheck`, `deadcode`, etc.) and put the result in the
quickfix or location list. quickfix or location list.
* Lint your code with `:GoLint`, run your code through `:GoVet` to catch static * Lint your code with `:GoLint`, run your code through `:GoVet` to catch static
@ -30,10 +32,12 @@ This plugin adds Go language support for Vim, with the following main features:
`:GoCallees`, and `:GoReferrers`. `:GoCallees`, and `:GoReferrers`.
* ... and many more! Please see [doc/vim-go.txt](doc/vim-go.txt) for more * ... and many more! Please see [doc/vim-go.txt](doc/vim-go.txt) for more
information. information.
* The `gopls` instance can be shared with other Vim plugins.
* Vim-go's use of `gopls` can be disabled.
## Install ## Install
vim-go requires at least Vim 7.4.2009 or Neovim 0.3.1. vim-go requires at least Vim 8.0.1453 or Neovim 0.4.0.
The [**latest stable release**](https://github.com/fatih/vim-go/releases/latest) is the The [**latest stable release**](https://github.com/fatih/vim-go/releases/latest) is the
recommended version to use. If you choose to use the master branch instead, recommended version to use. If you choose to use the master branch instead,
@ -68,8 +72,28 @@ Depending on your installation method, you may have to generate the plugin's
[`help tags`](http://vimhelp.appspot.com/helphelp.txt.html#%3Ahelptags) [`help tags`](http://vimhelp.appspot.com/helphelp.txt.html#%3Ahelptags)
manually (e.g. `:helptags ALL`). manually (e.g. `:helptags ALL`).
We also have an [official vim-go tutorial](https://github.com/fatih/vim-go-tutorial). We also have an [official vim-go tutorial](https://github.com/fatih/vim-go/wiki).
## FAQ and troubleshooting
The FAQ and troubleshooting tips are in the documentation and can be quickly
accessed using `:help go-troubleshooting`. If you believe you've found a bug or
shortcoming in vim-go that is neither addressed by help nor in [existing
issues](https://github.com/fatih/vim-go/issues), please open an issue with
clear reproduction steps. `:GoReportGitHubIssue` can be used pre-populate a lot
of the information needed when creating a new issue.
## Contributing
All PRs are welcome. If you are planning to contribute a large patch or to
integrate a new tool, please create an issue first to get any upfront questions
or design decisions out of the way first.
You can run the tests locally by running `make`. It will lint the VimL for you,
lint the documentation, and run the tests against the minimum required version
of Vim, other versions of Vim that may be critical to support, and Neovim.
## License ## License
The BSD 3-Clause License - see [`LICENSE`](LICENSE) for more details The BSD 3-Clause License - see [`LICENSE`](LICENSE) for more details

View file

@ -121,10 +121,10 @@ function! s:source(mode,...) abort
\ decl.col \ decl.col
\) \)
call add(ret_decls, printf("%s\t%s %s\t%s", call add(ret_decls, printf("%s\t%s %s\t%s",
\ s:color(decl.ident . space, "Function"), \ s:color(decl.ident . space, "goDeclsFzfFunction"),
\ s:color(decl.keyword, "Keyword"), \ s:color(decl.keyword, "goDeclsFzfKeyword"),
\ s:color(pos, "SpecialComment"), \ s:color(pos, "goDeclsFzfSpecialComment"),
\ s:color(decl.full, "Comment"), \ s:color(decl.full, "goDeclsFzfComment"),
\)) \))
endfor endfor

View file

@ -11,7 +11,43 @@ function! go#auto#template_autocreate()
call go#template#create() call go#template#create()
endfunction endfunction
function! go#auto#echo_go_info() function! go#auto#complete_done()
call s:echo_go_info()
call s:ExpandSnippet()
endfunction
function! s:ExpandSnippet() abort
if !exists('v:completed_item') || empty(v:completed_item) || !go#config#GoplsUsePlaceholders()
return
endif
let l:engine = go#config#SnippetEngine()
if l:engine is 'ultisnips'
if !get(g:, 'did_plugin_ultisnips', 0)
return
endif
" the snippet may have a '{\}' in it. For UltiSnips, that should be spelled
" \{}. fmt.Printf is such a snippet that can be used to demonstrate.
let l:snippet = substitute(v:completed_item.word, '{\\}', '\{}', 'g')
call UltiSnips#Anon(l:snippet, v:completed_item.word, '', 'i')
" elseif l:engine is 'neosnippet'
" " TODO(bc): make the anonymous expansion for neosnippet work
"
" if !get(g:, 'loaded_neosnippet') is 1
" return
" endif
"
" " neosnippet#anonymous doesn't need a trigger, so replace the
" " completed_item.word with an empty string before calling neosnippet#anonymous
" let l:snippet = substitute(v:completed_item.word, '{\\}', '\{\}', 'g')
" call setline('.', substitute(getline('.'), substitute(v:completed_item.word, '\', '\\', 'g'), '', ''))
" call neosnippet#anonymous(l:snippet)
endif
endfunction
function! s:echo_go_info()
if !go#config#EchoGoInfo() if !go#config#EchoGoInfo()
return return
endif endif
@ -21,46 +57,109 @@ function! go#auto#echo_go_info()
endif endif
let item = v:completed_item let item = v:completed_item
if !has_key(item, "info") if !has_key(item, "user_data")
return return
endif endif
if empty(item.info) if empty(item.user_data)
return return
endif endif
redraws! | echo "vim-go: " | echohl Function | echon item.info | echohl None redraws! | echo "vim-go: " | echohl Function | echon item.user_data | echohl None
endfunction endfunction
function! go#auto#auto_type_info() let s:timer_id = 0
if !go#config#AutoTypeInfo() || !filereadable(expand('%:p'))
" go#auto#update_autocmd() will be called on BufEnter,CursorHold. This
" configures the augroup according to conditions below.
"
" | # | has_timer | should_enable | do |
" |---|-----------|---------------|------------------------------------|
" | 1 | false | false | return early |
" | 2 | true | true | return early |
" | 3 | true | false | clear the group and stop the timer |
" | 4 | false | true | configure the group |
function! go#auto#update_autocmd()
let has_timer = get(b:, 'has_timer')
let should_enable = go#config#AutoTypeInfo() || go#config#AutoSameids()
if (!has_timer && !should_enable) || (has_timer && should_enable)
return return
endif endif
" GoInfo automatic update if has_timer
call go#tool#Info(0) augroup vim-go-buffer-auto
autocmd! * <buffer>
augroup END
let b:has_timer = 0
call s:timer_stop()
return
endif
augroup vim-go-buffer-auto
autocmd! * <buffer>
autocmd CursorMoved <buffer> call s:timer_restart()
autocmd BufLeave <buffer> call s:timer_stop()
augroup END
let b:has_timer = 1
call s:timer_start()
endfunction endfunction
function! go#auto#auto_sameids() function! s:timer_restart()
if !go#config#AutoSameids() || !filereadable(expand('%:p')) if isdirectory(expand('%:p:h'))
return call s:timer_stop()
call s:timer_start()
endif endif
endfunction
" GoSameId automatic update function! s:timer_stop()
call go#guru#SameIds(0) if s:timer_id
call timer_stop(s:timer_id)
let s:timer_id = 0
endif
endfunction
function! s:timer_start()
let s:timer_id = timer_start(go#config#Updatetime(), function('s:handler'))
endfunction
function! s:handler(timer_id)
if go#config#AutoTypeInfo()
call go#tool#Info(0)
endif
if go#config#AutoSameids()
call go#guru#SameIds(0)
endif
let s:timer_id = 0
endfunction endfunction
function! go#auto#fmt_autosave() function! go#auto#fmt_autosave()
if !go#config#FmtAutosave() || !filereadable(expand('%:p')) if !(isdirectory(expand('%:p:h')) && expand('<afile>:p') == expand('%:p'))
return
endif
if !(go#config#FmtAutosave() || go#config#ImportsAutosave())
return
endif
if go#config#ImportsAutosave() && !(go#config#FmtAutosave() && go#config#FmtCommand() == 'goimports')
call go#fmt#Format(1)
" return early when the imports mode is goimports, because there's no need
" to format again when goimports was run
if go#config#ImportsMode() == 'goimports'
return
endif
endif
if !go#config#FmtAutosave()
return return
endif endif
" Go code formatting on save
call go#fmt#Format(-1) call go#fmt#Format(-1)
endfunction endfunction
function! go#auto#metalinter_autosave() function! go#auto#metalinter_autosave()
if !go#config#MetalinterAutosave() || !filereadable(expand('%:p')) if !go#config#MetalinterAutosave() || !isdirectory(expand('%:p:h'))
return return
endif endif
@ -69,7 +168,7 @@ function! go#auto#metalinter_autosave()
endfunction endfunction
function! go#auto#modfmt_autosave() function! go#auto#modfmt_autosave()
if !go#config#ModFmtAutosave() || !filereadable(expand('%:p')) if !(go#config#ModFmtAutosave() && isdirectory(expand('%:p:h')) && expand('<afile>:p') == expand('%:p'))
return return
endif endif
@ -78,7 +177,7 @@ function! go#auto#modfmt_autosave()
endfunction endfunction
function! go#auto#asmfmt_autosave() function! go#auto#asmfmt_autosave()
if !go#config#AsmfmtAutosave() || !filereadable(expand('%:p')) if !(go#config#AsmfmtAutosave() && isdirectory(expand('%:p:h')) && expand('<afile>:p') == expand('%:p'))
return return
endif endif

View file

@ -9,8 +9,8 @@ function! go#cmd#autowrite() abort
for l:nr in range(0, bufnr('$')) for l:nr in range(0, bufnr('$'))
if buflisted(l:nr) && getbufvar(l:nr, '&modified') if buflisted(l:nr) && getbufvar(l:nr, '&modified')
" Sleep one second to make sure people see the message. Otherwise it is " Sleep one second to make sure people see the message. Otherwise it is
" often immediacy overwritten by the async messages (which also don't " often immediately overwritten by the async messages (which also
" invoke the "hit ENTER" prompt). " doesn't invoke the "hit ENTER" prompt).
call go#util#EchoWarning('[No write since last change]') call go#util#EchoWarning('[No write since last change]')
sleep 1 sleep 1
return return
@ -32,7 +32,7 @@ function! go#cmd#Build(bang, ...) abort
\ map(copy(a:000), "expand(v:val)") + \ map(copy(a:000), "expand(v:val)") +
\ [".", "errors"] \ [".", "errors"]
" Vim and Neovim async. " Vim and Neovim async
if go#util#has_job() if go#util#has_job()
call s:cmd_job({ call s:cmd_job({
\ 'cmd': ['go'] + args, \ 'cmd': ['go'] + args,
@ -41,8 +41,15 @@ function! go#cmd#Build(bang, ...) abort
\ 'statustype': 'build' \ 'statustype': 'build'
\}) \})
" Vim 7.4 without async " Vim without async
else else
let l:status = {
\ 'desc': 'current status',
\ 'type': 'build',
\ 'state': "started",
\ }
call go#statusline#Update(expand('%:p:h'), l:status)
let default_makeprg = &makeprg let default_makeprg = &makeprg
let &makeprg = "go " . join(go#util#Shelllist(args), ' ') let &makeprg = "go " . join(go#util#Shelllist(args), ' ')
@ -68,10 +75,16 @@ function! go#cmd#Build(bang, ...) abort
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) && !a:bang if !empty(errors) && !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
let l:status.state = 'failed'
else else
call go#util#EchoSuccess("[build] SUCCESS") let l:status.state = 'success'
if go#config#EchoCommandInfo()
call go#util#EchoSuccess("[build] SUCCESS")
endif
endif endif
call go#statusline#Update(expand('%:p:h'), l:status)
endif endif
endfunction endfunction
@ -104,16 +117,15 @@ endfunction
" Run runs the current file (and their dependencies if any) in a new terminal. " Run runs the current file (and their dependencies if any) in a new terminal.
function! go#cmd#RunTerm(bang, mode, files) abort function! go#cmd#RunTerm(bang, mode, files) abort
let cmd = "go run " let cmd = ["go", "run"]
let tags = go#config#BuildTags() if len(go#config#BuildTags()) > 0
if len(tags) > 0 call extend(cmd, ["-tags", go#config#BuildTags()])
let cmd .= "-tags " . go#util#Shellescape(tags) . " "
endif endif
if empty(a:files) if empty(a:files)
let cmd .= go#util#Shelljoin(go#tool#Files()) call extend(cmd, go#tool#Files())
else else
let cmd .= go#util#Shelljoin(map(copy(a:files), "expand(v:val)"), 1) call extend(cmd, map(copy(a:files), funcref('s:expandRunArgs')))
endif endif
call go#term#newmode(a:bang, cmd, s:runerrorformat(), a:mode) call go#term#newmode(a:bang, cmd, s:runerrorformat(), a:mode)
endfunction endfunction
@ -123,7 +135,7 @@ endfunction
" suitable for long running apps, because vim is blocking by default and " suitable for long running apps, because vim is blocking by default and
" calling long running apps will block the whole UI. " calling long running apps will block the whole UI.
function! go#cmd#Run(bang, ...) abort function! go#cmd#Run(bang, ...) abort
if has('nvim') if go#config#TermEnabled()
call go#cmd#RunTerm(a:bang, '', a:000) call go#cmd#RunTerm(a:bang, '', a:000)
return return
endif endif
@ -134,61 +146,101 @@ function! go#cmd#Run(bang, ...) abort
" anything. Once this is implemented we're going to make :GoRun async " anything. Once this is implemented we're going to make :GoRun async
endif endif
let cmd = "go run " let l:status = {
let tags = go#config#BuildTags() \ 'desc': 'current status',
if len(tags) > 0 \ 'type': 'run',
let cmd .= "-tags " . go#util#Shellescape(tags) . " " \ 'state': "started",
\ }
call go#statusline#Update(expand('%:p:h'), l:status)
let l:cmd = ['go', 'run']
let l:tags = go#config#BuildTags()
if len(l:tags) > 0
let l:cmd = l:cmd + ['-tags', l:tags]
endif endif
if a:0 == 0
let l:files = go#tool#Files()
else
let l:files = map(copy(a:000), funcref('s:expandRunArgs'))
endif
let l:cmd = l:cmd + l:files
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let l:dir = getcwd()
if go#util#IsWin() if go#util#IsWin()
if a:0 == 0 try
exec '!' . cmd . go#util#Shelljoin(go#tool#Files(), 1) if go#util#HasDebug('shell-commands')
else call go#util#EchoInfo(printf('shell command: %s', string(l:cmd)))
exec '!' . cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1) endif
endif
execute l:cd . fnameescape(expand("%:p:h"))
exec printf('!%s', go#util#Shelljoin(l:cmd, 1))
finally
execute l:cd . fnameescape(l:dir)
endtry
let l:status.state = 'success'
if v:shell_error if v:shell_error
redraws! | echon "vim-go: [run] " | echohl ErrorMsg | echon "FAILED"| echohl None let l:status.state = 'failed'
if go#config#EchoCommandInfo()
redraws!
call go#util#EchoError('[run] FAILED')
endif
else else
redraws! | echon "vim-go: [run] " | echohl Function | echon "SUCCESS"| echohl None if go#config#EchoCommandInfo()
redraws!
call go#util#EchoSuccess('[run] SUCCESS')
endif
endif endif
call go#statusline#Update(expand('%:p:h'), l:status)
return return
endif endif
" :make expands '%' and '#' wildcards, so they must also be escaped " :make expands '%' and '#' wildcards, so they must also be escaped
let default_makeprg = &makeprg let l:default_makeprg = &makeprg
if a:0 == 0 let &makeprg = go#util#Shelljoin(l:cmd, 1)
let &makeprg = cmd . go#util#Shelljoin(go#tool#Files(), 1)
else
let &makeprg = cmd . go#util#Shelljoin(map(copy(a:000), "expand(v:val)"), 1)
endif
let l:listtype = go#list#Type("GoRun") let l:listtype = go#list#Type("GoRun")
try let l:status.state = 'success'
try
" backup user's errorformat, will be restored once we are finished " backup user's errorformat, will be restored once we are finished
let l:old_errorformat = &errorformat let l:old_errorformat = &errorformat
let &errorformat = s:runerrorformat() let &errorformat = s:runerrorformat()
if go#util#HasDebug('shell-commands')
call go#util#EchoInfo('shell command: ' . l:cmd)
endif
execute l:cd . fnameescape(expand("%:p:h"))
if l:listtype == "locationlist" if l:listtype == "locationlist"
exe 'lmake!' exe 'lmake!'
else else
exe 'make!' exe 'make!'
endif endif
finally finally
"restore errorformat "restore the working directory, errformat, and makeprg
execute cd . fnameescape(l:dir)
let &errorformat = l:old_errorformat let &errorformat = l:old_errorformat
let &makeprg = default_makeprg let &makeprg = l:default_makeprg
endtry endtry
let l:errors = go#list#Get(l:listtype) let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(l:errors)) call go#list#Window(l:listtype, len(l:errors))
if !empty(l:errors) && !a:bang if !empty(l:errors)
call go#list#JumpToFirst(l:listtype) let l:status.state = 'failed'
if !a:bang
call go#list#JumpToFirst(l:listtype)
endif
endif endif
call go#statusline#Update(expand('%:p:h'), l:status)
endfunction endfunction
" Install installs the package by simple calling 'go install'. If any argument " Install installs the package by simple calling 'go install'. If any argument
@ -255,9 +307,18 @@ function! go#cmd#Generate(bang, ...) abort
let &makeprg = "go generate " . goargs . ' ' . gofiles let &makeprg = "go generate " . goargs . ' ' . gofiles
endif endif
let l:listtype = go#list#Type("GoGenerate") let l:status = {
\ 'desc': 'current status',
\ 'type': 'generate',
\ 'state': "started",
\ }
call go#statusline#Update(expand('%:p:h'), l:status)
echon "vim-go: " | echohl Identifier | echon "generating ..."| echohl None if go#config#EchoCommandInfo()
call go#util#EchoProgress('generating ...')
endif
let l:listtype = go#list#Type("GoGenerate")
try try
if l:listtype == "locationlist" if l:listtype == "locationlist"
@ -273,13 +334,18 @@ function! go#cmd#Generate(bang, ...) abort
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if !empty(errors) if !empty(errors)
let l:status.status = 'failed'
if !a:bang if !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
endif endif
else else
redraws! | echon "vim-go: " | echohl Function | echon "[generate] SUCCESS"| echohl None let l:status.status = 'success'
if go#config#EchoCommandInfo()
redraws!
call go#util#EchoSuccess('[generate] SUCCESS')
endif
endif endif
call go#statusline#Update(expand(':%:p:h'), l:status)
endfunction endfunction
function! s:runerrorformat() function! s:runerrorformat()
@ -288,6 +354,18 @@ function! s:runerrorformat()
return l:errorformat return l:errorformat
endfunction endfunction
" s:expandRunArgs expands arguments for go#cmd#Run according to the
" documentation of :GoRun. When val is a readable file, it is expanded to the
" full path so that go run can be executed in the current buffer's directory.
" val is return unaltered otherwise to support non-file arguments to go run.
function! s:expandRunArgs(idx, val) abort
let l:val = expand(a:val)
if !filereadable(l:val)
return l:val
endif
return fnamemodify(l:val, ':p')")
endfunction
" --------------------- " ---------------------
" | Vim job callbacks | " | Vim job callbacks |
" --------------------- " ---------------------

View file

@ -2,276 +2,48 @@
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
function! s:gocodeCommand(cmd, args) abort
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 = 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)
return cmd
endfunction
function! s:sync_gocode(cmd, args, input) abort
" We might hit cache problems, as gocode doesn't handle different GOPATHs
" well. See: https://github.com/nsf/gocode/issues/239
let old_goroot = $GOROOT
let $GOROOT = go#util#env("goroot")
try
let cmd = s:gocodeCommand(a:cmd, a:args)
" gocode can sometimes be slow, so redraw now to avoid waiting for gocode
" to return before redrawing automatically.
redraw
let [l:result, l:err] = go#util#Exec(cmd, a:input)
finally
let $GOROOT = old_goroot
endtry
if l:err != 0
return "[0, []]"
endif
if &encoding != 'utf-8'
let l:result = iconv(l:result, 'utf-8', &encoding)
endif
return l:result
endfunction
function! s:gocodeAutocomplete() abort
" use the offset as is, because the cursor position is the position for
" which autocomplete candidates are needed.
return s:sync_gocode('autocomplete',
\ [expand('%:p'), go#util#OffsetCursor()],
\ go#util#GetLines())
endfunction
" go#complete#GoInfo returns the description of the identifier under the " go#complete#GoInfo returns the description of the identifier under the
" cursor. " cursor.
function! go#complete#GetInfo() abort function! go#complete#GetInfo() abort
return s:sync_info(0) return go#lsp#GetInfo()
endfunction
function! go#complete#Info(showstatus) abort
if go#util#has_job(1)
return s:async_info(1, a:showstatus)
else
return s:sync_info(1)
endif
endfunction
function! s:async_info(echo, showstatus)
let state = {'echo': a:echo}
" 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
let offset = go#util#OffsetCursor()+1
" We might hit cache problems, as gocode doesn't handle different GOPATHs
" well. See: https://github.com/nsf/gocode/issues/239
let env = {
\ "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 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('%')}
" doesn't work.
call extend(opts, {
\ 'env': env,
\ 'in_io': 'file',
\ 'in_name': s:gocodeFile(),
\ })
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()
let file = tempname()
call writefile(go#util#GetLines(), file)
return file
endfunction
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
let result = s:sync_gocode('autocomplete',
\ [expand('%:p'), offset],
\ go#util#GetLines())
let result = s:info_filter(a:echo, result)
return s:info_complete(a:echo, result)
endfunction
function! s:info_filter(echo, result) abort
if empty(a:result)
return ""
endif
let l:result = eval(a:result)
if len(l:result) != 2
return ""
endif
let l:candidates = l:result[1]
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:echo && l:candidates[0].info ==# "PANIC PANIC PANIC"
return ""
endif
return l:candidates[0].info
endif
let filtered = []
let wordMatch = '\<' . expand("<cword>") . '\>'
" escape single quotes in wordMatch before passing it to filter
let wordMatch = substitute(wordMatch, "'", "''", "g")
let filtered = filter(l:candidates, "v:val.info =~ '".wordMatch."'")
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(echo, result) abort
if a:echo
call go#util#ShowInfo(a:result)
endif
return a:result
endfunction
function! s:trim_bracket(val) abort
let a:val.word = substitute(a:val.word, '[(){}\[\]]\+$', '', '')
return a:val
endfunction
let s:completions = []
function! go#complete#GocodeComplete(findstart, base) abort
"findstart = 1 when we need to get the text length
if a:findstart == 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]
if s =~ '[(){}\{\}]'
return map(copy(s:completions[1]), 's:trim_bracket(v:val)')
endif
return s:completions[1]
endif
endfunction endfunction
function! go#complete#Complete(findstart, base) abort function! go#complete#Complete(findstart, base) abort
let l:state = {'done': 0, 'matches': []} if !go#config#GoplsEnabled()
return -3
endif
function! s:handler(state, matches) abort dict let l:state = {'done': 0, 'matches': [], 'start': -1}
function! s:handler(state, start, matches) abort dict
let a:state.start = a:start
let a:state.matches = a:matches let a:state.matches = a:matches
let a:state.done = 1 let a:state.done = 1
endfunction endfunction
"findstart = 1 when we need to get the start of the match "findstart = 1 when we need to get the start of the match
if a:findstart == 1 if a:findstart == 1
call go#lsp#Completion(expand('%:p'), line('.'), col('.'), funcref('s:handler', [l:state])) let [l:line, l:col] = getpos('.')[1:2]
let [l:line, l:col] = go#lsp#lsp#Position(l:line, l:col)
let l:completion = go#lsp#Completion(expand('%:p'), l:line, l:col, funcref('s:handler', [l:state]))
if l:completion
return -3
endif
while !l:state.done while !l:state.done
sleep 10m sleep 10m
endwhile endwhile
let s:completions = l:state.matches
if len(l:state.matches) == 0 if len(l:state.matches) == 0
" no matches. cancel and leave completion mode. " no matches. cancel and leave completion mode.
call go#util#EchoInfo("no matches") call go#util#EchoInfo("no matches")
return -3 return -3
endif endif
return col('.') let s:completions = l:state.matches
return go#lsp#lsp#PositionOf(getline(l:line+1), l:state.start-1)
else "findstart = 0 when we need to return the list of completions else "findstart = 0 when we need to return the list of completions
return s:completions return s:completions
endif endif
@ -281,11 +53,11 @@ function! go#complete#ToggleAutoTypeInfo() abort
if go#config#AutoTypeInfo() if go#config#AutoTypeInfo()
call go#config#SetAutoTypeInfo(0) call go#config#SetAutoTypeInfo(0)
call go#util#EchoProgress("auto type info disabled") call go#util#EchoProgress("auto type info disabled")
return else
end call go#config#SetAutoTypeInfo(1)
call go#util#EchoProgress("auto type info enabled")
call go#config#SetAutoTypeInfo(1) endif
call go#util#EchoProgress("auto type info enabled") call go#auto#update_autocmd()
endfunction endfunction
" restore Vi compatibility settings " restore Vi compatibility settings

View file

@ -2,23 +2,24 @@
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
func! Test_GetInfo() func! Test_GetInfo_gopls()
let g:go_info_mode = 'gopls'
call s:getinfo()
unlet g:go_info_mode
endfunction
func! s:getinfo()
let l:filename = 'complete/complete.go' let l:filename = 'complete/complete.go'
let l:tmp = gotest#load_fixture(l:filename) let l:tmp = gotest#load_fixture(l:filename)
try
call cursor(8, 3)
call cursor(8, 3) let expected = 'func Example(s string)'
let actual = go#complete#GetInfo()
let g:go_info_mode = 'gocode' call assert_equal(expected, actual)
let expected = 'func Example(s string)' finally
let actual = go#complete#GetInfo() call delete(l:tmp, 'rf')
call assert_equal(expected, actual) endtry
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 endfunction
" restore Vi compatibility settings " restore Vi compatibility settings

View file

@ -21,10 +21,12 @@ endfunction
function! go#config#SetBuildTags(value) abort function! go#config#SetBuildTags(value) abort
if a:value is '' if a:value is ''
silent! unlet g:go_build_tags silent! unlet g:go_build_tags
call go#lsp#ResetWorkspaceDirectories()
return return
endif endif
let g:go_build_tags = a:value let g:go_build_tags = a:value
call go#lsp#ResetWorkspaceDirectories()
endfunction endfunction
function! go#config#TestTimeout() abort function! go#config#TestTimeout() abort
@ -47,8 +49,23 @@ function! go#config#TermMode() abort
return get(g:, 'go_term_mode', 'vsplit') return get(g:, 'go_term_mode', 'vsplit')
endfunction endfunction
function! go#config#TermCloseOnExit() abort
return get(g:, 'go_term_close_on_exit', 1)
endfunction
function! go#config#TermReuse() abort
return get(g:, 'go_term_reuse', 0)
endfunction
function! go#config#SetTermCloseOnExit(value) abort
let g:go_term_close_on_exit = a:value
endfunction
function! go#config#TermEnabled() abort function! go#config#TermEnabled() abort
return has('nvim') && get(g:, 'go_term_enabled', 0) " nvim always support
" vim will support if terminal feature exists
let l:support = has('nvim') || has('terminal')
return support && get(g:, 'go_term_enabled', 0)
endfunction endfunction
function! go#config#SetTermEnabled(value) abort function! go#config#SetTermEnabled(value) abort
@ -72,7 +89,18 @@ function! go#config#StatuslineDuration() abort
endfunction endfunction
function! go#config#SnippetEngine() abort function! go#config#SnippetEngine() abort
return get(g:, 'go_snippet_engine', 'automatic') let l:engine = get(g:, 'go_snippet_engine', 'automatic')
if l:engine is? "automatic"
if get(g:, 'did_plugin_ultisnips') is 1
let l:engine = 'ultisnips'
elseif get(g:, 'loaded_neosnippet') is 1
let l:engine = 'neosnippet'
elseif get(g:, 'loaded_minisnip') is 1
let l:engine = 'minisnip'
endif
endif
return l:engine
endfunction endfunction
function! go#config#PlayBrowserCommand() abort function! go#config#PlayBrowserCommand() abort
@ -114,7 +142,7 @@ function! go#config#ListAutoclose() abort
endfunction endfunction
function! go#config#InfoMode() abort function! go#config#InfoMode() abort
return get(g:, 'go_info_mode', 'gocode') return get(g:, 'go_info_mode', 'gopls')
endfunction endfunction
function! go#config#GuruScope() abort function! go#config#GuruScope() abort
@ -139,30 +167,13 @@ function! go#config#SetGuruScope(scope) abort
endif endif
endfunction 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 function! go#config#EchoCommandInfo() abort
return get(g:, 'go_echo_command_info', 1) return get(g:, 'go_echo_command_info', 1)
endfunction endfunction
function! go#config#DocUrl() abort function! go#config#DocUrl() abort
let godoc_url = get(g:, 'go_doc_url', 'https://godoc.org') let godoc_url = get(g:, 'go_doc_url', 'https://pkg.go.dev')
if godoc_url isnot 'https://godoc.org' if godoc_url isnot 'https://pkg.go.dev'
" strip last '/' character if available " strip last '/' character if available
let last_char = strlen(godoc_url) - 1 let last_char = strlen(godoc_url) - 1
if godoc_url[last_char] == '/' if godoc_url[last_char] == '/'
@ -174,12 +185,15 @@ function! go#config#DocUrl() abort
return godoc_url return godoc_url
endfunction endfunction
function! go#config#DocPopupWindow() abort
return get(g:, 'go_doc_popup_window', 0)
endfunction
function! go#config#DefReuseBuffer() abort function! go#config#DefReuseBuffer() abort
return get(g:, 'go_def_reuse_buffer', 0) return get(g:, 'go_def_reuse_buffer', 0)
endfunction endfunction
function! go#config#DefMode() abort function! go#config#DefMode() abort
return get(g:, 'go_def_mode', 'guru') return get(g:, 'go_def_mode', 'gopls')
endfunction endfunction
function! go#config#DeclsIncludes() abort function! go#config#DeclsIncludes() abort
@ -192,9 +206,10 @@ endfunction
function! go#config#DebugWindows() abort function! go#config#DebugWindows() abort
return get(g:, 'go_debug_windows', { return get(g:, 'go_debug_windows', {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew', \ 'vars': 'leftabove 30vnew',
\ 'stack': 'leftabove 20new',
\ 'goroutines': 'botright 10new',
\ 'out': 'botright 5new',
\ } \ }
\ ) \ )
@ -211,7 +226,7 @@ function! go#config#DebugCommands() abort
endfunction endfunction
function! go#config#DebugLogOutput() abort function! go#config#DebugLogOutput() abort
return get(g:, 'go_debug_log_output', 'debugger, rpc') return get(g:, 'go_debug_log_output', 'debugger,rpc')
endfunction endfunction
function! go#config#LspLog() abort function! go#config#LspLog() abort
@ -236,6 +251,10 @@ function! go#config#AddtagsTransform() abort
return get(g:, 'go_addtags_transform', "snakecase") return get(g:, 'go_addtags_transform', "snakecase")
endfunction endfunction
function! go#config#AddtagsSkipUnexported() abort
return get(g:, 'go_addtags_skip_unexported', 0)
endfunction
function! go#config#TemplateAutocreate() abort function! go#config#TemplateAutocreate() abort
return get(g:, "go_template_autocreate", 1) return get(g:, "go_template_autocreate", 1)
endfunction endfunction
@ -245,31 +264,15 @@ function! go#config#SetTemplateAutocreate(value) abort
endfunction endfunction
function! go#config#MetalinterCommand() abort function! go#config#MetalinterCommand() abort
return get(g:, "go_metalinter_command", "gometalinter") return get(g:, "go_metalinter_command", "golangci-lint")
endfunction endfunction
function! go#config#MetalinterAutosaveEnabled() abort function! go#config#MetalinterAutosaveEnabled() abort
let l:default_enabled = ["vet", "golint"] return get(g:, "go_metalinter_autosave_enabled", ["govet", "golint"])
if go#config#MetalinterCommand() == "golangci-lint"
let l:default_enabled = ["govet", "golint"]
endif
return get(g:, "go_metalinter_autosave_enabled", default_enabled)
endfunction endfunction
function! go#config#MetalinterEnabled() abort function! go#config#MetalinterEnabled() abort
let l:default_enabled = ["vet", "golint", "errcheck"] return get(g:, "go_metalinter_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 endfunction
function! go#config#GolintBin() abort function! go#config#GolintBin() abort
@ -296,6 +299,10 @@ function! go#config#FmtAutosave() abort
return get(g:, "go_fmt_autosave", 1) return get(g:, "go_fmt_autosave", 1)
endfunction endfunction
function! go#config#ImportsAutosave() abort
return get(g:, 'go_imports_autosave', 0)
endfunction
function! go#config#SetFmtAutosave(value) abort function! go#config#SetFmtAutosave(value) abort
let g:go_fmt_autosave = a:value let g:go_fmt_autosave = a:value
endfunction endfunction
@ -340,8 +347,12 @@ function! go#config#FmtCommand() abort
return get(g:, "go_fmt_command", "gofmt") return get(g:, "go_fmt_command", "gofmt")
endfunction endfunction
function! go#config#ImportsMode() abort
return get(g:, "go_imports_mode", "goimports")
endfunction
function! go#config#FmtOptions() abort function! go#config#FmtOptions() abort
return get(g:, "go_fmt_options", {}) return get(b:, "go_fmt_options", get(g:, "go_fmt_options", {}))
endfunction endfunction
function! go#config#FmtFailSilently() abort function! go#config#FmtFailSilently() abort
@ -356,8 +367,13 @@ function! go#config#PlayOpenBrowser() abort
return get(g:, "go_play_open_browser", 1) return get(g:, "go_play_open_browser", 1)
endfunction endfunction
function! go#config#RenameCommand() abort
" delegate to go#config#GorenameBin for backwards compatability.
return get(g:, "go_rename_command", go#config#GorenameBin())
endfunction
function! go#config#GorenameBin() abort function! go#config#GorenameBin() abort
return get(g:, "go_gorename_bin", "gorename") return get(g:, "go_gorename_bin", "gopls")
endfunction endfunction
function! go#config#GorenamePrefill() abort function! go#config#GorenamePrefill() abort
@ -451,10 +467,22 @@ function! go#config#HighlightVariableDeclarations() abort
return get(g:, 'go_highlight_variable_declarations', 0) return get(g:, 'go_highlight_variable_declarations', 0)
endfunction endfunction
function! go#config#HighlightDiagnosticErrors() abort
return get(g:, 'go_highlight_diagnostic_errors', 1)
endfunction
function! go#config#HighlightDiagnosticWarnings() abort
return get(g:, 'go_highlight_diagnostic_warnings', 1)
endfunction
function! go#config#HighlightDebug() abort function! go#config#HighlightDebug() abort
return get(g:, 'go_highlight_debug', 1) return get(g:, 'go_highlight_debug', 1)
endfunction endfunction
function! go#config#DebugBreakpointSignText() abort
return get(g:, 'go_debug_breakpoint_sign_text', '>')
endfunction
function! go#config#FoldEnable(...) abort function! go#config#FoldEnable(...) abort
if a:0 > 0 if a:0 > 0
return index(go#config#FoldEnable(), a:1) > -1 return index(go#config#FoldEnable(), a:1) > -1
@ -466,6 +494,82 @@ function! go#config#EchoGoInfo() abort
return get(g:, "go_echo_go_info", 1) return get(g:, "go_echo_go_info", 1)
endfunction endfunction
function! go#config#CodeCompletionEnabled() abort
return get(g:, "go_code_completion_enabled", 1)
endfunction
function! go#config#CodeCompletionIcase() abort
return get(g:, "go_code_completion_icase", 0)
endfunction
function! go#config#Updatetime() abort
let go_updatetime = get(g:, 'go_updatetime', 800)
return go_updatetime == 0 ? &updatetime : go_updatetime
endfunction
function! go#config#ReferrersMode() abort
return get(g:, 'go_referrers_mode', 'gopls')
endfunction
function! go#config#ImplementsMode() abort
return get(g:, 'go_implements_mode', 'guru')
endfunction
function! go#config#GoplsCompleteUnimported() abort
return get(g:, 'go_gopls_complete_unimported', v:null)
endfunction
function! go#config#GoplsDeepCompletion() abort
return get(g:, 'go_gopls_deep_completion', v:null)
endfunction
function! go#config#GoplsMatcher() abort
if !exists('g:go_gopls_matcher') && get(g:, 'g:go_gopls_fuzzy_matching', v:null) is 1
return 'fuzzy'
endif
return get(g:, 'go_gopls_matcher', v:null)
endfunction
function! go#config#GoplsStaticCheck() abort
return get(g:, 'go_gopls_staticcheck', v:null)
endfunction
function! go#config#GoplsUsePlaceholders() abort
return get(g:, 'go_gopls_use_placeholders', v:null)
endfunction
function! go#config#GoplsTempModfile() abort
return get(g:, 'go_gopls_temp_modfile', v:null)
endfunction
function! go#config#GoplsAnalyses() abort
return get(g:, 'go_gopls_analyses', v:null)
endfunction
function! go#config#GoplsLocal() abort
return get(g:, 'go_gopls_local', v:null)
endfunction
function! go#config#GoplsGofumpt() abort
return get(g:, 'go_gopls_gofumpt', v:null)
endfunction
function! go#config#GoplsSettings() abort
return get(g:, 'go_gopls_settings', v:null)
endfunction
function! go#config#GoplsEnabled() abort
return get(g:, 'go_gopls_enabled', 1)
endfunction
function! go#config#DiagnosticsEnabled() abort
return get(g:, 'go_diagnostics_enabled', 0)
endfunction
function! go#config#GoplsOptions() abort
return get(g:, 'go_gopls_options', ['-remote=auto'])
endfunction
" Set the default value. A value of "1" is a shortcut for this, for " Set the default value. A value of "1" is a shortcut for this, for
" compatibility reasons. " compatibility reasons.
if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1 if exists("g:go_gorename_prefill") && g:go_gorename_prefill == 1

View file

@ -0,0 +1,107 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
func! Test_SetBuildTags() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:dir = 'test-fixtures/config/buildtags'
let l:jumpstart = [0, 4, 2, 0]
execute 'e ' . printf('%s/buildtags.go', l:dir)
let l:jumpstartbuf = bufnr('')
call setpos('.', [l:jumpstartbuf, l:jumpstart[1], l:jumpstart[2], 0])
let l:expectedfilename = printf('%s/foo.go', l:dir)
let l:expected = [0, 5, 1, 0]
call assert_notequal(l:expected, l:jumpstart)
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expectedfilename, bufname("%"))
call assert_equal(l:expected, getpos('.'))
execute 'e ' . printf('%s/buildtags.go', l:dir)
" prepare to wait for the workspace/configuration request
let g:go_debug=['lsp']
" set the build constraint
call go#config#SetBuildTags('constrained')
" wait for the workspace/configuration request
let l:lsplog = getbufline('__GOLSP_LOG__', 1, '$')
let l:start = reltime()
while match(l:lsplog, 'workspace/configuration') == -1 && reltimefloat(reltime(l:start)) < 10
sleep 50m
let l:lsplog = getbufline('__GOLSP_LOG__', 1, '$')
endwhile
unlet g:go_debug
" close the __GOLSP_LOG__ window
only
" verify the cursor position within buildtags.go
call setpos('.', [l:jumpstartbuf, l:jumpstart[1], l:jumpstart[2], 0])
call assert_equal(l:jumpstart, getpos('.'))
let l:expectedfilename = printf('%s/constrainedfoo.go', l:dir)
let l:expected = [0, 6, 1, 0]
call assert_notequal(l:expected, l:jumpstart)
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expectedfilename, bufname("%"))
call assert_equal(l:expected, getpos('.'))
let l:lsplog = getbufline('__GOLSP_LOG__', 1, '$')
finally
call go#config#SetBuildTags('')
unlet g:go_def_mode
endtry
endfunc
func! Test_GoplsEnabled_Clear() abort
if !go#util#has_job()
return
endif
try
let g:go_gopls_enabled = 0
let l:tmp = gotest#write_file('gopls_disabled.go', [
\ 'package example',
\ '',
\ 'func Example() {',
\ "\tprintln(" . '"hello, world!")',
\ '}',
\ ] )
finally
unlet g:go_gopls_enabled
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -25,10 +25,10 @@ endfunction
" the code. Calling it again reruns the tests and shows the last updated " the code. Calling it again reruns the tests and shows the last updated
" coverage. " coverage.
function! go#coverage#Buffer(bang, ...) abort function! go#coverage#Buffer(bang, ...) abort
" we use matchaddpos() which was introduce with 7.4.330, be sure we have
" it: http://ftp.vim.org/vim/patches/7.4/7.4.330 " check if the version of Vim being tested supports matchaddpos()
if !exists("*matchaddpos") if !exists("*matchaddpos")
call go#util#EchoError("GoCoverage is supported with Vim version 7.4-330 or later") call go#util#EchoError("GoCoverage is not supported by your version of Vim.")
return -1 return -1
endif endif

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,10 @@ function! Test_GoDebugStart_RelativePackage() abort
call s:debug('./debug/debugmain') call s:debug('./debug/debugmain')
endfunction endfunction
function! Test_GoDebugStart_RelativePackage_NullModule() abort
call s:debug('./debug/debugmain', 1)
endfunction
function! Test_GoDebugStart_Package() abort function! Test_GoDebugStart_Package() abort
call s:debug('debug/debugmain') call s:debug('debug/debugmain')
endfunction endfunction
@ -20,20 +24,21 @@ function! Test_GoDebugStart_Errors() abort
endif endif
try try
let l:tmp = gotest#load_fixture('debug/compilerror/main.go')
let l:expected = [ let l:expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': '# debug/compilerror'}, \ {'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': 6, 'bufnr': bufnr('%'), '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'} \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exit status 2'}
\] \]
call setqflist([], 'r') call setqflist([], 'r')
let l:tmp = gotest#load_fixture('debug/compilerror/main.go')
call assert_false(exists(':GoDebugStop')) call assert_false(exists(':GoDebugStop'))
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
execute l:cd . ' debug/compilerror' execute l:cd . ' debug/compilerror'
call go#debug#Start(0) call go#debug#Start('debug')
let l:actual = getqflist() let l:actual = getqflist()
let l:start = reltime() let l:start = reltime()
@ -52,14 +57,22 @@ function! Test_GoDebugStart_Errors() abort
endtry endtry
endfunction endfunction
" s:debug takes 2 optional arguments. The first is a package to debug. The
" second is a flag to indicate whether to reset GOPATH after
" gotest#load_fixture is called in order to test behavior outside of GOPATH.
function! s:debug(...) abort function! s:debug(...) abort
if !go#util#has_job() if !go#util#has_job()
return return
endif endif
try try
let $oldgopath = $GOPATH
let l:tmp = gotest#load_fixture('debug/debugmain/debugmain.go') let l:tmp = gotest#load_fixture('debug/debugmain/debugmain.go')
if a:0 > 1 && a:2 == 1
let $GOPATH = $oldgopath
endif
call go#debug#Breakpoint(6) call go#debug#Breakpoint(6)
call assert_false(exists(':GoDebugStop')) call assert_false(exists(':GoDebugStop'))
@ -67,9 +80,9 @@ function! s:debug(...) abort
if a:0 == 0 if a:0 == 0
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
execute l:cd . ' debug/debugmain' execute l:cd . ' debug/debugmain'
let l:job = go#debug#Start(0) let l:job = go#debug#Start('debug')
else else
let l:job = go#debug#Start(0, a:1) let l:job = go#debug#Start('debug', a:1)
endif endif
let l:start = reltime() let l:start = reltime()

View file

@ -6,7 +6,7 @@ let s:go_stack = []
let s:go_stack_level = 0 let s:go_stack_level = 0
function! go#def#Jump(mode, type) abort function! go#def#Jump(mode, type) abort
let fname = fnamemodify(expand("%"), ':p:gs?\\?/?') let l:fname = fnamemodify(expand("%"), ':p:gs?\\?/?')
" so guru right now is slow for some people. previously we were using " so guru right now is slow for some people. previously we were using
" godef which also has it's own quirks. But this issue come up so many " godef which also has it's own quirks. But this issue come up so many
@ -66,7 +66,15 @@ function! go#def#Jump(mode, type) abort
let [l:out, l:err] = go#util#ExecInDir(l:cmd) let [l:out, l:err] = go#util#ExecInDir(l:cmd)
endif endif
elseif bin_name == 'gopls' elseif bin_name == 'gopls'
let [l:line, l:col] = getpos('.')[1:2] if !go#config#GoplsEnabled()
call go#util#EchoError("go_def_mode is 'gopls', but gopls is disabled")
return
endif
" reset l:fname when using gopls so that the filename will be converted to
" a URI correctly on windows.
let l:fname = expand('%')
let [l:line, l:col] = go#lsp#lsp#Position()
" delegate to gopls, with an empty job object and an exit status of 0 " delegate to gopls, with an empty job object and an exit status of 0
" (they're irrelevant for gopls). " (they're irrelevant for gopls).
if a:type if a:type
@ -162,9 +170,9 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
if filename != fnamemodify(expand("%"), ':p:gs?\\?/?') if filename != fnamemodify(expand("%"), ':p:gs?\\?/?')
" jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded " jump to existing buffer if, 1. we have enabled it, 2. the buffer is loaded
" and 3. there is buffer window number we switch to " and 3. there is buffer window number we switch to
if go#config#DefReuseBuffer() && bufloaded(filename) != 0 && bufwinnr(filename) != -1 if go#config#DefReuseBuffer() && bufwinnr(filename) != -1
" jumpt to existing buffer if it exists " jump to existing buffer if it exists
execute bufwinnr(filename) . 'wincmd w' call win_gotoid(bufwinid(filename))
else else
if &modified if &modified
let cmd = 'hide edit' let cmd = 'hide edit'
@ -186,7 +194,13 @@ function! go#def#jump_to_declaration(out, mode, bin_name) abort
endif endif
" open the file and jump to line and column " open the file and jump to line and column
exec cmd fnameescape(fnamemodify(filename, ':.')) try
exec cmd fnameescape(fnamemodify(filename, ':.'))
catch
if stridx(v:exception, ':E325:') < 0
call go#util#EchoError(v:exception)
endif
endtry
endif endif
endif endif
call cursor(line, col) call cursor(line, col)

View file

@ -2,19 +2,21 @@
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
scriptencoding utf-8
func! Test_jump_to_declaration_guru() abort func! Test_jump_to_declaration_guru() abort
try try
let l:filename = 'def/jump.go' let l:filename = 'def/jump.go'
let lnum = 5 let l:lnum = 5
let col = 6 let l:col = 6
let l:tmp = gotest#load_fixture(l:filename) let l:tmp = gotest#load_fixture(l:filename)
let guru_out = printf("%s:%d:%d: defined here as func main", filename, lnum, col) let l:guru_out = printf("%s:%d:%d: defined here as func main", l:filename, l:lnum, l:col)
call go#def#jump_to_declaration(guru_out, "", 'guru') call go#def#jump_to_declaration(l:guru_out, "", 'guru')
call assert_equal(filename, bufname("%")) call assert_equal(l:filename, bufname("%"))
call assert_equal(lnum, getcurpos()[1]) call assert_equal(l:lnum, getcurpos()[1])
call assert_equal(col, getcurpos()[2]) call assert_equal(l:col, getcurpos()[2])
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
@ -22,17 +24,17 @@ endfunc
func! Test_jump_to_declaration_godef() abort func! Test_jump_to_declaration_godef() abort
try try
let filename = 'def/jump.go' let l:filename = 'def/jump.go'
let lnum = 5 let l:lnum = 5
let col = 6 let l:col = 6
let l:tmp = gotest#load_fixture(l:filename) let l:tmp = gotest#load_fixture(l:filename)
let godef_out = printf("%s:%d:%d\ndefined here as func main", filename, lnum, col) let l:godef_out = printf("%s:%d:%d\ndefined here as func main", l:filename, l:lnum, l:col)
call go#def#jump_to_declaration(godef_out, "", 'godef') call go#def#jump_to_declaration(godef_out, "", 'godef')
call assert_equal(filename, bufname("%")) call assert_equal(l:filename, bufname("%"))
call assert_equal(lnum, getcurpos()[1]) call assert_equal(l:lnum, getcurpos()[1])
call assert_equal(col, getcurpos()[2]) call assert_equal(l:col, getcurpos()[2])
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
@ -40,33 +42,180 @@ endfunc
func! Test_Jump_leaves_lists() abort func! Test_Jump_leaves_lists() abort
try try
let filename = 'def/jump.go' let l:filename = 'def/jump.go'
let l:tmp = gotest#load_fixture(l:filename) 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'}] let l:expected = [{'lnum': 10, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'quux'}]
call setloclist(winnr(), copy(expected), 'r' ) call setloclist(0, copy(l:expected), 'r' )
call setqflist(copy(expected), 'r' ) call setqflist(copy(l:expected), 'r' )
let l:bufnr = bufnr('%') let l:bufnr = bufnr('%')
call cursor(6, 7) call cursor(6, 7)
if !go#util#has_job()
let g:go_def_mode='godef'
endif
call go#def#Jump('', 0) call go#def#Jump('', 0)
let start = reltime() if !go#util#has_job()
while bufnr('%') == l:bufnr && reltimefloat(reltime(start)) < 10 unlet g:go_def_mode
endif
let l:start = reltime()
while bufnr('%') == l:bufnr && reltimefloat(reltime(l:start)) < 10
sleep 100m sleep 100m
endwhile endwhile
let actual = getloclist(winnr()) let l:actual = getloclist(0)
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(l:actual, l:expected)
let actual = getqflist() let l:actual = getqflist()
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(l:actual, l:expected)
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
endfunc endfunc
func! Test_DefJump_gopls_simple_first() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('simple/firstposition/firstposition.go', [
\ 'package firstposition',
\ '',
\ 'func Example() {',
\ "\tid := " . '"foo"',
\ "\tprintln(" . '"id:", id)',
\ '}',
\ ] )
let l:expected = [0, 4, 2, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
func! Test_DefJump_gopls_simple_last() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('simple/lastposition/lastposition.go', [
\ 'package lastposition',
\ '',
\ 'func Example() {',
\ "\tid := " . '"foo"',
\ "\tprintln(" . '"id:", id)',
\ '}',
\ ] )
let l:expected = [0, 4, 2, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
func! Test_DefJump_gopls_MultipleCodeUnit_first() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('multiplecodeunit/firstposition/firstposition.go', [
\ 'package firstposition',
\ '',
\ 'func Example() {',
\ "\t𐐀, id := " . '"foo", "bar"',
\ "\tprintln(" . '"(𐐀, id):", 𐐀, id)',
\ '}',
\ ] )
let l:expected = [0, 4, 8, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
func! Test_DefJump_gopls_MultipleCodeUnit_last() abort
if !go#util#has_job()
return
endif
try
let g:go_def_mode = 'gopls'
let l:tmp = gotest#write_file('multiplecodeunit/lastposition/lastposition.go', [
\ 'package lastposition',
\ '',
\ 'func Example() {',
\ "\t𐐀, id := " . '"foo", "bar"',
\ "\tprintln(" . '"(𐐀, id):", 𐐀, id)',
\ '}',
\ ] )
let l:expected = [0, 4, 8, 0]
call assert_notequal(l:expected, getpos('.'))
call go#def#Jump('', 0)
let l:start = reltime()
while getpos('.') != l:expected && reltimefloat(reltime(l:start)) < 10
sleep 100m
endwhile
call assert_equal(l:expected, getpos('.'))
finally
call delete(l:tmp, 'rf')
unlet g:go_def_mode
endtry
endfunc
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -6,36 +6,25 @@
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
scriptencoding utf-8
let s:buf_nr = -1 let s:buf_nr = -1
function! go#doc#OpenBrowser(...) abort function! go#doc#OpenBrowser(...) abort
" check if we have gogetdoc as it gives us more and accurate information. if len(a:000) == 0
" Only supported if we have json_decode as it's not worth to parse the plain let [l:out, l:err] = go#lsp#DocLink()
" non-json output of gogetdoc
let bin_path = go#path#CheckBinPath('gogetdoc')
if !empty(bin_path) && exists('*json_decode')
let [l:json_out, l:err] = s:gogetdoc(1)
if l:err if l:err
call go#util#EchoError(json_out) call go#util#EchoError(l:out)
return return
endif endif
let out = json_decode(json_out) if len(l:out) == 0
if type(out) != type({}) call go#util#EchoWarning("could not path for doc URL")
call go#util#EchoError("gogetdoc output is malformed")
endif endif
let import = out["import"] let l:godoc_url = printf('%s/%s', go#config#DocUrl(), l:out)
let name = out["name"]
let decl = out["decl"]
let godoc_url = go#config#DocUrl() call go#util#OpenBrowser(l:godoc_url)
let godoc_url .= "/" . import
if decl !~ "^package"
let godoc_url .= "#" . name
endif
call go#util#OpenBrowser(godoc_url)
return return
endif endif
@ -48,7 +37,7 @@ function! go#doc#OpenBrowser(...) abort
let exported_name = pkgs[1] let exported_name = pkgs[1]
" example url: https://godoc.org/github.com/fatih/set#Set " example url: https://godoc.org/github.com/fatih/set#Set
let godoc_url = go#config#DocUrl() . "/" . pkg . "#" . exported_name let godoc_url = printf('%s/%s#%s', go#config#DocUrl(), pkg, exported_name)
call go#util#OpenBrowser(godoc_url) call go#util#OpenBrowser(godoc_url)
endfunction endfunction
@ -56,11 +45,8 @@ function! go#doc#Open(newmode, mode, ...) abort
" With argument: run "godoc [arg]". " With argument: run "godoc [arg]".
if len(a:000) if len(a:000)
let [l:out, l:err] = go#util#Exec(['go', 'doc'] + a:000) let [l:out, l:err] = go#util#Exec(['go', 'doc'] + a:000)
else " Without argument: run gogetdoc on cursor position. else " Without argument: use gopls to get documentation
let [l:out, l:err] = s:gogetdoc(0) let [l:out, l:err] = go#lsp#Doc()
if out == -1
return
endif
endif endif
if l:err if l:err
@ -68,10 +54,62 @@ function! go#doc#Open(newmode, mode, ...) abort
return return
endif endif
call s:GodocView(a:newmode, a:mode, out) call s:GodocView(a:newmode, a:mode, l:out)
endfunction endfunction
function! s:GodocView(newposition, position, content) abort function! s:GodocView(newposition, position, content) abort
" popup window
if go#config#DocPopupWindow()
if exists('*popup_atcursor') && exists('*popup_clear')
call popup_clear()
let borderchars = ['-', '|', '-', '|', '+', '+', '+', '+']
if &encoding == "utf-8"
let borderchars = ['─', '│', '─', '│', '┌', '┐', '┘', '└']
endif
call popup_atcursor(split(a:content, '\n'), {
\ 'padding': [1, 1, 1, 1],
\ 'borderchars': borderchars,
\ 'border': [1, 1, 1, 1],
\ })
elseif has('nvim') && exists('*nvim_open_win')
let lines = split(a:content, '\n')
let height = 0
let width = 0
for line in lines
let lw = strdisplaywidth(line)
if lw > width
let width = lw
endif
let height += 1
endfor
let width += 1 " right margin
let max_height = go#config#DocMaxHeight()
if height > max_height
let height = max_height
endif
let buf = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buf, 0, -1, v:true, lines)
let opts = {
\ 'relative': 'cursor',
\ 'row': 1,
\ 'col': 0,
\ 'width': width,
\ 'height': height,
\ 'style': 'minimal',
\ }
call nvim_open_win(buf, v:true, opts)
setlocal nomodified nomodifiable filetype=godoc
" close easily with CR, Esc and q
noremap <buffer> <silent> <CR> :<C-U>close<CR>
noremap <buffer> <silent> <Esc> :<C-U>close<CR>
noremap <buffer> <silent> q :<C-U>close<CR>
endif
return
endif
" reuse existing buffer window if it exists otherwise create a new one " reuse existing buffer window if it exists otherwise create a new one
let is_visible = bufexists(s:buf_nr) && bufwinnr(s:buf_nr) != -1 let is_visible = bufexists(s:buf_nr) && bufwinnr(s:buf_nr) != -1
if !bufexists(s:buf_nr) if !bufexists(s:buf_nr)
@ -129,23 +167,6 @@ function! s:GodocView(newposition, position, content) abort
nnoremap <buffer> <silent> <Esc>[ <Esc>[ nnoremap <buffer> <silent> <Esc>[ <Esc>[
endfunction endfunction
function! s:gogetdoc(json) abort
let l:cmd = [
\ 'gogetdoc',
\ '-tags', go#config#BuildTags(),
\ '-pos', expand("%:p:gs!\\!/!") . ':#' . go#util#OffsetCursor()]
if a:json
let l:cmd += ['-json']
endif
if &modified
let l:cmd += ['-modified']
return go#util#Exec(l:cmd, go#util#archive())
endif
return go#util#Exec(l:cmd)
endfunction
" returns the package and exported name. exported name might be empty. " returns the package and exported name. exported name might be empty.
" ie: fmt and Println " ie: fmt and Println
" ie: github.com/fatih/set and New " ie: github.com/fatih/set and New

View file

@ -62,7 +62,7 @@ func! Test_fillstruct_two_line() abort
\ '\tAddress: "",', \ '\tAddress: "",',
\ '}) }']) \ '}) }'])
finally finally
"call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
endfunc endfunc

View file

@ -18,6 +18,30 @@ set cpo&vim
" this and have VimL experience, please look at the function for " this and have VimL experience, please look at the function for
" improvements, patches are welcome :) " improvements, patches are welcome :)
function! go#fmt#Format(withGoimport) abort function! go#fmt#Format(withGoimport) abort
let l:bin_name = go#config#FmtCommand()
if a:withGoimport == 1
let l:mode = go#config#ImportsMode()
if l:mode == 'gopls'
if !go#config#GoplsEnabled()
call go#util#EchoError("go_imports_mode is 'gopls', but gopls is disabled")
return
endif
call go#lsp#Imports()
return
endif
let l:bin_name = 'goimports'
endif
if l:bin_name == 'gopls'
if !go#config#GoplsEnabled()
call go#util#EchoError("go_def_mode is 'gopls', but gopls is disabled")
return
endif
call go#lsp#Format()
return
endif
if go#config#FmtExperimental() if go#config#FmtExperimental()
" Using winsaveview to save/restore cursor state has the problem of " Using winsaveview to save/restore cursor state has the problem of
" closing folds on save: " closing folds on save:
@ -52,20 +76,16 @@ function! go#fmt#Format(withGoimport) abort
let l:tmpname = tr(l:tmpname, '\', '/') let l:tmpname = tr(l:tmpname, '\', '/')
endif endif
let bin_name = go#config#FmtCommand()
if a:withGoimport == 1
let bin_name = "goimports"
endif
let current_col = col('.') let current_col = col('.')
let [l:out, l:err] = go#fmt#run(bin_name, l:tmpname, expand('%')) let [l:out, l:err] = go#fmt#run(l:bin_name, l:tmpname, expand('%'))
let diff_offset = len(readfile(l:tmpname)) - line('$') let line_offset = len(readfile(l:tmpname)) - line('$')
let l:orig_line = getline('.')
if l:err == 0 if l:err == 0
call go#fmt#update_file(l:tmpname, expand('%')) call go#fmt#update_file(l:tmpname, expand('%'))
elseif !go#config#FmtFailSilently() elseif !go#config#FmtFailSilently()
let errors = s:parse_errors(expand('%'), out) let l:errors = s:replace_filename(expand('%'), out)
call s:show_errors(errors) call go#fmt#ShowErrors(l:errors)
endif endif
" We didn't use the temp file, so clean up " We didn't use the temp file, so clean up
@ -87,8 +107,10 @@ function! go#fmt#Format(withGoimport) abort
call winrestview(l:curw) call winrestview(l:curw)
endif endif
" be smart and jump to the line the new statement was added/removed " be smart and jump to the line the new statement was added/removed and
call cursor(line('.') + diff_offset, current_col) " adjust the column within the line
let l:lineno = line('.') + line_offset
call cursor(l:lineno, current_col + (len(getline(l:lineno)) - len(l:orig_line)))
" Syntax highlighting breaks less often. " Syntax highlighting breaks less often.
syntax sync fromstart syntax sync fromstart
@ -115,28 +137,12 @@ function! go#fmt#update_file(source, target)
" reload buffer to reflect latest changes " reload buffer to reflect latest changes
silent edit! silent edit!
call go#lsp#DidChange(expand(a:target, ':p'))
let &fileformat = old_fileformat let &fileformat = old_fileformat
let &syntax = &syntax let &syntax = &syntax
let l:listtype = go#list#Type("GoFmt") call go#fmt#CleanErrors()
" 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 endfunction
" run runs the gofmt/goimport command for the given source file and returns " run runs the gofmt/goimport command for the given source file and returns
@ -169,39 +175,42 @@ function! s:fmt_cmd(bin_name, source, target)
return cmd return cmd
endfunction endfunction
" parse_errors parses the given errors and returns a list of parsed errors " replace_filename replaces the filename on each line of content with
function! s:parse_errors(filename, content) abort " a:filename.
let splitted = split(a:content, '\n') function! s:replace_filename(filename, content) abort
let l:errors = split(a:content, '\n')
" list of errors to be put into location list let l:errors = map(l:errors, printf('substitute(v:val, ''^.\{-}:'', ''%s:'', '''')', a:filename))
let errors = [] return join(l:errors, "\n")
for line in splitted
let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\)\s*\(.*\)')
if !empty(tokens)
call add(errors,{
\"filename": a:filename,
\"lnum": tokens[2],
\"col": tokens[3],
\"text": tokens[4],
\ })
endif
endfor
return errors
endfunction endfunction
" show_errors opens a location list and shows the given errors. If the given function! go#fmt#CleanErrors() abort
" errors is empty, it closes the the location list
function! s:show_errors(errors) abort
let l:listtype = go#list#Type("GoFmt") let l:listtype = go#list#Type("GoFmt")
if !empty(a:errors)
call go#list#Populate(l:listtype, a:errors, 'Format') " clean up previous list
echohl Error | echomsg "Gofmt returned error" | echohl None if l:listtype == "quickfix"
let l:list_title = getqflist({'title': 1})
else
let l:list_title = getloclist(0, {'title': 1})
endif endif
if has_key(l:list_title, 'title') && (l:list_title['title'] == 'Format' || l:list_title['title'] == 'GoMetaLinterAutoSave')
call go#list#Clean(l:listtype)
endif
endfunction
" show_errors opens a location list and shows the given errors. If errors is
" empty, it closes the the location list.
function! go#fmt#ShowErrors(errors) abort
let l:errorformat = '%f:%l:%c:\ %m'
let l:listtype = go#list#Type("GoFmt")
call go#list#ParseFormat(l:listtype, l:errorformat, a:errors, 'Format', 0)
let l:errors = go#list#Get(l:listtype)
" this closes the window if there are no errors or it opens " this closes the window if there are no errors or it opens
" it if there is any " it if there are any.
call go#list#Window(l:listtype, len(a:errors)) call go#list#Window(l:listtype, len(l:errors))
endfunction endfunction
function! go#fmt#ToggleFmtAutoSave() abort function! go#fmt#ToggleFmtAutoSave() abort

View file

@ -35,7 +35,7 @@ func! Test_update_file() abort
endfunc endfunc
func! Test_goimports() abort func! Test_goimports() abort
let $GOPATH = 'test-fixtures/fmt/' let $GOPATH = printf('%s/%s', fnamemodify(getcwd(), ':p'), 'test-fixtures/fmt')
let actual_file = tempname() let actual_file = tempname()
call writefile(readfile("test-fixtures/fmt/src/imports/goimports.go"), actual_file) call writefile(readfile("test-fixtures/fmt/src/imports/goimports.go"), actual_file)

View file

@ -232,11 +232,10 @@ function! go#guru#Describe(selected) abort
endfunction endfunction
function! go#guru#DescribeInfo(showstatus) 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 " check if the version of Vim being tested supports json_decode()
" nvim: https://github.com/neovim/neovim/pull/4131
if !exists("*json_decode") if !exists("*json_decode")
call go#util#EchoError("requires 'json_decode'. Update your Vim/Neovim version.") call go#util#EchoError("GoDescribeInfo requires 'json_decode'. Update your Vim/Neovim version.")
return return
endif endif
@ -406,43 +405,31 @@ endfunction
" Show all refs to entity denoted by selected identifier " Show all refs to entity denoted by selected identifier
function! go#guru#Referrers(selected) abort function! go#guru#Referrers(selected) abort
let args = { let args = {
\ 'mode': 'referrers', \ 'mode': 'referrers',
\ 'format': 'plain', \ 'format': 'plain',
\ 'selected': a:selected, \ 'selected': a:selected,
\ 'needs_scope': 0, \ 'needs_scope': 0,
\ } \ }
call s:run_guru(args) call s:run_guru(args)
endfunction endfunction
function! go#guru#SameIds(showstatus) abort function! go#guru#SameIds(showstatus) abort
" we use matchaddpos() which was introduce with 7.4.330, be sure we have " check if the version of Vim being tested supports matchaddpos()
" it: http://ftp.vim.org/vim/patches/7.4/7.4.330
if !exists("*matchaddpos") if !exists("*matchaddpos")
call go#util#EchoError("GoSameIds requires 'matchaddpos'. Update your Vim/Neovim version.") call go#util#EchoError("GoSameIds requires 'matchaddpos'. Update your Vim/Neovim version.")
return return
endif endif
" json_encode() and friends are introduced with this patch (7.4.1304) " check if the version of Vim being tested supports json_decode()
" vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
" nvim: https://github.com/neovim/neovim/pull/4131
if !exists("*json_decode") if !exists("*json_decode")
call go#util#EchoError("GoSameIds requires 'json_decode'. Update your Vim/Neovim version.") call go#util#EchoError("GoSameIds requires 'json_decode'. Update your Vim/Neovim version.")
return return
endif endif
let args = { let [l:line, l:col] = getpos('.')[1:2]
\ 'mode': 'what', let [l:line, l:col] = go#lsp#lsp#Position(l:line, l:col)
\ 'format': 'json', call go#lsp#SameIDs(0, expand('%:p'), l:line, l:col, funcref('s:same_ids_highlight'))
\ 'selected': -1,
\ '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 endfunction
function! s:same_ids_highlight(exit_val, output, mode) abort function! s:same_ids_highlight(exit_val, output, mode) abort
@ -482,18 +469,26 @@ function! s:same_ids_highlight(exit_val, output, mode) abort
endif endif
let same_ids = result['sameids'] let same_ids = result['sameids']
" highlight the lines " highlight the lines
let l:matches = []
for item in same_ids for item in same_ids
let pos = split(item, ':') let pos = split(item, ':')
call matchaddpos('goSameId', [[str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]]) let l:matches = add(l:matches, [str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)])
endfor endfor
call go#util#HighlightPositions('goSameId', l:matches)
if go#config#AutoSameids() if go#config#AutoSameids()
" re-apply SameIds at the current cursor position at the time the buffer " re-apply SameIds at the current cursor position at the time the buffer
" is redisplayed: e.g. :edit, :GoRename, etc. " is redisplayed: e.g. :edit, :GoRename, etc.
augroup vim-go-sameids augroup vim-go-sameids
autocmd! * <buffer> autocmd! * <buffer>
autocmd BufWinEnter <buffer> nested call go#guru#SameIds(0) if has('textprop')
autocmd BufReadPost <buffer> nested call go#guru#SameIds(0)
else
autocmd BufWinEnter <buffer> nested call go#guru#SameIds(0)
endif
augroup end augroup end
endif endif
endfunction endfunction
@ -501,15 +496,7 @@ endfunction
" ClearSameIds returns 0 when it removes goSameId groups and non-zero if no " ClearSameIds returns 0 when it removes goSameId groups and non-zero if no
" goSameId groups are found. " goSameId groups are found.
function! go#guru#ClearSameIds() abort function! go#guru#ClearSameIds() abort
let l:cleared = 0 let l:cleared = go#util#ClearHighlights('goSameId')
let m = getmatches()
for item in m
if item['group'] == 'goSameId'
call matchdelete(item['id'])
let l:cleared = 1
endif
endfor
if !l:cleared if !l:cleared
return 1 return 1
@ -534,11 +521,11 @@ function! go#guru#AutoToggleSameIds() abort
call go#util#EchoProgress("sameids auto highlighting disabled") call go#util#EchoProgress("sameids auto highlighting disabled")
call go#guru#ClearSameIds() call go#guru#ClearSameIds()
call go#config#SetAutoSameids(0) call go#config#SetAutoSameids(0)
return else
call go#util#EchoSuccess("sameids auto highlighting enabled")
call go#config#SetAutoSameids(1)
endif endif
call go#auto#update_autocmd()
call go#util#EchoSuccess("sameids auto highlighting enabled")
call go#config#SetAutoSameids(1)
endfunction endfunction
@ -565,11 +552,11 @@ function! s:parse_guru_output(exit_val, output, title) abort
let errformat = "%f:%l.%c-%[%^:]%#:\ %m,%f:%l:%c:\ %m" let errformat = "%f:%l.%c-%[%^:]%#:\ %m,%f:%l:%c:\ %m"
let l:listtype = go#list#Type("_guru") let l:listtype = go#list#Type("_guru")
call go#list#ParseFormat(l:listtype, errformat, a:output, a:title) call go#list#ParseFormat(l:listtype, errformat, a:output, a:title, 0)
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
endfun endfunction
function! go#guru#Scope(...) abort function! go#guru#Scope(...) abort
if a:0 if a:0
@ -602,11 +589,9 @@ function! go#guru#DescribeBalloon() abort
return return
endif endif
" json_encode() and friends are introduced with this patch (7.4.1304) " check if the version of Vim being tested supports json_decode()
" vim: https://groups.google.com/d/msg/vim_dev/vLupTNhQhZ8/cDGIk0JEDgAJ
" nvim: https://github.com/neovim/neovim/pull/4131
if !exists("*json_decode") if !exists("*json_decode")
call go#util#EchoError("requires 'json_decode'. Update your Vim/Neovim version.") call go#util#EchoError("GoDescribeBalloon requires 'json_decode'. Update your Vim/Neovim version.")
return return
endif endif

View file

@ -98,6 +98,325 @@ function! Test_gomodVersion_incompatible_highlight() abort
endtry endtry
endfunc endfunc
function! Test_numeric_literal_highlight() abort
syntax on
let tests = {
\ 'lone zero': {'group': 'goDecimalInt', 'value': '0'},
\ 'integer': {'group': 'goDecimalInt', 'value': '1234567890'},
\ 'integerGrouped': {'group': 'goDecimalInt', 'value': '1_234_567_890'},
\ 'integerErrorLeadingUnderscore': {'group': 'goDecimalError', 'value': '_1234_567_890'},
\ 'integerErrorTrailingUnderscore': {'group': 'goDecimalError', 'value': '1_234_567890_'},
\ 'integerErrorDoubleUnderscore': {'group': 'goDecimalError', 'value': '1_234__567_890'},
\ 'hexadecimal': {'group': 'goHexadecimalInt', 'value': '0x0123456789abdef'},
\ 'hexadecimalGrouped': {'group': 'goHexadecimalInt', 'value': '0x012_345_678_9ab_def'},
\ 'hexadecimalErrorLeading': {'group': 'goHexadecimalError', 'value': '0xg0123456789abdef'},
\ 'hexadecimalErrorTrailing': {'group': 'goHexadecimalError', 'value': '0x0123456789abdefg'},
\ 'hexadecimalErrorDoubleUnderscore': {'group': 'goHexadecimalError', 'value': '0x__0123456789abdef'},
\ 'hexadecimalErrorTrailingUnderscore': {'group': 'goHexadecimalError', 'value': '0x0123456789abdef_'},
\ 'heXadecimal': {'group': 'goHexadecimalInt', 'value': '0X0123456789abdef'},
\ 'heXadecimalErrorLeading': {'group': 'goHexadecimalError', 'value': '0Xg0123456789abdef'},
\ 'heXadecimalErrorTrailing': {'group': 'goHexadecimalError', 'value': '0X0123456789abdefg'},
\ 'octal': {'group': 'goOctalInt', 'value': '01234567'},
\ 'octalPrefix': {'group': 'goOctalInt', 'value': '0o1234567'},
\ 'octalGrouped': {'group': 'goOctalInt', 'value': '0o1_234_567'},
\ 'octalErrorLeading': {'group': 'goOctalError', 'value': '081234567'},
\ 'octalErrorTrailing': {'group': 'goOctalError', 'value': '012345678'},
\ 'octalErrorDoubleUnderscore': {'group': 'goOctalError', 'value': '0o__1234567'},
\ 'octalErrorTrailingUnderscore': {'group': 'goOctalError', 'value': '0o_123456_7_'},
\ 'octalErrorTrailingO': {'group': 'goOctalError', 'value': '0o_123456_7o'},
\ 'octalErrorTrailingX': {'group': 'goOctalError', 'value': '0o_123456_7x'},
\ 'octalErrorTrailingB': {'group': 'goOctalError', 'value': '0o_123456_7b'},
\ 'OctalPrefix': {'group': 'goOctalInt', 'value': '0O1234567'},
\ 'binaryInt': {'group': 'goBinaryInt', 'value': '0b0101'},
\ 'binaryIntGrouped': {'group': 'goBinaryInt', 'value': '0b_01_01'},
\ 'binaryErrorLeading': {'group': 'goBinaryError', 'value': '0b20101'},
\ 'binaryErrorTrailing': {'group': 'goBinaryError', 'value': '0b01012'},
\ 'binaryErrorDoubleUnderscore': {'group': 'goBinaryError', 'value': '0b_01__01'},
\ 'binaryOverrideOctal': {'group': 'goBinaryError', 'value': '0b1234567'},
\ 'binaryErrorTrailingUnderscore': {'group': 'goBinaryError', 'value': '0b_01_01_'},
\ 'BinaryInt': {'group': 'goBinaryInt', 'value': '0B0101'},
\ 'BinaryErrorLeading': {'group': 'goBinaryError', 'value': '0B20101'},
\ 'BinaryErrorTrailing': {'group': 'goBinaryError', 'value': '0B01012'},
\ }
for kv in items(tests)
let l:actual = s:numericHighlightGroupInAssignment(kv[0], kv[1].value)
call assert_equal(kv[1].group, l:actual, kv[0])
endfor
endfunction
function! Test_zero_as_index_element() abort
syntax on
let l:actual = s:numericHighlightGroupInSliceElement('zero-element', '0')
call assert_equal('goDecimalInt', l:actual)
let l:actual = s:numericHighlightGroupInMultidimensionalSliceElement('zero-element', '0')
call assert_equal('goDecimalInt', l:actual, 'multi-dimensional')
endfunction
function! Test_zero_as_slice_index() abort
syntax on
let l:actual = s:numericHighlightGroupInSliceIndex('zero-index', '0')
call assert_equal('goDecimalInt', l:actual)
let l:actual = s:numericHighlightGroupInMultidimensionalSliceIndex('zero-index', '0', '0')
call assert_equal('goDecimalInt', l:actual, 'multi-dimensional')
endfunction
function! Test_zero_as_start_slicing_slice() abort
syntax on
let l:actual = s:numericHighlightGroupInSliceSlicing('slice-slicing', '0', '1')
call assert_equal('goDecimalInt', l:actual)
endfunction
function! s:numericHighlightGroupInAssignment(testname, value)
let l:dir = gotest#write_file(printf('numeric/%s.go', a:testname), [
\ 'package numeric',
\ '',
\ printf("var v = %s\x1f", a:value),
\ ])
try
let l:pos = getcurpos()
let l:actual = synIDattr(synID(l:pos[1], l:pos[2], 1), 'name')
return l:actual
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:numericHighlightGroupInSliceElement(testname, value)
let l:dir = gotest#write_file(printf('numeric/slice-element/%s.go', a:testname), [
\ 'package numeric',
\ '',
\ printf("v := []int{\x1f%s}", a:value),
\ ])
try
let l:pos = getcurpos()
let l:actual = synIDattr(synID(l:pos[1], l:pos[2], 1), 'name')
return l:actual
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:numericHighlightGroupInMultidimensionalSliceElement(testname, value)
let l:dir = gotest#write_file(printf('numeric/slice-multidimensional-element/%s.go', a:testname), [
\ 'package numeric',
\ '',
\ printf("v := [][]int{{\x1f%s},{%s}}", a:value, a:value),
\ ])
try
let l:pos = getcurpos()
let l:actual = synIDattr(synID(l:pos[1], l:pos[2], 1), 'name')
return l:actual
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:numericHighlightGroupInSliceIndex(testname, value)
let l:dir = gotest#write_file(printf('numeric/slice-index/%s.go', a:testname), [
\ 'package numeric',
\ '',
\ 'var sl []int',
\ printf("println(sl[\x1f%s])", a:value),
\ ])
try
let l:pos = getcurpos()
let l:actual = synIDattr(synID(l:pos[1], l:pos[2], 1), 'name')
return l:actual
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:numericHighlightGroupInMultidimensionalSliceIndex(testname, first, second)
let l:dir = gotest#write_file(printf('numeric/slice-multidimensional-index/%s.go', a:testname), [
\ 'package numeric',
\ '',
\ 'var sl [][]int',
\ printf("println(sl[\x1f%s][%s])", a:first, a:second),
\ ])
try
let l:pos = getcurpos()
let l:actual = synIDattr(synID(l:pos[1], l:pos[2], 1), 'name')
return l:actual
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:numericHighlightGroupInSliceSlicing(testname, from, to)
let l:dir = gotest#write_file(printf('numeric/slice-slicing/%s.go', a:testname), [
\ 'package numeric',
\ '',
\ 'var sl = []int{1,2}',
\ printf("println(sl[\x1f%s:%s])", a:from, a:to),
\ ])
try
let l:pos = getcurpos()
let l:actual = synIDattr(synID(l:pos[1], l:pos[2], 1), 'name')
return l:actual
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! Test_diagnostic_after_fmt() abort
let g:go_fmt_command = 'gofmt'
try
call s:diagnostic_after_write( [
\ 'package main',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ '',
\ "\tfmt.Println(\x1fhello)",
\ '}',
\ ], [])
finally
unlet g:go_fmt_command
endtry
endfunction
function! Test_diagnostic_after_fmt_change() abort
" craft a file that will be changed when its written (gofmt will change it).
let g:go_fmt_command = 'gofmt'
try
call s:diagnostic_after_write( [
\ 'package main',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ '',
\ "fmt.Println(\x1fhello)",
\ '}',
\ ], [])
finally
unlet g:go_fmt_command
endtry
endfunction
function! Test_diagnostic_after_fmt_cleared() abort
" craft a file that will be fixed when it is written.
let g:go_fmt_command = 'gofmt'
try
call s:diagnostic_after_write( [
\ 'package main',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ '',
\ "fmt.Println(\x1fhello)",
\ '}',
\ ], ['hello := "hello, vim-go"'])
finally
unlet g:go_fmt_command
endtry
endfunction
function! Test_diagnostic_after_reload() abort
let l:dir = gotest#write_file('diagnostic/after-reload.go', [
\ 'package main',
\ 'import "fmt"',
\ '',
\ 'func main() {',
\ '',
\ "\tfmt.Println(\x1fhello)",
\ '}',
\ ])
try
call s:check_diagnostics('', 'goDiagnosticError', 'initial')
let l:pos = getcurpos()
edit
call setpos('.', l:pos)
call s:check_diagnostics('', 'goDiagnosticError', 'after-reload')
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:diagnostic_after_write(contents, changes) abort
syntax on
let l:dir = gotest#write_file('diagnostic/after-write.go', a:contents)
try
let l:pos = getcurpos()
call s:check_diagnostics('', 'goDiagnosticError', 'initial')
" write a:changes to the previous line and make sure l:actual and
" l:expected are set so that they won't accidentally match on the next
" check.
if len(a:changes) > 0
call append(l:pos[1]-1, a:changes)
let l:actual = 'goDiagnosticError'
let l:expected = ''
else
let l:actual = ''
let l:expected = 'goDiagnosticError'
endif
write
call s:check_diagnostics(l:actual, l:expected, 'after-write')
finally
call delete(l:dir, 'rf')
endtry
endfunction
function! s:check_diagnostics(actual, expected, when)
let l:actual = a:actual
let l:start = reltime()
while l:actual != a:expected && reltimefloat(reltime(l:start)) < 10
" Get the cursor position on each iteration, because the cursor postion
" may change between iterations when go#fmt#GoFmt formats, reloads the
" file, and moves the cursor to try to keep it where the user expects it
" to be when gofmt modifies the files.
let l:pos = getcurpos()
if !has('textprop')
let l:matches = getmatches()
if len(l:matches) == 0
let l:actual = ''
endif
for l:m in l:matches
let l:matchline = l:m.pos1[0]
if len(l:m.pos1) < 2
continue
endif
let l:matchcol = get(l:m.pos1, 1, 1)
if l:pos[1] == l:matchline && l:pos[2] >= l:matchcol && l:pos[2] <= l:matchcol + l:m.pos1[2]
" Ideally, we'd check that the cursor is within the match, but when a
" tab is added on the current line, the cursor position within the
" line will stay constant while the line itself is shifted over by a
" column, so just check the line itself instead of checking a precise
" cursor location.
" if l:pos[1] == l:matchline
let l:actual = l:m.group
break
endif
endfor
sleep 100m
continue
endif
let l:actual = get(prop_list(l:pos[1]), 0, {'type': ''}).type
sleep 100m
endwhile
call assert_equal(a:expected, l:actual, a:when)
endfunction
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -12,7 +12,7 @@ func! Test_impl() abort
call go#impl#Impl('r', 'reader', 'io.Reader') call go#impl#Impl('r', 'reader', 'io.Reader')
call gotest#assert_buffer(1, [ call gotest#assert_buffer(1, [
\ 'func (r reader) Read(p []byte) (n int, err error) {', \ 'func (r reader) Read(p []byte) (n int, err error) {',
\ ' panic("not implemented")', \ ' panic("not implemented") // TODO: Implement',
\ '}']) \ '}'])
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
@ -33,7 +33,7 @@ func! Test_impl_get() abort
\ 'type reader struct {}', \ 'type reader struct {}',
\ '', \ '',
\ 'func (r *reader) Read(p []byte) (n int, err error) {', \ 'func (r *reader) Read(p []byte) (n int, err error) {',
\ ' panic("not implemented")', \ ' panic("not implemented") // TODO: Implement',
\ '}']) \ '}'])
finally finally
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')

View file

@ -0,0 +1,44 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#implements#Implements(selected) abort
let l:mode = go#config#ImplementsMode()
if l:mode == 'guru'
call go#guru#Implements(a:selected)
return
elseif l:mode == 'gopls'
if !go#config#GoplsEnabled()
call go#util#EchoError("go_implements_mode is 'gopls', but gopls is disabled")
endif
let [l:line, l:col] = getpos('.')[1:2]
let [l:line, l:col] = go#lsp#lsp#Position(l:line, l:col)
let l:fname = expand('%:p')
call go#lsp#Implements(l:fname, l:line, l:col, funcref('s:parse_output'))
return
else
call go#util#EchoWarning('unknown value for g:go_implements_mode')
endif
endfunction
" This uses Vim's errorformat to parse the output and put it into a quickfix
" or locationlist.
function! s:parse_output(exit_val, output, title) abort
if a:exit_val
call go#util#EchoError(a:output)
return
endif
let errformat = ",%f:%l:%c:\ %m"
let l:listtype = go#list#Type("GoImplements")
call go#list#ParseFormat(l:listtype, errformat, a:output, a:title, 0)
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -82,7 +82,7 @@ function! go#import#SwitchImport(enabled, localname, path, bang) abort
while line <= line("$") while line <= line("$")
let line = line + 1 let line = line + 1
let linestr = getline(line) let linestr = getline(line)
let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\S*\s*\)"\(.\+\)"\)') let m = matchlist(getline(line), '^\()\|\(\s\+\)\(\w\+\s\+\)\="\(.\+\)"\)')
if empty(m) if empty(m)
if siteprefix == "" && a:enabled if siteprefix == "" && a:enabled
" must be in the first group " must be in the first group

View file

@ -0,0 +1,35 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_SwitchImportAddIgnoresCommented()
try
let l:tmp = gotest#write_file('import/import.go', [
\ 'package import',
\ '',
\ 'import (',
\ "\t" . '// "fmt"',
\ "\t" . '"io"',
\ "\t" . '"ioutil"',
\ "\t" . '"os"',
\ ')',
\ '',
\ 'func main() {',
\ ' io.Copy(ioutil.Discard, os.Stdin)',
\ ' fmt.Println("import the package")',
\ '}',
\ ])
call go#import#SwitchImport(1, '', 'fmt', 0)
let l:actual = getline(4)
call assert_equal("\t" . '"fmt"', l:actual)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -18,20 +18,34 @@ function! s:issuebody() abort
for l in lines for l in lines
let body = add(body, l) let body = add(body, l)
if l =~ '^\* Vim version' if l =~ '^<!-- :version'
redir => out let out = execute('version')
silent version
redir END
let body = extend(body, split(out, "\n")[0:2]) let body = extend(body, split(out, "\n")[0:2])
elseif l =~ '^\* Go version' elseif l =~ '^<!-- go version -->'
let [out, err] = go#util#Exec(['go', 'version']) let [out, err] = go#util#Exec(['go', 'version'])
let body = add(body, substitute(l:out, rtrimpat, '', '')) let body = add(body, substitute(l:out, rtrimpat, '', ''))
elseif l =~ '^\* Go environment' elseif l =~ '^<!-- go env -->'
let [out, err] = go#util#Exec(['go', 'env']) let [out, err] = go#util#ExecInDir(['go', 'env'])
let body = add(body, substitute(l:out, rtrimpat, '', ''))
elseif l=~ '^<!-- gopls version -->'
let [out, err] = go#util#Exec(['gopls', 'version'])
let body = add(body, substitute(l:out, rtrimpat, '', '')) let body = add(body, substitute(l:out, rtrimpat, '', ''))
endif endif
endfor endfor
let body = add(body, "\n#### vim-go configuration:\n<details><summary>vim-go configuration</summary><br><pre>")
for k in keys(g:)
if k =~ '^go_'
let body = add(body, 'g:' . k . ' = ' . string(get(g:, k)))
endif
endfor
let body = add(body, '</pre></details>')
let body = add(body, printf("\n#### filetype detection configuration:\n<details><summary>filetype detection</summary><br><pre>%s", execute('filetype')))
let body = add(body, '</pre></details>')
return join(body, "\n") return join(body, "\n")
endfunction endfunction

View file

@ -41,7 +41,12 @@ endfunction
" the current window will be the window that was hosting the buffer when " 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 " the job was started. After it returns, the current window will be
" restored to what it was before the function was called. " restored to what it was before the function was called.
" 'preserveerrors':
" A function that will be passed one value, the list type. It should
" return a boolean value that indicates whether any errors encountered
" should be consider additive to the existing set of errors. This is
" mostly useful for a set of commands that are run via autocmds.
"
" The return value is a dictionary with these keys: " The return value is a dictionary with these keys:
" 'callback': " 'callback':
" A function suitable to be passed as a job callback handler. See " A function suitable to be passed as a job callback handler. See
@ -61,7 +66,7 @@ function! go#job#Options(args)
let state = { let state = {
\ 'winid': win_getid(winnr()), \ 'winid': win_getid(winnr()),
\ 'dir': getcwd(), \ 'dir': getcwd(),
\ 'jobdir': fnameescape(expand("%:p:h")), \ 'jobdir': expand("%:p:h"),
\ 'messages': [], \ 'messages': [],
\ 'bang': 0, \ 'bang': 0,
\ 'for': "_job", \ 'for': "_job",
@ -69,12 +74,10 @@ function! go#job#Options(args)
\ 'exit_status': 0, \ 'exit_status': 0,
\ 'closed': 0, \ 'closed': 0,
\ 'errorformat': &errorformat, \ 'errorformat': &errorformat,
\ 'statustype' : '' \ 'statustype' : '',
\ } \ }
if has("patch-8.0.0902") || has('nvim') let cbs.cwd = state.jobdir
let cbs.cwd = state.jobdir
endif
if has_key(a:args, 'bang') if has_key(a:args, 'bang')
let state.bang = a:args.bang let state.bang = a:args.bang
@ -92,6 +95,10 @@ function! go#job#Options(args)
let state.errorformat = a:args.errorformat let state.errorformat = a:args.errorformat
endif endif
if has_key(a:args, 'preserveerrors')
let state.preserveerrors = a:args.preserveerrors
endif
function state.complete(job, exit_status, data) function state.complete(job, exit_status, data)
if has_key(self, 'custom_complete') if has_key(self, 'custom_complete')
let l:winid = win_getid(winnr()) let l:winid = win_getid(winnr())
@ -177,16 +184,26 @@ function! go#job#Options(args)
call win_gotoid(self.winid) call win_gotoid(self.winid)
let l:listtype = go#list#Type(self.for) let l:listtype = go#list#Type(self.for)
let l:preserveerrors = 0
if has_key(self, 'preserveerrors')
let l:preserveerrors = self.preserveerrors(l:listtype)
endif
if a:exit_status == 0 if a:exit_status == 0
call go#list#Clean(l:listtype) if !l:preserveerrors
call win_gotoid(l:winid) call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
endif
return return
endif endif
let l:listtype = go#list#Type(self.for) let l:listtype = go#list#Type(self.for)
if len(a:data) == 0 if len(a:data) == 0
call go#list#Clean(l:listtype) if !l:preserveerrors
call win_gotoid(l:winid) call go#list#Clean(l:listtype)
call win_gotoid(l:winid)
endif
return return
endif endif
@ -195,8 +212,8 @@ function! go#job#Options(args)
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
try try
" parse the errors relative to self.jobdir " parse the errors relative to self.jobdir
execute l:cd self.jobdir execute l:cd fnameescape(self.jobdir)
call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for) call go#list#ParseFormat(l:listtype, self.errorformat, out, self.for, l:preserveerrors)
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
finally finally
execute l:cd fnameescape(self.dir) execute l:cd fnameescape(self.dir)
@ -295,11 +312,6 @@ function! go#job#Start(cmd, options)
let l:manualcd = 1 let l:manualcd = 1
let dir = getcwd() let dir = getcwd()
execute l:cd fnameescape(filedir) 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 endif
if has_key(l:options, '_start') if has_key(l:options, '_start')

View file

@ -0,0 +1,53 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_JobDirWithSpaces()
if !go#util#has_job()
return
endif
try
let l:filename = 'job/dir has spaces/main.go'
let l:tmp = gotest#load_fixture(l:filename)
exe 'cd ' . fnameescape(l:tmp . '/src/job/dir has spaces')
" set the compiler type so that the errorformat option will be set
" correctly.
compiler go
let expected = [{'lnum': 4, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'undefined: notafunc'}]
" clear the quickfix lists
call setqflist([], 'r')
" go build discards any results when it compiles multiple packages. So we
" pass the `errors` package just as a placeholder with the current folder
" (indicated with '.').
let l:cmd = ['go', 'build', '.', 'errors']
let l:complete = go#promise#New(function('s:complete'), 10000, '')
call go#job#Spawn(l:cmd, {
\ 'for': 'GoBuild',
\ 'complete': l:complete.wrapper,
\ 'statustype': 'build'
\})
let l:out = l:complete.await()
let actual = getqflist()
call gotest#assert_quickfix(actual, l:expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! s:complete(job, exit_code, messages)
return a:messages
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -3,30 +3,36 @@ let s:cpo_save = &cpo
set cpo&vim set cpo&vim
function! go#lint#Gometa(bang, 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 l:metalinter = go#config#MetalinterCommand() let l:metalinter = go#config#MetalinterCommand()
if l:metalinter == 'gometalinter' || l:metalinter == 'golangci-lint' if a:0 == 0
let cmd = s:metalintercmd(l:metalinter) let l:goargs = [expand('%:p:h')]
if l:metalinter == 'gopls'
let l:pkg = go#package#ImportPath()
if l:pkg == -1
call go#util#EchoError('could not determine package name')
return
endif
let l:goargs = [l:pkg]
endif
else
let l:goargs = a:000
endif
let cmd = []
if l:metalinter == 'golangci-lint'
let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled()
let cmd = s:metalintercmd(l:metalinter, len(linters) != 0)
if empty(cmd) if empty(cmd)
return return
endif endif
" linters " add linters to cmd
let linters = a:autosave ? go#config#MetalinterAutosaveEnabled() : go#config#MetalinterEnabled()
for linter in linters for linter in linters
let cmd += ["--enable=".linter] let cmd += ["--enable=".linter]
endfor endfor
elseif l:metalinter != 'gopls'
for linter in go#config#MetalinterDisabled()
let cmd += ["--disable=".linter]
endfor
else
" the user wants something else, let us use it. " the user wants something else, let us use it.
let cmd = split(go#config#MetalinterCommand(), " ") let cmd = split(go#config#MetalinterCommand(), " ")
endif endif
@ -36,15 +42,8 @@ function! go#lint#Gometa(bang, autosave, ...) abort
" will be cleared " will be cleared
redraw redraw
if l:metalinter == "gometalinter" if l:metalinter == "golangci-lint"
" Include only messages for the active buffer for autosave. let l:goargs[0] = expand('%:p:h')
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 endif
endif endif
@ -54,139 +53,288 @@ function! go#lint#Gometa(bang, autosave, ...) abort
let cmd += ["--deadline=" . deadline] let cmd += ["--deadline=" . deadline]
endif endif
let cmd += goargs let cmd += l:goargs
if l:metalinter == "gometalinter" let errformat = s:errorformat(l:metalinter)
" Gometalinter can output one of the two, so we look for both:
" <file>:<line>:<column>:<severity>: <message> (<linter>) if l:metalinter == 'gopls'
" <file>:<line>::<severity>: <message> (<linter>) if a:autosave
" This can be defined by the following errorformat: let l:messages = go#lsp#AnalyzeFile(expand('%:p'))
let errformat = "%f:%l:%c:%t%*[^:]:\ %m,%f:%l::%t%*[^:]:\ %m" else
let l:import_paths = l:goargs
let l:messages = call('go#lsp#Diagnostics', l:import_paths)
endif
let l:err = len(l:messages)
else else
" Golangci-lint can output the following: if go#util#has_job()
" <file>:<line>:<column>: <message> (<linter>) if a:autosave
" This can be defined by the following errorformat: let l:for = 'GoMetaLinterAutoSave'
let errformat = "%f:%l:%c:\ %m" else
endif let l:for = 'GoMetaLinter'
endif
if go#util#has_job() call s:lint_job(l:metalinter, {'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat, 'for': l:for}, a:bang, a:autosave)
call s:lint_job({'cmd': cmd, 'statustype': l:metalinter, 'errformat': errformat}, a:bang, a:autosave) return
return endif
endif
let [l:out, l:err] = go#util#Exec(cmd) let [l:out, l:err] = go#util#Exec(cmd)
let l:messages = split(out, "\n")
endif
if a:autosave if a:autosave
let l:listtype = go#list#Type("GoMetaLinterAutoSave") let l:listtype = go#list#Type('GoMetaLinterAutoSave')
let l:for = 'GoMetaLinterAutoSave'
else else
let l:listtype = go#list#Type("GoMetaLinter") let l:listtype = go#list#Type('GoMetaLinter')
let l:for = 'GoMetaLinterAuto'
endif endif
if l:err == 0 if l:err == 0
call go#list#Clean(l:listtype) if !s:preserveerrors(a:autosave, l:listtype)
echon "vim-go: " | echohl Function | echon "[metalinter] PASS" | echohl None call go#list#Clean(l:listtype)
endif
call go#util#EchoSuccess('[metalinter] PASS')
else else
let l:winid = win_getid(winnr()) let l:winid = win_getid(winnr())
" Parse and populate our location list " Parse and populate our location list
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'GoMetaLinter')
if a:autosave
call s:metalinterautosavecomplete(l:metalinter, fnamemodify(expand('%:p'), ":."), 0, 1, l:messages)
endif
call go#list#ParseFormat(l:listtype, errformat, l:messages, l:for, s:preserveerrors(a:autosave, l:listtype))
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if a:autosave || a:bang if a:autosave || a:bang
call win_gotoid(l:winid) call win_gotoid(l:winid)
else
call go#list#JumpToFirst(l:listtype)
endif
endif
endfunction
function! go#lint#Diagnostics(bang, ...) abort
if a:0 == 0
let l:pkg = go#package#ImportPath()
if l:pkg == -1
call go#util#EchoError('could not determine package name')
return return
endif endif
call go#list#JumpToFirst(l:listtype)
let l:import_paths = [l:pkg]
else
let l:import_paths = a:000
endif
let errformat = s:errorformat('gopls')
let l:messages = call('go#lsp#Diagnostics', l:import_paths)
let l:listtype = go#list#Type("GoDiagnostics")
if len(l:messages) == 0
call go#list#Clean(l:listtype)
call go#util#EchoSuccess('[diagnostics] PASS')
else
" Parse and populate the quickfix list
let l:winid = win_getid(winnr())
call go#list#ParseFormat(l:listtype, errformat, l:messages, 'GoDiagnostics', 0)
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
if a:bang
call win_gotoid(l:winid)
else
call go#list#JumpToFirst(l:listtype)
endif
endif endif
endfunction endfunction
" Golint calls 'golint' on the current directory. Any warnings are populated in " Golint calls 'golint' on the current directory. Any warnings are populated in
" the location list " the location list
function! go#lint#Golint(bang, ...) abort function! go#lint#Golint(bang, ...) abort
call go#cmd#autowrite()
let l:type = 'golint'
let l:status = {
\ 'desc': 'current status',
\ 'type': l:type,
\ 'state': "started",
\ }
if go#config#EchoCommandInfo()
call go#util#EchoProgress(printf('[%s] analyzing...', l:type))
endif
call go#statusline#Update(expand('%:p:h'), l:status)
if a:0 == 0 if a:0 == 0
let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), go#package#ImportPath()]) let [l:out, l:err] = go#util#Exec([go#config#GolintBin(), expand('%:p:h')])
else else
let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000) let [l:out, l:err] = go#util#Exec([go#config#GolintBin()] + a:000)
endif endif
if empty(l:out) let l:status.state = 'success'
call go#util#EchoSuccess('[lint] PASS') let l:state = 'PASS'
return
endif
let l:winid = win_getid(winnr())
let l:listtype = go#list#Type("GoLint") let l:listtype = go#list#Type("GoLint")
call go#list#Parse(l:listtype, l:out, "GoLint") if !empty(l:out)
let l:errors = go#list#Get(l:listtype) let l:status.state = 'failed'
call go#list#Window(l:listtype, len(l:errors)) let l:state = 'FAIL'
if a:bang let l:winid = win_getid(winnr())
call win_gotoid(l:winid) call go#list#Parse(l:listtype, l:out, "GoLint", 0)
return 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)
else
call go#list#JumpToFirst(l:listtype)
endif
if go#config#EchoCommandInfo()
call go#util#EchoError(printf('[%s] %s', l:type, l:state))
endif
else
call go#list#Clean(l:listtype)
if go#config#EchoCommandInfo()
call go#util#EchoSuccess(printf('[%s] %s', l:type, l:state))
endif
endif endif
call go#statusline#Update(expand('%:p:h'), l:status)
call go#list#JumpToFirst(l:listtype)
endfunction endfunction
" Vet calls 'go vet' on the current directory. Any warnings are populated in " Vet calls 'go vet' on the current buffer's directory. Any warnings are
" the location list " populated in the location list
function! go#lint#Vet(bang, ...) abort function! go#lint#Vet(bang, ...) abort
call go#cmd#autowrite() call go#cmd#autowrite()
if go#config#EchoCommandInfo() let l:cmd = ['go', 'vet']
call go#util#EchoProgress('calling vet...')
let buildtags = go#config#BuildTags()
if buildtags isnot ''
let l:cmd += ['-tags', buildtags]
endif endif
if a:0 == 0 if a:0 == 0
let [l:out, l:err] = go#util#Exec(['go', 'vet', go#package#ImportPath()]) let l:import_path = go#package#ImportPath()
if l:import_path == -1
call go#util#EchoError('could not determine package')
return
endif
let l:cmd = add(l:cmd, l:import_path)
else else
let [l:out, l:err] = go#util#Exec(['go', 'tool', 'vet'] + a:000) let l:cmd = extend(l:cmd, a:000)
endif endif
let l:type = 'go vet'
if go#config#EchoCommandInfo()
call go#util#EchoProgress(printf('[%s] analyzing...', l:type))
endif
let l:status = {
\ 'desc': 'current status',
\ 'type': l:type,
\ 'state': "started",
\ }
call go#statusline#Update(expand('%:p:h'), l:status)
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
let l:status.state = 'success'
let l:state = 'PASS'
let l:listtype = go#list#Type("GoVet") let l:listtype = go#list#Type("GoVet")
if l:err != 0 if l:err != 0
let l:status.state = 'failed'
let l:state = 'FAIL'
let l:winid = win_getid(winnr()) let l:winid = win_getid(winnr())
let errorformat = "%-Gexit status %\\d%\\+," . &errorformat let l:errorformat = "%-Gexit status %\\d%\\+," . &errorformat
call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet") let l:dir = go#util#Chdir(expand('%:p:h'))
let errors = go#list#Get(l:listtype) try
call go#list#Window(l:listtype, len(errors)) call go#list#ParseFormat(l:listtype, l:errorformat, out, "GoVet", 0)
if !empty(errors) && !a:bang finally
call go#util#Chdir(l:dir)
endtry
let l:errors = go#list#Get(l:listtype)
if empty(l:errors)
call go#util#EchoError(l:out)
return
endif
call go#list#Window(l:listtype, len(l:errors))
if !empty(l:errors) && !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
else else
call win_gotoid(l:winid) call win_gotoid(l:winid)
endif endif
if go#config#EchoCommandInfo()
call go#util#EchoError(printf('[%s] %s', l:type, l:state))
endif
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#util#EchoSuccess('[vet] PASS') if go#config#EchoCommandInfo()
call go#util#EchoSuccess(printf('[%s] %s', l:type, l:state))
endif
endif endif
call go#statusline#Update(expand('%:p:h'), l:status)
endfunction endfunction
" ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in " ErrCheck calls 'errcheck' for the given packages. Any warnings are populated in
" the location list " the location list
function! go#lint#Errcheck(bang, ...) abort function! go#lint#Errcheck(bang, ...) abort
if a:0 == 0 call go#cmd#autowrite()
let l:import_path = go#package#ImportPath()
if import_path == -1 let l:cmd = [go#config#ErrcheckBin(), '-abspath']
call go#util#EchoError('package is not inside GOPATH src')
return let buildtags = go#config#BuildTags()
endif if buildtags isnot ''
else let l:cmd += ['-tags', buildtags]
let l:import_path = join(a:000, ' ')
endif endif
call go#util#EchoProgress('[errcheck] analysing ...') if a:0 == 0
let l:import_path = go#package#ImportPath()
if l:import_path == -1
call go#util#EchoError('could not determine package')
return
endif
let l:cmd = add(l:cmd, l:import_path)
else
let l:cmd = extend(l:cmd, a:000)
endif
let l:type = 'errcheck'
if go#config#EchoCommandInfo()
call go#util#EchoProgress(printf('[%s] analyzing...', l:type))
endif
let l:status = {
\ 'desc': 'current status',
\ 'type': l:type,
\ 'state': "started",
\ }
redraw redraw
let [l:out, l:err] = go#util#Exec([go#config#ErrcheckBin(), '-abspath', l:import_path]) call go#statusline#Update(expand('%:p:h'), l:status)
let [l:out, l:err] = go#util#ExecInDir(l:cmd)
let l:status.state = 'success'
let l:state = 'PASS'
let l:listtype = go#list#Type("GoErrCheck") let l:listtype = go#list#Type("GoErrCheck")
if l:err != 0 if l:err != 0
let l:winid = win_getid(winnr()) let l:status.state = 'failed'
let errformat = "%f:%l:%c:\ %m, %f:%l:%c\ %#%m" let l:state = 'FAIL'
" Parse and populate our location list let l:winid = win_getid(winnr())
call go#list#ParseFormat(l:listtype, errformat, split(out, "\n"), 'Errcheck')
if l:err == 1
let l:errformat = "%f:%l:%c:\ %m,%f:%l:%c\ %#%m"
" Parse and populate our location list
call go#list#ParseFormat(l:listtype, l:errformat, split(out, "\n"), 'Errcheck', 0)
endif
let l:errors = go#list#Get(l:listtype) let l:errors = go#list#Get(l:listtype)
if empty(l:errors) if empty(l:errors)
@ -195,18 +343,24 @@ function! go#lint#Errcheck(bang, ...) abort
endif endif
if !empty(errors) if !empty(errors)
call go#list#Populate(l:listtype, errors, 'Errcheck') call go#list#Populate(l:listtype, l:errors, 'Errcheck')
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(l:errors))
if !a:bang if !a:bang
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
else else
call win_gotoid(l:winid) call win_gotoid(l:winid)
endif endif
endif endif
if go#config#EchoCommandInfo()
call go#util#EchoError(printf('[%s] %s', l:type, l:state))
endif
else else
call go#list#Clean(l:listtype) call go#list#Clean(l:listtype)
call go#util#EchoSuccess('[errcheck] PASS') if go#config#EchoCommandInfo()
call go#util#EchoSuccess(printf('[%s] %s', l:type, l:state))
endif
endif endif
call go#statusline#Update(expand('%:p:h'), l:status)
endfunction endfunction
function! go#lint#ToggleMetaLinterAutoSave() abort function! go#lint#ToggleMetaLinterAutoSave() abort
@ -220,16 +374,19 @@ function! go#lint#ToggleMetaLinterAutoSave() abort
call go#util#EchoProgress("auto metalinter enabled") call go#util#EchoProgress("auto metalinter enabled")
endfunction endfunction
function! s:lint_job(args, bang, autosave) function! s:lint_job(metalinter, args, bang, autosave)
let l:opts = { let l:opts = {
\ 'statustype': a:args.statustype, \ 'statustype': a:args.statustype,
\ 'errorformat': a:args.errformat, \ 'errorformat': a:args.errformat,
\ 'for': "GoMetaLinter", \ 'for': 'GoMetaLinter',
\ 'bang': a:bang, \ 'bang': a:bang,
\ } \ }
if a:autosave if a:autosave
let l:opts.for = "GoMetaLinterAutoSave" let l:opts.for = 'GoMetaLinterAutoSave'
" s:metalinterautosavecomplete is really only needed for golangci-lint
let l:opts.complete = funcref('s:metalinterautosavecomplete', [a:metalinter, expand('%:p:t')])
let l:opts.preserveerrors = funcref('s:preserveerrors', [a:autosave])
endif endif
" autowrite is not enabled for jobs " autowrite is not enabled for jobs
@ -238,45 +395,72 @@ function! s:lint_job(args, bang, autosave)
call go#job#Spawn(a:args.cmd, l:opts) call go#job#Spawn(a:args.cmd, l:opts)
endfunction endfunction
function! s:metalintercmd(metalinter) function! s:metalintercmd(metalinter, haslinter)
let l:cmd = [] let l:cmd = []
let bin_path = go#path#CheckBinPath(a:metalinter) let bin_path = go#path#CheckBinPath(a:metalinter)
if !empty(bin_path) if !empty(bin_path)
if a:metalinter == "gometalinter" if a:metalinter == "golangci-lint"
let l:cmd = s:gometalintercmd(bin_path) let l:cmd = s:golangcilintcmd(bin_path, a:haslinter)
elseif a:metalinter == "golangci-lint"
let l:cmd = s:golangcilintcmd(bin_path)
endif endif
endif endif
return cmd return cmd
endfunction endfunction
function! s:gometalintercmd(bin_path) function! s:golangcilintcmd(bin_path, haslinter)
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 = [a:bin_path]
let cmd += ["run"] let cmd += ["run"]
let cmd += ["--print-issued-lines=false"] let cmd += ["--print-issued-lines=false"]
let cmd += ["--disable-all"] let cmd += ['--build-tags', go#config#BuildTags()]
" do not use the default exclude patterns, because doing so causes golint " do not use the default exclude patterns, because doing so causes golint
" problems about missing doc strings to be ignored and other things that " problems about missing doc strings to be ignored and other things that
" golint identifies. " golint identifies.
let cmd += ["--exclude-use-default=false"] let cmd += ["--exclude-use-default=false"]
if a:haslinter
let cmd += ["--disable-all"]
endif
return cmd return cmd
endfunction endfunction
function! s:metalinterautosavecomplete(metalinter, filepath, job, exit_code, messages)
if a:metalinter != 'golangci-lint'
return
endif
if len(a:messages) == 0
return
endif
let l:idx = len(a:messages) - 1
while l:idx >= 0
" leave in any messages that report errors about a:filepath or that report
" more general problems that prevent golangci-lint from linting
" a:filepath.
if a:messages[l:idx] !~# '^' . a:filepath . ':' && a:messages[l:idx] !~# '^level='
call remove(a:messages, l:idx)
endif
let l:idx -= 1
endwhile
endfunction
function! s:errorformat(metalinter) abort
if a:metalinter == 'golangci-lint'
" Golangci-lint can output the following:
" <file>:<line>:<column>: <message> (<linter>)
" This can be defined by the following errorformat:
return 'level=%tarning\ msg="%m:\ [%f:%l:%c:\ %.%#]",level=%tarning\ msg="%m",level=%trror\ msg="%m:\ [%f:%l:%c:\ %.%#]",level=%trror\ msg="%m",%f:%l:%c:\ %m,%f:%l\ %m'
elseif a:metalinter == 'gopls'
return '%f:%l:%c:%t:\ %m,%f:%l:%c::\ %m,%f:%l::%t:\ %m'
endif
endfunction
function! s:preserveerrors(autosave, listtype) abort
return a:autosave && a:listtype == go#list#Type("GoFmt") && go#config#FmtAutosave() && isdirectory(expand('%:p:h'))
endfunction
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -2,23 +2,24 @@
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
func! Test_Gometa() abort
call s:gometa('gometaliner')
endfunc
func! Test_GometaGolangciLint() abort func! Test_GometaGolangciLint() abort
call s:gometa('golangci-lint') call s:gometa('golangci-lint')
endfunc endfunc
func! s:gometa(metalinter) abort func! s:gometa(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go' silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try try
let g:go_metalinter_comand = a:metalinter let g:go_metalinter_command = a:metalinter
let expected = [ 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)'} \ {'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)'}
\ ] \ ]
if a:metalinter == 'golangci-lint'
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%')+5, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function `MissingFooDoc` should have comment or be unexported (golint)'}
\ ]
endif
" clear the quickfix lists " clear the quickfix lists
call setqflist([], 'r') call setqflist([], 'r')
@ -36,34 +37,32 @@ func! s:gometa(metalinter) abort
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(actual, expected)
finally finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_enabled unlet g:go_metalinter_enabled
endtry endtry
endfunc endfunc
func! Test_GometaWithDisabled() abort func! Test_GometaGolangciLint_shadow() abort
call s:gometawithdisabled('gometalinter') call s:gometa_shadow('golangci-lint')
endfunc endfunc
func! Test_GometaWithDisabledGolangciLint() abort func! s:gometa_shadow(metalinter) abort
call s:gometawithdisabled('golangci-lint') let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
endfunc silent exe 'e ' . $GOPATH . '/src/lint/golangci-lint/problems/shadow/problems.go'
func! s:gometawithdisabled(metalinter) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint'
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try try
let g:go_metalinter_comand = a:metalinter let g:go_metalinter_command = a:metalinter
let expected = [ 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)'} \ {'lnum': 4, 'bufnr': bufnr('%'), 'col': 7, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'module': '', 'text': '[runner] Can''t run linter golint: golint: analysis skipped: errors in package'},
\ {'lnum': 4, 'bufnr': bufnr('%'), 'col': 7, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'e', 'module': '', 'text': 'Running error: golint: analysis skipped: errors in package'}
\ ] \ ]
" clear the quickfix lists " clear the quickfix lists
call setqflist([], 'r') call setqflist([], 'r')
let g:go_metalinter_disabled = ['vet'] let g:go_metalinter_enabled = ['golint']
call go#lint#Gometa(0, 0, $GOPATH . '/src/foo') call go#lint#Gometa(0, 0)
let actual = getqflist() let actual = getqflist()
let start = reltime() let start = reltime()
@ -74,58 +73,305 @@ func! s:gometawithdisabled(metalinter) abort
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(actual, expected)
finally finally
unlet g:go_metalinter_disabled call call(RestoreGOPATH, [])
unlet g:go_metalinter_enabled
endtry endtry
endfunc endfunc
func! Test_GometaAutoSave() abort
call s:gometaautosave('gometalinter')
endfunc
func! Test_GometaAutoSaveGolangciLint() abort func! Test_GometaAutoSaveGolangciLint() abort
call s:gometaautosave('golangci-lint') call s:gometaautosave('golangci-lint', 0)
endfunc endfunc
func! s:gometaautosave(metalinter) abort func! Test_GometaAutoSaveKeepsErrors() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' call s:gometaautosave('golangci-lint', 1)
endfunc
func! s:gometaautosave(metalinter, withList) abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go' silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
try try
let g:go_metalinter_comand = a:metalinter let g:go_metalinter_command = a:metalinter
let expected = [ let l: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)'} \ {'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)'}
\ ] \ ]
if a:metalinter == 'golangci-lint'
let l:expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function `MissingDoc` should have comment or be unexported (golint)'}
\ ]
endif
let winnr = winnr() let l:list = []
if a:withList
let l:list = [
\ {'lnum': 1, 'bufnr': 1, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'before metalinter'}
\ ]
let l:expected = extend(l:list, l:expected)
endif
" clear the location lists " set the location lists
call setloclist(l:winnr, [], 'r') call setloclist(0, l:list, 'r')
let g:go_metalinter_autosave_enabled = ['golint'] let g:go_metalinter_autosave_enabled = ['golint']
call go#lint#Gometa(0, 1) call go#lint#Gometa(0, 1)
let actual = getloclist(l:winnr) let l:actual = getloclist(0)
let l:start = reltime()
while len(l:actual) != len(l:expected) && reltimefloat(reltime(l:start)) < 10
sleep 100m
let l:actual = getloclist(0)
endwhile
call gotest#assert_quickfix(l:actual, l:expected)
finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_autosave_enabled
endtry
endfunc
func! Test_GometaGolangciLint_importabs() abort
call s:gometa_importabs('golangci-lint')
endfunc
func! s:gometa_importabs(metalinter) abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/golangci-lint/problems/importabs/problems.go'
try
let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 3, 'bufnr': bufnr('%'), 'col': 8, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'module': '', 'text': '[runner] Can''t run linter golint: golint: analysis skipped: errors in package'},
\ {'lnum': 3, 'bufnr': bufnr('%'), 'col': 8, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'e', 'module': '', 'text': 'Running error: golint: analysis skipped: errors in package'},
\ ]
" clear the quickfix lists
call setqflist([], 'r')
let g:go_metalinter_enabled = ['golint']
call go#lint#Gometa(0, 0)
let actual = getqflist()
let start = reltime() let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10 while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m sleep 100m
let actual = getloclist(l:winnr) let actual = getqflist()
endwhile endwhile
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(actual, expected)
finally finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_enabled
endtry
endfunc
func! Test_GometaAutoSaveGolangciLint_importabs() abort
call s:gometaautosave_importabs('golangci-lint')
endfunc
func! s:gometaautosave_importabs(metalinter) abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/golangci-lint/problems/importabs/ok.go'
try
let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 3, 'bufnr': bufnr('%')+1, 'col': 8, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'module': '', 'text': '[runner] Can''t run linter golint: golint: analysis skipped: errors in package'},
\ {'lnum': 3, 'bufnr': bufnr('%')+1, 'col': 8, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'e', 'module': '', 'text': 'Running error: golint: analysis skipped: errors in package'}
\ ]
" clear the location lists
call setloclist(0, [], 'r')
let g:go_metalinter_autosave_enabled = ['golint']
call go#lint#Gometa(0, 1)
let actual = getloclist(0)
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(0)
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_autosave_enabled
endtry
endfunc
func! Test_GometaGolangciLint_multiple() abort
call s:gometa_multiple('golangci-lint')
endfunc
func! s:gometa_multiple(metalinter) abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/golangci-lint/problems/multiple/problems.go'
try
let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 8, 'bufnr': bufnr('%'), 'col': 7, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'module': '', 'text': '[runner] Can''t run linter golint: golint: analysis skipped: errors in package'},
\ {'lnum': 8, 'bufnr': bufnr('%'), 'col': 7, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'e', 'module': '', 'text': 'Running error: golint: analysis skipped: errors in package'},
\ ]
" clear the quickfix lists
call setqflist([], 'r')
let g:go_metalinter_enabled = ['golint']
call go#lint#Gometa(0, 0)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_enabled
endtry
endfunc
func! Test_GometaAutoSaveGolangciLint_multiple() abort
call s:gometaautosave_multiple('golangci-lint')
endfunc
func! s:gometaautosave_multiple(metalinter) abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/golangci-lint/problems/multiple/problems.go'
try
let g:go_metalinter_command = a:metalinter
let expected = [
\ {'lnum': 8, 'bufnr': bufnr('%'), 'col': 7, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'w', 'module': '', 'text': '[runner] Can''t run linter golint: golint: analysis skipped: errors in package'},
\ {'lnum': 8, 'bufnr': bufnr('%'), 'col': 7, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': 'e', 'module': '', 'text': 'Running error: golint: analysis skipped: errors in package'},
\ ]
" clear the location lists
call setloclist(0, [], 'r')
let g:go_metalinter_autosave_enabled = ['golint']
call go#lint#Gometa(0, 1)
let actual = getloclist(0)
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getloclist(0)
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call call(RestoreGOPATH, [])
unlet g:go_metalinter_autosave_enabled unlet g:go_metalinter_autosave_enabled
endtry endtry
endfunc endfunc
func! Test_Vet() abort func! Test_Vet() abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint' let l:tmp = gotest#load_fixture('lint/src/vet/vet.go')
silent exe 'e ' . $GOPATH . '/src/vet/vet.go'
try
let expected = [
\ {'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()
" clear the location lists
call setqflist([], 'r')
call go#lint#Vet(1)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_Vet_subdir() abort
let l:tmp = gotest#load_fixture('lint/src/vet/vet.go')
" go up one directory to easily test that go vet's file paths are handled
" correctly when the working directory is not the directory that contains
" the file being vetted.
call go#util#Chdir('..')
try
let expected = [
\ {'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()
" clear the location lists
call setqflist([], 'r')
call go#lint#Vet(1)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_Vet_compilererror() abort
let l:tmp = gotest#load_fixture('lint/src/vet/compilererror/compilererror.go')
try
let expected = [
\ {'lnum': 6, 'bufnr': bufnr('%'), 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': "missing ',' before newline in argument list (and 1 more errors)"}
\ ]
let winnr = winnr()
" clear the location lists
call setqflist([], 'r')
call go#lint#Vet(1)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_Lint_GOPATH() abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/lint/lint.go'
compiler go compiler go
let expected = [ let expected = [
\ {'lnum': 7, 'bufnr': bufnr('%'), 'col': 2, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', \ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported'},
\ 'text': 'Printf format %d has arg str of wrong type string'} \ {'lnum': 5, 'bufnr': bufnr('%')+7, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function AlsoMissingDoc should have comment or be unexported'}
\ ] \ ]
let winnr = winnr() let winnr = winnr()
@ -133,7 +379,35 @@ func! Test_Vet() abort
" clear the location lists " clear the location lists
call setqflist([], 'r') call setqflist([], 'r')
call go#lint#Vet(1) call go#lint#Golint(1)
let actual = getqflist()
let start = reltime()
while len(actual) == 0 && reltimefloat(reltime(start)) < 10
sleep 100m
let actual = getqflist()
endwhile
call gotest#assert_quickfix(actual, expected)
call call(RestoreGOPATH, [])
endfunc
func! Test_Lint_NullModule() abort
silent exe 'e ' . fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/lint/src/lint/lint.go'
compiler go
let expected = [
\ {'lnum': 5, 'bufnr': bufnr('%'), 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function MissingDoc should have comment or be unexported'},
\ {'lnum': 5, 'bufnr': bufnr('%')+7, 'col': 1, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exported function AlsoMissingDoc should have comment or be unexported'}
\ ]
let winnr = winnr()
" clear the location lists
call setqflist([], 'r')
call go#lint#Golint(1)
let actual = getqflist() let actual = getqflist()
let start = reltime() let start = reltime()
@ -145,6 +419,70 @@ func! Test_Vet() abort
call gotest#assert_quickfix(actual, expected) call gotest#assert_quickfix(actual, expected)
endfunc endfunc
func! Test_Errcheck() abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/errcheck/errcheck.go'
try
let l:bufnr = bufnr('')
let expected = [
\ {'lnum': 9, 'bufnr': bufnr(''), 'col': 9, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'module': '', 'text': ":\tio.Copy(os.Stdout, os.Stdin)"},
\ {'lnum': 10, 'bufnr': bufnr('')+1, 'col': 9, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'module': '', 'text': ":\tio.Copy(os.Stdout, os.Stdin)"},
\ ]
" clear the location lists
call setqflist([], 'r')
call go#lint#Errcheck(1)
call gotest#assert_quickfix(getqflist(), expected)
call assert_equal(l:bufnr, bufnr(''))
finally
call call(RestoreGOPATH, [])
endtry
endfunc
func! Test_Errcheck_options() abort
let RestoreGOPATH = go#util#SetEnv('GOPATH', fnamemodify(getcwd(), ':p') . 'test-fixtures/lint')
silent exe 'e ' . $GOPATH . '/src/errcheck/errcheck.go'
try
let l:bufnr = bufnr('')
let expected = [
\ {'lnum': 9, 'bufnr': bufnr(''), 'col': 9, 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'module': '', 'text': ":\tio.Copy(os.Stdout, os.Stdin)"},
\ ]
" clear the location lists
call setqflist([], 'r')
call go#lint#Errcheck(1, '-ignoretests')
call gotest#assert_quickfix(getqflist(), expected)
call assert_equal(l:bufnr, bufnr(''))
finally
call call(RestoreGOPATH, [])
endtry
endfunc
func! Test_Errcheck_compilererror() abort
let l:tmp = gotest#load_fixture('lint/src/errcheck/compilererror/compilererror.go')
try
let l:bufnr = bufnr('')
let expected = []
" clear the location lists
call setqflist([], 'r')
call go#lint#Errcheck(1)
call gotest#assert_quickfix(getqflist(), expected)
call assert_equal(l:bufnr, bufnr(''))
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -49,26 +49,23 @@ endfunction
function! go#list#Populate(listtype, items, title) abort function! go#list#Populate(listtype, items, title) abort
if a:listtype == "locationlist" if a:listtype == "locationlist"
call setloclist(0, a:items, 'r') call setloclist(0, a:items, 'r')
call setloclist(0, [], 'a', {'title': a:title})
" The last argument ({what}) is introduced with 7.4.2200:
" https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640
if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif
else else
call setqflist(a:items, 'r') call setqflist(a:items, 'r')
if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif call setqflist([], 'a', {'title': a:title})
endif endif
endfunction endfunction
" Parse parses the given items based on the specified errorformat and " Parse parses the given items based on the specified errorformat and
" populates the list. " populates the list.
function! go#list#ParseFormat(listtype, errformat, items, title) abort function! go#list#ParseFormat(listtype, errformat, items, title, add) abort
" backup users errorformat, will be restored once we are finished " backup users errorformat, will be restored once we are finished
let old_errorformat = &errorformat let old_errorformat = &errorformat
" parse and populate the location list " parse and populate the location list
let &errorformat = a:errformat let &errorformat = a:errformat
try try
call go#list#Parse(a:listtype, a:items, a:title) call go#list#Parse(a:listtype, a:items, a:title, a:add)
finally finally
"restore back "restore back
let &errorformat = old_errorformat let &errorformat = old_errorformat
@ -77,13 +74,26 @@ endfunction
" Parse parses the given items based on the global errorformat and " Parse parses the given items based on the global errorformat and
" populates the list. " populates the list.
function! go#list#Parse(listtype, items, title) abort function! go#list#Parse(listtype, items, title, add) abort
let l:list = []
if a:add
let l:list = go#list#Get(a:listtype)
endif
if a:listtype == "locationlist" if a:listtype == "locationlist"
lgetexpr a:items if a:add
if has("patch-7.4.2200") | call setloclist(0, [], 'a', {'title': a:title}) | endif laddexpr a:items
else
lgetexpr a:items
endif
call setloclist(0, [], 'a', {'title': a:title})
else else
cgetexpr a:items if a:add
if has("patch-7.4.2200") | call setqflist([], 'a', {'title': a:title}) | endif caddexpr a:items
else
cgetexpr a:items
endif
call setqflist([], 'a', {'title': a:title})
endif endif
endfunction endfunction
@ -138,6 +148,7 @@ endfunction
" in g:go_list_type_commands. " in g:go_list_type_commands.
let s:default_list_type_commands = { let s:default_list_type_commands = {
\ "GoBuild": "quickfix", \ "GoBuild": "quickfix",
\ "GoDiagnostics": "quickfix",
\ "GoDebug": "quickfix", \ "GoDebug": "quickfix",
\ "GoErrCheck": "quickfix", \ "GoErrCheck": "quickfix",
\ "GoFmt": "locationlist", \ "GoFmt": "locationlist",
@ -152,6 +163,8 @@ let s:default_list_type_commands = {
\ "GoRun": "quickfix", \ "GoRun": "quickfix",
\ "GoTest": "quickfix", \ "GoTest": "quickfix",
\ "GoVet": "quickfix", \ "GoVet": "quickfix",
\ "GoReferrers": "locationlist",
\ "GoImplements": "locationlist",
\ "_guru": "locationlist", \ "_guru": "locationlist",
\ "_term": "locationlist", \ "_term": "locationlist",
\ "_job": "locationlist", \ "_job": "locationlist",

File diff suppressed because it is too large Load diff

View file

@ -28,7 +28,7 @@ let s:Event = 23
let s:Operator = 24 let s:Operator = 24
let s:TypeParameter = 25 let s:TypeParameter = 25
function! go#lsp#completionitemkind#Vim(kind) function! go#lsp#completionitemkind#Vim(kind) abort
if a:kind == s:Method || a:kind == s:Function || a:kind == s:Constructor if a:kind == s:Method || a:kind == s:Function || a:kind == s:Constructor
return 'f' return 'f'
elseif a:kind == s:Variable || a:kind == s:Constant elseif a:kind == s:Variable || a:kind == s:Constant
@ -40,6 +40,22 @@ function! go#lsp#completionitemkind#Vim(kind)
endif endif
endfunction endfunction
function! go#lsp#completionitemkind#IsFunction(kind) abort
if a:kind == s:Function
return 1
endif
return 0
endfunction
function! go#lsp#completionitemkind#IsMethod(kind) abort
if a:kind == s:Method
return 1
endif
return 0
endfunction
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -0,0 +1,72 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
" go#lsp#lsp#Position returns the LSP text position. If no arguments are
" provided, the cursor position is assumed. Otherwise, there should be two
" arguments: the line and the column.
function! go#lsp#lsp#Position(...)
if a:0 < 2
let [l:line, l:col] = getpos('.')[1:2]
else
let l:line = a:1
let l:col = a:2
endif
let l:content = getline(l:line)
" LSP uses 0-based lines.
return [l:line - 1, s:character(l:line, l:col-1)]
endfunction
function! s:strlen(str) abort
let l:runes = split(a:str, '\zs')
return len(l:runes) + len(filter(l:runes, 'char2nr(v:val)>=0x10000'))
endfunction
function! s:character(line, col) abort
return s:strlen(getline(a:line)[:col([a:line, a:col - 1])])
endfunction
" go#lsp#PositionOf returns len(content[0:units]) where units is utf-16 code
" units. This is mostly useful for converting LSP text position to vim
" position.
function! go#lsp#lsp#PositionOf(content, units, ...) abort
if a:units == 0
return 1
endif
let l:remaining = a:units
let l:str = ''
for l:rune in split(a:content, '\zs')
if l:remaining < 0
break
endif
let l:remaining -= 1
if char2nr(l:rune) >= 0x10000
let l:remaining -= 1
endif
let l:str = l:str . l:rune
endfor
return len(l:str)
endfunction
function! go#lsp#lsp#SeverityToErrorType(severity) abort
if a:severity == 1
return 'E'
elseif a:severity == 2
return 'W'
elseif a:severity == 3
return 'I'
elseif a:severity == 4
return 'I'
endif
return ''
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,32 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
function! Test_PositionOf_Simple()
let l:actual = go#lsp#lsp#PositionOf("just ascii", 3)
call assert_equal(4, l:actual)
endfunc
function! Test_PositionOf_MultiByte()
" ⌘ is U+2318, which encodes to three bytes in utf-8 and 1 code unit in
" utf-16.
let l:actual = go#lsp#lsp#PositionOf("⌘⌘ foo", 3)
call assert_equal(8, l:actual)
endfunc
function! Test_PositionOf_MultipleCodeUnit()
" 𐐀 is U+10400, which encodes to 4 bytes in utf-8 and 2 code units in
" utf-16.
let l:actual = go#lsp#lsp#PositionOf("𐐀 bar", 3)
call assert_equal(6, l:actual)
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -10,17 +10,100 @@ function! go#lsp#message#Initialize(wd) abort
\ 'processId': getpid(), \ 'processId': getpid(),
\ 'rootUri': go#path#ToURI(a:wd), \ 'rootUri': go#path#ToURI(a:wd),
\ 'capabilities': { \ 'capabilities': {
\ 'workspace': {}, \ 'workspace': {
\ 'workspaceFolders': v:true,
\ 'didChangeConfiguration': {
\ 'dynamicRegistration': v:true,
\ },
\ 'configuration': v:true,
\ },
\ 'textDocument': { \ 'textDocument': {
\ 'hover': { \ 'hover': {
\ 'contentFormat': ['plaintext'], \ 'contentFormat': ['plaintext'],
\ }, \ },
\ 'completion': {
\ 'completionItem': {
\ 'snippetSupport': go#config#GoplsUsePlaceholders() ? v:true : v:false,
\ },
\ },
\ 'codeAction': {
\ 'codeActionLiteralSupport': {
\ 'codeActionKind': {
\ 'valueSet': ['source.organizeImports'],
\ },
\ },
\ },
\ } \ }
\ } \ },
\ 'workspaceFolders': [s:workspaceFolder(0, a:wd)],
\ } \ }
\ } \ }
endfunction endfunction
function! go#lsp#message#Initialized() abort
return {
\ 'notification': 1,
\ 'method': 'initialized',
\ 'params': {},
\ }
endfunction
function! go#lsp#message#Shutdown() abort
return {
\ 'notification': 0,
\ 'method': 'shutdown',
\ }
endfunction
function! go#lsp#message#Format(file) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/formatting',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'options': {
\ 'insertSpaces': v:false,
\ },
\ }
\ }
endfunction
function! go#lsp#message#CodeActionImports(file) abort
return s:codeAction('source.organizeImports', a:file)
endfunction
function! s:codeAction(name, file) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/codeAction',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'range': {
\ 'start': {'line': 0, 'character': 0},
\ 'end': {'line': line('$'), 'character': 0},
\ },
\ 'context': {
\ 'only': [a:name],
\ },
\ }
\ }
endfunction
function! go#lsp#message#Exit() abort
return {
\ 'notification': 1,
\ 'method': 'exit',
\ }
endfunction
function! go#lsp#message#WorkspaceFoldersResult(dirs) abort
return map(copy(a:dirs), function('s:workspaceFolder', []))
endfunction
function! go#lsp#message#Definition(file, line, col) abort function! go#lsp#message#Definition(file, line, col) abort
return { return {
\ 'notification': 0, \ 'notification': 0,
@ -47,7 +130,20 @@ function! go#lsp#message#TypeDefinition(file, line, col) abort
\ } \ }
endfunction endfunction
function! go#lsp#message#DidOpen(file, content) abort function! go#lsp#message#Implementation(file, line, col) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/implementation',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col)
\ }
\ }
endfunction
function! go#lsp#message#DidOpen(file, content, version) abort
return { return {
\ 'notification': 1, \ 'notification': 1,
\ 'method': 'textDocument/didOpen', \ 'method': 'textDocument/didOpen',
@ -56,18 +152,20 @@ function! go#lsp#message#DidOpen(file, content) abort
\ 'uri': go#path#ToURI(a:file), \ 'uri': go#path#ToURI(a:file),
\ 'languageId': 'go', \ 'languageId': 'go',
\ 'text': a:content, \ 'text': a:content,
\ 'version': a:version,
\ } \ }
\ } \ }
\ } \ }
endfunction endfunction
function! go#lsp#message#DidChange(file, content) abort function! go#lsp#message#DidChange(file, content, version) abort
return { return {
\ 'notification': 1, \ 'notification': 1,
\ 'method': 'textDocument/didChange', \ 'method': 'textDocument/didChange',
\ 'params': { \ 'params': {
\ 'textDocument': { \ 'textDocument': {
\ 'uri': go#path#ToURI(a:file), \ 'uri': go#path#ToURI(a:file),
\ 'version': a:version,
\ }, \ },
\ 'contentChanges': [ \ 'contentChanges': [
\ { \ {
@ -103,6 +201,22 @@ function! go#lsp#message#Completion(file, line, col) abort
\ } \ }
endfunction endfunction
function! go#lsp#message#References(file, line, col) abort
return {
\ 'notification': 0,
\ 'method': 'textDocument/references',
\ 'params': {
\ 'textDocument': {
\ 'uri': go#path#ToURI(a:file)
\ },
\ 'position': s:position(a:line, a:col),
\ 'context': {
\ 'includeDeclaration': v:true,
\ },
\ }
\ }
endfunction
function! go#lsp#message#Hover(file, line, col) abort function! go#lsp#message#Hover(file, line, col) abort
return { return {
\ 'notification': 0, \ 'notification': 0,
@ -116,8 +230,120 @@ function! go#lsp#message#Hover(file, line, col) abort
\ } \ }
endfunction endfunction
function! go#lsp#message#ChangeWorkspaceFolders(add, remove) abort
let l:addDirs = map(copy(a:add), function('s:workspaceFolder', []))
let l:removeDirs = map(copy(a:remove), function('s:workspaceFolder', []))
return {
\ 'notification': 1,
\ 'method': 'workspace/didChangeWorkspaceFolders',
\ 'params': {
\ 'event': {
\ 'removed': l:removeDirs,
\ 'added': l:addDirs,
\ },
\ }
\ }
endfunction
function! go#lsp#message#ConfigurationResult(items) abort
let l:result = []
" results must be in the same order as the items
for l:item in a:items
let l:config = {
\ 'buildFlags': [],
\ 'hoverKind': 'Structured',
\ }
let l:buildtags = go#config#BuildTags()
if buildtags isnot ''
let l:config.buildFlags = extend(l:config.buildFlags, ['-tags', go#config#BuildTags()])
endif
let l:deepCompletion = go#config#GoplsDeepCompletion()
let l:matcher = go#config#GoplsMatcher()
let l:completeUnimported = go#config#GoplsCompleteUnimported()
let l:staticcheck = go#config#GoplsStaticCheck()
let l:usePlaceholder = go#config#GoplsUsePlaceholders()
let l:tempModfile = go#config#GoplsTempModfile()
let l:analyses = go#config#GoplsAnalyses()
let l:local = go#config#GoplsLocal()
let l:gofumpt = go#config#GoplsGofumpt()
let l:settings = go#config#GoplsSettings()
if l:deepCompletion isnot v:null
if l:deepCompletion
let l:config.deepCompletion = v:true
else
let l:config.deepCompletion = v:false
endif
endif
if l:matcher isnot v:null
let l:config.matcher = l:matcher
endif
if l:completeUnimported isnot v:null
if l:completeUnimported
let l:config.completeUnimported = v:true
else
let l:config.completeUnimported = v:false
endif
endif
if l:staticcheck isnot v:null
if l:staticcheck
let l:config.staticcheck = v:true
else
let l:config.staticcheck = v:false
endif
endif
if l:usePlaceholder isnot v:null
if l:usePlaceholder
let l:config.usePlaceholders = v:true
else
let l:config.usePlaceholders = v:false
endif
endif
if l:tempModfile isnot v:null
if l:tempModfile
let l:config.tempModfile = v:true
else
let l:config.tempModfile = v:false
endif
endif
if l:analyses isnot v:null
let l:config.analyses = l:analyses
endif
if l:local isnot v:null
let l:config.local = l:local
endif
if l:gofumpt isnot v:null
let l:config.gofumpt = l:gofumpt
endif
if l:settings isnot v:null
let l:config = extend(l:config, l:settings, 'keep')
endif
let l:result = add(l:result, l:config)
endfor
return l:result
endfunction
function s:workspaceFolder(key, val) abort
return {'uri': go#path#ToURI(a:val), 'name': a:val}
endfunction
function! s:position(line, col) abort function! s:position(line, col) abort
return {'line': a:line - 1, 'character': a:col-1} return {'line': a:line, 'character': a:col}
endfunction endfunction
" restore Vi compatibility settings " restore Vi compatibility settings

View file

@ -0,0 +1,97 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
function! Test_GetSimpleTextPosition()
call s:getinfo('lsp text position should align with cursor position after ascii only', 'ascii')
endfunction
function! Test_GetMultiByteTextPosition()
call s:getinfo('lsp text position should align with cursor position after two place of interest symbols ⌘⌘', 'multi-byte')
endfunction
function! Test_GetMultipleCodeUnitTextPosition()
call s:getinfo('lsp text position should align with cursor position after Deseret Capital Letter Long I 𐐀', 'multi-code-units')
endfunction
function! s:getinfo(str, name)
if !go#util#has_job()
return
endif
try
let g:go_info_mode = 'gopls'
let l:tmp = gotest#write_file(a:name . '/position/position.go', [
\ 'package position',
\ '',
\ 'func Example() {',
\ "\tid := " . '"foo"',
\ "\tprintln(" .'"' . a:str . '", id)',
\ '}',
\ ] )
let l:expected = 'var id string'
let l:actual = go#lsp#GetInfo()
call assert_equal(l:expected, l:actual)
finally
call delete(l:tmp, 'rf')
unlet g:go_info_mode
endtry
endfunction
func! Test_Format() abort
try
let expected = join(readfile("test-fixtures/lsp/fmt/format_golden.go"), "\n")
let l:tmp = gotest#load_fixture('lsp/fmt/format.go')
call go#lsp#Format()
" this should now contain the formatted code
let actual = join(go#util#GetLines(), "\n")
call assert_equal(expected, actual)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_Format_SingleNewline() abort
try
let expected = join(readfile("test-fixtures/lsp/fmt/format_golden.go"), "\n")
let l:tmp = gotest#load_fixture('lsp/fmt/newline.go')
call go#lsp#Format()
" this should now contain the formatted code
let actual = join(go#util#GetLines(), "\n")
call assert_equal(expected, actual)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
func! Test_Imports() abort
try
let expected = join(readfile("test-fixtures/lsp/imports/imports_golden.go"), "\n")
let l:tmp = gotest#load_fixture('lsp/imports/imports.go')
call go#lsp#Imports()
" this should now contain the expected imports code
let actual = join(go#util#GetLines(), "\n")
call assert_equal(expected, actual)
finally
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -7,11 +7,15 @@ let s:go_major_version = ""
function! go#mod#Format() abort function! go#mod#Format() abort
" go mod only exists in `v1.11` " go mod only exists in `v1.11`
if empty(s:go_major_version) if empty(s:go_major_version)
let tokens = matchlist(go#util#System("go version"), '\d\+.\(\d\+\)\(\.\d\+\)\? ') let tokens = matchlist(go#util#Exec(['go', 'version']), '\d\+.\(\d\+\)\(\.\d\+\)\? ')
let s:go_major_version = str2nr(tokens[1]) if len(tokens) > 0
let s:go_major_version = str2nr(tokens[1])
else
let s:go_major_version = ""
endif
endif endif
if s:go_major_version < "11" if !empty(s:go_major_version) && s:go_major_version < "11"
call go#util#EchoError("Go v1.11 is required to format go.mod file") call go#util#EchoError("Go v1.11 is required to format go.mod file")
return return
endif endif
@ -79,18 +83,11 @@ function! go#mod#update_file(source, target)
let l:listtype = go#list#Type("GoModFmt") let l:listtype = go#list#Type("GoModFmt")
" the title information was introduced with 7.4-2200 " clean up previous list
" https://github.com/vim/vim/commit/d823fa910cca43fec3c31c030ee908a14c272640 if l:listtype == "quickfix"
if has('patch-7.4.2200') let l:list_title = getqflist({'title': 1})
" 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 else
" can't check the title, so assume that the list was for go fmt. let l:list_title = getloclist(0, {'title': 1})
let l:list_title = {'title': 'Format'}
endif endif
if has_key(l:list_title, "title") && l:list_title['title'] == "Format" if has_key(l:list_title, "title") && l:list_title['title'] == "Format"

View file

@ -39,7 +39,7 @@ function! s:paths() abort
if executable('go') if executable('go')
let s:goroot = go#util#env("goroot") let s:goroot = go#util#env("goroot")
if go#util#ShellError() != 0 if go#util#ShellError() != 0
echomsg '''go env GOROOT'' failed' call go#util#EchoError('`go env GOROOT` failed')
endif endif
else else
let s:goroot = $GOROOT let s:goroot = $GOROOT
@ -82,6 +82,10 @@ function! s:vendordirs() abort
if l:err != 0 if l:err != 0
return [] return []
endif endif
if empty(l:root)
return []
endif
let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src' let l:root = split(l:root, '\n')[0] . go#util#PathSep() . 'src'
let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}']) let [l:dir, l:err] = go#util#ExecInDir(['go', 'list', '-f', '{{.Dir}}'])
@ -111,55 +115,77 @@ function! s:vendordirs() abort
endfunction endfunction
let s:import_paths = {} let s:import_paths = {}
" ImportPath returns the import path of the package for current buffer. " ImportPath returns the import path of the package for current buffer. It
" returns -1 if the import path cannot be determined.
function! go#package#ImportPath() abort function! go#package#ImportPath() abort
let dir = expand("%:p:h") let l:dir = expand("%:p:h")
if has_key(s:import_paths, dir) if has_key(s:import_paths, dir)
return s:import_paths[dir] return s:import_paths[l:dir]
endif endif
let [l:out, l:err] = go#util#ExecInDir(['go', 'list']) let l:importpath = go#package#FromPath(l:dir)
if l:err != 0 if type(l:importpath) == type(0)
return -1 return -1
endif endif
let l:importpath = split(out, '\n')[0] let s:import_paths[l:dir] = l:importpath
" 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
let s:import_paths[dir] = l:importpath
return l:importpath return l:importpath
endfunction endfunction
" go#package#FromPath returns the import path of arg. -1 is returned when arg
" FromPath returns the import path of arg. " does not specify a package. -2 is returned when arg is a relative path
" outside of GOPATH, not in a module, and not below the current working
" directory. A relative path is returned when in a null module at or below the
" current working directory..
function! go#package#FromPath(arg) abort function! go#package#FromPath(arg) abort
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd' let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
let l:dir = getcwd() let l:dir = getcwd()
let l:path = a:arg let l:path = fnamemodify(a:arg, ':p')
if !isdirectory(l:path) if !isdirectory(l:path)
let l:path = fnamemodify(l:path, ':h') let l:path = fnamemodify(l:path, ':h')
endif endif
execute l:cd fnameescape(l:path) execute l:cd fnameescape(l:path)
let [l:out, l:err] = go#util#Exec(['go', 'list']) try
execute l:cd fnameescape(l:dir) if glob("*.go") == ""
if l:err != 0 " There's no Go code in this directory. We might be in a module directory
return -1 " which doesn't have any code at this level. To avoid `go list` making a
endif " bunch of HTTP requests to fetch dependencies, short-circuit `go list`
" and return -1 immediately.
if !empty(s:module())
return -1
endif
endif
let [l:out, l:err] = go#util#Exec(['go', 'list'])
if l:err != 0
return -1
endif
let l:importpath = split(l:out, '\n')[0] let l:importpath = split(l:out, '\n')[0]
finally
execute l:cd fnameescape(l:dir)
endtry
" go list returns '_CURRENTDIRECTORY' if the directory is not inside GOPATH. " go list returns '_CURRENTDIRECTORY' if the directory is in a null module
" Check it and retun an error if that is the case " (i.e. neither in GOPATH nor in a module). Return a relative import path
" if possible or an error if that is the case.
if l:importpath[0] ==# '_' if l:importpath[0] ==# '_'
return -1 let l:relativeimportpath = fnamemodify(l:importpath[1:], ':.')
if go#util#IsWin()
let l:relativeimportpath = substitute(l:relativeimportpath, '\\', '/', 'g')
endif
if l:relativeimportpath == l:importpath[1:]
return '.'
endif
if l:relativeimportpath[0] == '/'
return -2
endif
let l:importpath= printf('./%s', l:relativeimportpath)
endif endif
return l:importpath return l:importpath
@ -206,10 +232,17 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
let vendordirs = s:vendordirs() let vendordirs = s:vendordirs()
let l:modcache = go#util#env('gomodcache')
let ret = {} let ret = {}
for dir in dirs for dir in dirs
" this may expand to multiple lines " this may expand to multiple lines
let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n") let root = split(expand(dir . '/pkg/' . s:goos . '_' . s:goarch), "\n")
if l:modcache != ''
let root = add(root, l:modcache)
else
let root = add(root, expand(dir . '/pkg/mod'))
endif
let root = add(root, expand(dir . '/src'), ) let root = add(root, expand(dir . '/src'), )
let root = extend(root, vendordirs) let root = extend(root, vendordirs)
let root = add(root, module) let root = add(root, module)
@ -234,9 +267,8 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
let glob = module.dir let glob = module.dir
endif endif
elseif stridx(module.path, a:ArgLead) == 0 && stridx(module.path, '/', len(a:ArgLead)) < 0 elseif stridx(module.path, a:ArgLead) == 0 && stridx(module.path, '/', len(a:ArgLead)) < 0
" use the module directory when a:ArgLead is contained in " use the module directory when module.path begins wih a:ArgLead and
" module.path and module.path does not have any path segments after " module.path does not have any path segments after a:ArgLead.
" a:ArgLead.
let glob = module.dir let glob = module.dir
else else
continue continue
@ -251,6 +283,11 @@ function! go#package#Complete(ArgLead, CmdLine, CursorPos) abort
if fnamemodify(candidate, ':t') == 'vendor' if fnamemodify(candidate, ':t') == 'vendor'
continue continue
endif endif
" if path contains version info, strip it out
let vidx = strridx(candidate, '@')
if vidx >= 0
let candidate = strpart(candidate, 0, vidx)
endif
let candidate .= '/' let candidate .= '/'
elseif candidate !~ '\.a$' elseif candidate !~ '\.a$'
continue continue

View file

@ -28,11 +28,11 @@ function! go#path#GoPath(...) abort
let s:initial_go_path = "" let s:initial_go_path = ""
endif endif
echon "vim-go: " | echohl Function | echon "GOPATH restored to ". $GOPATH | echohl None call go#util#EchoInfo("GOPATH restored to ". $GOPATH)
return return
endif endif
echon "vim-go: " | echohl Function | echon "GOPATH changed to ". a:1 | echohl None call go#util#EchoInfo("GOPATH changed to ". a:1)
let s:initial_go_path = $GOPATH let s:initial_go_path = $GOPATH
let $GOPATH = a:1 let $GOPATH = a:1
endfunction endfunction

View file

@ -4,7 +4,7 @@ set cpo&vim
function! go#play#Share(count, line1, line2) abort function! go#play#Share(count, line1, line2) abort
if !executable('curl') if !executable('curl')
echohl ErrorMsg | echomsg "vim-go: require 'curl' command" | echohl None call go#util#EchoError('cannot share: curl cannot be found')
return return
endif endif
@ -20,8 +20,7 @@ function! go#play#Share(count, line1, line2) abort
call delete(share_file) call delete(share_file)
if l:err != 0 if l:err != 0
echom 'A error has occurred. Run this command to see what the problem is:' call go#util#EchoError(['A error has occurred. Run this command to see what the problem is:', go#util#Shelljoin(l:cmd)])
echom go#util#Shelljoin(l:cmd)
return return
endif endif
@ -38,7 +37,7 @@ function! go#play#Share(count, line1, line2) abort
call go#util#OpenBrowser(url) call go#util#OpenBrowser(url)
endif endif
echo "vim-go: snippet uploaded: ".url call go#util#EchoInfo('snippet uploaded: ' . url)
endfunction endfunction

View file

@ -0,0 +1,58 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
" New returns a promise. A promise's primary purpose is to make async jobs
" synchronous by awaiting fn.
"
" A promise is a dictionary with two keys:
" 'wrapper':
" A function that wraps fn. It can be used in place of fn.
" 'await':
" A function that waits for wrapper to be called and returns the value
" returned by fn. Returns default if timeout expires.
function! go#promise#New(fn, timeout, default) abort
let l:state = {}
" explicitly bind to state so that within l:promise's methods, self will
" always refer to state. See :help Partial for more information.
return {
\ 'wrapper': function('s:wrapper', [a:fn, a:default], l:state),
\ 'await': function('s:await', [a:timeout, a:default], l:state),
\ }
endfunction
function! s:wrapper(fn, default, ...) dict
try
let self.retval = call(a:fn, a:000)
catch
let self.retval = substitute(v:exception, '^Vim', '', '')
let self.exception = 1
endtry
return self.retval
endfunction
function! s:await(timeout, default) dict
let l:timer = timer_start(a:timeout, function('s:setretval', [a:default], self))
while !has_key(self, 'retval')
sleep 50m
endwhile
call timer_stop(l:timer)
if get(self, 'exception', 0)
throw self.retval
endif
return self.retval
endfunction
function! s:setretval(val, timer) dict
let self.retval = a:val
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,41 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
func! Test_PromiseNew() abort
let l:sut = go#promise#New(function('s:work', []), 100, -1)
call assert_true(has_key(l:sut, 'wrapper'))
call assert_true(has_key(l:sut, 'await'))
endfunc
func! Test_PromiseAwait() abort
let l:expected = 1
let l:default = -1
let l:sut = go#promise#New(function('s:work', [l:expected]), 100, l:default)
call timer_start(10, l:sut.wrapper)
let l:actual = call(l:sut.await, [])
call assert_equal(l:expected, l:actual)
endfunc
func! Test_PromiseAwait_Timeout() abort
let l:desired = 1
let l:expected = -1
let l:sut = go#promise#New(function('s:work', [l:desired]), 10, l:expected)
call timer_start(100, l:sut.wrapper)
let l:actual = call(l:sut.await, [])
call assert_equal(l:expected, l:actual)
endfunc
func! s:work(val, timer)
return a:val
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -0,0 +1,43 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
function! go#referrers#Referrers(selected) abort
let l:mode = go#config#ReferrersMode()
if l:mode == 'guru'
call go#guru#Referrers(a:selected)
return
elseif l:mode == 'gopls'
if !go#config#GoplsEnabled()
call go#util#EchoError("go_referrers_mode is 'gopls', but gopls is disabled")
endif
let [l:line, l:col] = getpos('.')[1:2]
let [l:line, l:col] = go#lsp#lsp#Position(l:line, l:col)
let l:fname = expand('%:p')
call go#lsp#Referrers(l:fname, l:line, l:col, funcref('s:parse_output'))
return
else
call go#util#EchoWarning('unknown value for g:go_referrers_mode')
endif
endfunction
" This uses Vim's errorformat to parse the output and put it into a quickfix
" or locationlist.
function! s:parse_output(exit_val, output, title) abort
if a:exit_val
call go#util#EchoError(a:output)
return
endif
let errformat = ",%f:%l:%c:\ %m"
let l:listtype = go#list#Type("GoReferrers")
call go#list#ParseFormat(l:listtype, errformat, a:output, a:title, 0)
let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors))
endfunction
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -20,8 +20,10 @@ function! go#rename#Rename(bang, ...) abort
let to_identifier = a:1 let to_identifier = a:1
endif endif
let l:bin = go#config#RenameCommand()
" return with a warning if the bin doesn't exist " return with a warning if the bin doesn't exist
let bin_path = go#path#CheckBinPath(go#config#GorenameBin()) let bin_path = go#path#CheckBinPath(l:bin)
if empty(bin_path) if empty(bin_path)
return return
endif endif
@ -29,7 +31,18 @@ function! go#rename#Rename(bang, ...) abort
let fname = expand('%:p') let fname = expand('%:p')
let pos = go#util#OffsetCursor() let pos = go#util#OffsetCursor()
let offset = printf('%s:#%d', fname, pos) let offset = printf('%s:#%d', fname, pos)
let cmd = [bin_path, "-offset", offset, "-to", to_identifier, '-tags', go#config#BuildTags()]
let args = []
if l:bin == 'gorename'
let l:args = extend(l:args, ['-tags', go#config#BuildTags(), '-offset', offset, '-to', to_identifier])
elseif l:bin == 'gopls'
" TODO(bc): use -tags when gopls supports it
let l:args = extend(l:args, ['rename', '-w', l:offset, to_identifier])
else
call go#util#EchoWarning('unexpected rename command')
endif
let l:cmd = extend([l:bin_path], l:args)
if go#util#has_job() if go#util#has_job()
call s:rename_job({ call s:rename_job({
@ -39,7 +52,12 @@ function! go#rename#Rename(bang, ...) abort
return return
endif endif
let [l:out, l:err] = go#util#ExecInDir(l:cmd) let l:wd = go#util#ModuleRoot()
if l:wd == -1
let l:wd = expand("%:p:h")
endif
let [l:out, l:err] = go#util#ExecInWorkDir(l:cmd, l:wd)
call s:parse_errors(l:err, a:bang, split(l:out, '\n')) call s:parse_errors(l:err, a:bang, split(l:out, '\n'))
endfunction endfunction
@ -54,6 +72,11 @@ function s:rename_job(args)
call go#cmd#autowrite() call go#cmd#autowrite()
let l:cbs = go#job#Options(l:job_opts) let l:cbs = go#job#Options(l:job_opts)
let l:wd = go#util#ModuleRoot()
if l:wd != -1
let l:cbs.cwd = l:wd
endif
" wrap l:cbs.exit_cb in s:exit_cb. " wrap l:cbs.exit_cb in s:exit_cb.
let l:cbs.exit_cb = funcref('s:exit_cb', [l:cbs.exit_cb]) let l:cbs.exit_cb = funcref('s:exit_cb', [l:cbs.exit_cb])

View file

@ -27,9 +27,8 @@ let s:last_status = ""
" if not it returns an empty string. This function should be plugged directly " if not it returns an empty string. This function should be plugged directly
" into the statusline. " into the statusline.
function! go#statusline#Show() abort function! go#statusline#Show() abort
" lazy initialiation of the cleaner " lazy initialization of the cleaner
if !s:timer_id if !s:timer_id
" clean every 60 seconds all statuses
let interval = go#config#StatuslineDuration() let interval = go#config#StatuslineDuration()
let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1}) let s:timer_id = timer_start(interval, function('go#statusline#Clear'), {'repeat': -1})
endif endif
@ -57,9 +56,9 @@ function! go#statusline#Show() abort
" only update highlight if status has changed. " only update highlight if status has changed.
if status_text != s:last_status if status_text != s:last_status
if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass" if status.state =~ "success" || status.state =~ "finished" || status.state =~ "pass" || status.state =~ 'initialized'
hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22 guibg=#5fd700 guifg=#005f00 hi goStatusLineColor cterm=bold ctermbg=76 ctermfg=22 guibg=#5fd700 guifg=#005f00
elseif status.state =~ "started" || status.state =~ "analysing" || status.state =~ "compiling" elseif status.state =~ "started" || status.state =~ "analysing" || status.state =~ "compiling" || status.state =~ 'initializing'
hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88 guibg=#ff8700 guifg=#870000 hi goStatusLineColor cterm=bold ctermbg=208 ctermfg=88 guibg=#ff8700 guifg=#870000
elseif status.state =~ "failed" elseif status.state =~ "failed"
hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52 guibg=#ff0000 guifg=#5f0000 hi goStatusLineColor cterm=bold ctermbg=196 ctermfg=52 guibg=#ff0000 guifg=#5f0000
@ -83,10 +82,11 @@ function! go#statusline#Update(status_dir, status) abort
" before we stop the timer, check if we have any previous jobs to be cleaned " before we stop the timer, check if we have any previous jobs to be cleaned
" up. Otherwise every job will reset the timer when this function is called " up. Otherwise every job will reset the timer when this function is called
" and thus old jobs will never be cleaned " and thus old jobs will never be cleaned
call go#statusline#Clear(0) call s:clear()
" also reset the timer, so the user has time to see it in the statusline. " also reset the timer, so the user has time to see it in the statusline.
" Setting the timer_id to 0 will trigger a new cleaner routine. " Setting the timer_id to 0 will cause a new timer to be created the next
" time the go#statusline#Show() is called.
call timer_stop(s:timer_id) call timer_stop(s:timer_id)
let s:timer_id = 0 let s:timer_id = 0
endfunction endfunction
@ -94,6 +94,10 @@ endfunction
" Clear clears all currently stored statusline data. The timer_id argument is " Clear clears all currently stored statusline data. The timer_id argument is
" just a placeholder so we can pass it to a timer_start() function if needed. " just a placeholder so we can pass it to a timer_start() function if needed.
function! go#statusline#Clear(timer_id) abort function! go#statusline#Clear(timer_id) abort
call s:clear()
endfunction
function! s:clear()
for [status_dir, status] in items(s:statuses) for [status_dir, status] in items(s:statuses)
let elapsed_time = reltimestr(reltime(status.created_at)) let elapsed_time = reltimestr(reltime(status.created_at))
" strip whitespace " strip whitespace

View file

@ -98,7 +98,7 @@ func s:write_out(out) abort
if has_key(result, 'errors') if has_key(result, 'errors')
let l:winnr = winnr() let l:winnr = winnr()
let l:listtype = go#list#Type("GoModifyTags") let l:listtype = go#list#Type("GoModifyTags")
call go#list#ParseFormat(l:listtype, "%f:%l:%c:%m", result['errors'], "gomodifytags") call go#list#ParseFormat(l:listtype, "%f:%l:%c:%m", result['errors'], "gomodifytags", 0)
call go#list#Window(l:listtype, len(result['errors'])) call go#list#Window(l:listtype, len(result['errors']))
"prevent jumping to quickfix list "prevent jumping to quickfix list
@ -124,6 +124,7 @@ func s:create_cmd(args) abort
let l:mode = a:args.mode let l:mode = a:args.mode
let l:cmd_args = a:args.cmd_args let l:cmd_args = a:args.cmd_args
let l:modifytags_transform = go#config#AddtagsTransform() let l:modifytags_transform = go#config#AddtagsTransform()
let l:modifytags_skip_unexported = go#config#AddtagsSkipUnexported()
" start constructing the command " start constructing the command
let cmd = [bin_path] let cmd = [bin_path]
@ -131,6 +132,10 @@ func s:create_cmd(args) abort
call extend(cmd, ["-file", a:args.fname]) call extend(cmd, ["-file", a:args.fname])
call extend(cmd, ["-transform", l:modifytags_transform]) call extend(cmd, ["-transform", l:modifytags_transform])
if l:modifytags_skip_unexported
call extend(cmd, ["-skip-unexported"])
endif
if has_key(a:args, "modified") if has_key(a:args, "modified")
call add(cmd, "-modified") call add(cmd, "-modified")
endif endif

View file

@ -24,8 +24,13 @@ function! go#template#create() abort
else else
let l:template_file = go#config#TemplateFile() let l:template_file = go#config#TemplateFile()
endif endif
let l:template_path = go#util#Join(l:root_dir, "templates", l:template_file) " If template_file is an absolute path, use it as-is. This is to support
silent exe 'keepalt 0r ' . fnameescape(l:template_path) " overrides pointing to templates outside of the vim-go plugin dir
if fnamemodify(l:template_file, ':p') != l:template_file
let l:template_file = go#util#Join(l:root_dir, "templates", l:template_file)
endif
silent exe 'keepalt 0r ' . fnameescape(l:template_file)
endif endif
else else
let l:content = printf("package %s", l:package_name) let l:content = printf("package %s", l:package_name)

View file

@ -2,6 +2,8 @@
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
let s:bufnameprefix = 'goterm://'
" new creates a new terminal with the given command. Mode is set based on the " new creates a new terminal with the given command. Mode is set based on the
" global variable g:go_term_mode, which is by default set to :vsplit " global variable g:go_term_mode, which is by default set to :vsplit
function! go#term#new(bang, cmd, errorformat) abort function! go#term#new(bang, cmd, errorformat) abort
@ -15,6 +17,10 @@ function! go#term#newmode(bang, cmd, errorformat, mode) abort
let l:mode = go#config#TermMode() let l:mode = go#config#TermMode()
endif endif
if go#config#TermReuse()
call s:closeterm()
endif
let l:state = { let l:state = {
\ 'cmd': a:cmd, \ 'cmd': a:cmd,
\ 'bang' : a:bang, \ 'bang' : a:bang,
@ -24,55 +30,96 @@ function! go#term#newmode(bang, cmd, errorformat, mode) abort
\ 'errorformat': a:errorformat, \ 'errorformat': a:errorformat,
\ } \ }
" execute go build in the files directory " execute the command in the current file's directory
let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let l:dir = go#util#Chdir(expand('%:p:h'))
let l:dir = getcwd()
execute l:cd . fnameescape(expand("%:p:h"))
execute l:mode . ' __go_term__' execute l:mode . ' __go_term__'
setlocal filetype=goterm setlocal filetype=goterm
setlocal bufhidden=delete setlocal bufhidden=delete
setlocal winfixheight setlocal winfixheight
" TODO(bc)?: setlocal winfixwidth
setlocal noswapfile setlocal noswapfile
setlocal nobuflisted setlocal nobuflisted
" explicitly bind callbacks to state so that within them, self will always " setup job for nvim
" refer to state. See :help Partial for more information. if has('nvim')
" " explicitly bind callbacks to state so that within them, self will always
" Don't set an on_stderr, because it will be passed the same data as " refer to state. See :help Partial for more information.
" on_stdout. See https://github.com/neovim/neovim/issues/2836 "
let l:job = { " Don't set an on_stderr, because it will be passed the same data as
\ 'on_stdout': function('s:on_stdout', [], state), " on_stdout. See https://github.com/neovim/neovim/issues/2836
\ 'on_exit' : function('s:on_exit', [], state), let l:job = {
\ } \ 'on_stdout': function('s:on_stdout', [], state),
\ 'on_exit' : function('s:on_exit', [], state),
\ }
let l:state.id = termopen(a:cmd, l:job)
let l:state.termwinid = win_getid(winnr())
let s:lasttermwinid = l:state.termwinid
call go#util#Chdir(l:dir)
let l:state.id = termopen(a:cmd, l:job) " resize new term if needed.
let l:state.termwinid = win_getid(winnr()) let l:height = go#config#TermHeight()
let l:width = go#config#TermWidth()
execute l:cd . fnameescape(l:dir) " Adjust the window width or height depending on whether it's a vertical or
" horizontal split.
if l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew"
exe 'vertical resize ' . l:width
elseif mode =~ "split" || mode =~ "new"
exe 'resize ' . l:height
endif
" we also need to resize the pty, so there you go...
call jobresize(l:state.id, l:width, l:height)
" resize new term if needed. " setup term for vim8
let l:height = go#config#TermHeight() elseif has('terminal')
let l:width = go#config#TermWidth() " Not great randomness, but "good enough" for our purpose here.
let l:rnd = sha256(printf('%s%s', reltimestr(reltime()), fnamemodify(bufname(''), ":p")))
let l:termname = printf("%s%s", s:bufnameprefix, l:rnd)
" Adjust the window width or height depending on whether it's a vertical or let l:term = {
" horizontal split. \ 'out_cb': function('s:out_cb', [], state),
if l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew" \ 'exit_cb' : function('s:exit_cb', [], state),
exe 'vertical resize ' . l:width \ 'curwin': 1,
elseif mode =~ "split" || mode =~ "new" \ 'term_name': l:termname,
exe 'resize ' . l:height \ }
if l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew"
let l:term["vertical"] = l:mode
endif
let l:state.id = term_start(a:cmd, l:term)
let l:state.termwinid = win_getid(bufwinnr(l:state.id))
let s:lasttermwinid = l:state.termwinid
call go#util#Chdir(l:dir)
" resize new term if needed.
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 l:mode =~ "vertical" || l:mode =~ "vsplit" || l:mode =~ "vnew"
exe 'vertical resize ' . l:width
elseif mode =~ "split" || mode =~ "new"
exe 'resize ' . l:height
endif
"if exists(*term_setsize)
"call term_setsize(l:state.id, l:height, l:width)
"endif
endif endif
" we also need to resize the pty, so there you go...
call jobresize(l:state.id, l:width, l:height)
call win_gotoid(l:state.winid) call win_gotoid(l:state.winid)
return l:state.id return l:state.id
endfunction endfunction
" out_cb continually concat's the self.stdout_buf on recv of stdout
" and sets self.stdout to the new-lined split content in self.stdout_buf
func! s:out_cb(channel, msg) dict abort
let self.stdout_buf = self.stdout_buf . a:msg
let self.stdout = split(self.stdout_buf, '\n')
endfunction
function! s:on_stdout(job_id, data, event) dict abort function! s:on_stdout(job_id, data, event) dict abort
" A single empty string means EOF was reached. The first item will never be " 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 " the empty string except for when it's the only item and is signaling that
@ -95,9 +142,21 @@ function! s:on_stdout(job_id, data, event) dict abort
endif endif
endfunction endfunction
" vim8 exit callback
function! s:exit_cb(job_id, exit_status) dict abort
call s:handle_exit(a:job_id, a:exit_status, self)
endfunction
" nvim exit callback
function! s:on_exit(job_id, exit_status, event) dict abort function! s:on_exit(job_id, exit_status, event) dict abort
call s:handle_exit(a:job_id, a:exit_status, self)
endfunction
" handle_exit implements both vim8 and nvim exit callbacks
func s:handle_exit(job_id, exit_status, state) abort
let l:winid = win_getid(winnr()) let l:winid = win_getid(winnr())
call win_gotoid(self.winid) call win_gotoid(a:state.winid)
let l:listtype = go#list#Type("_term") let l:listtype = go#list#Type("_term")
if a:exit_status == 0 if a:exit_status == 0
@ -106,40 +165,106 @@ function! s:on_exit(job_id, exit_status, event) dict abort
return return
endif endif
call win_gotoid(self.winid) let l:bufdir = expand('%:p:h')
if !isdirectory(l:bufdir)
call go#util#EchoWarning('terminal job failure not processed, because the job''s working directory no longer exists')
call win_gotoid(l:winid)
return
endif
let l:title = self.cmd " change to directory where the command was run. If we do not do this the
" quickfix items will have the incorrect paths.
" see: https://github.com/fatih/vim-go/issues/2400
let l:dir = go#util#Chdir(l:bufdir)
let l:title = a:state.cmd
if type(l:title) == v:t_list if type(l:title) == v:t_list
let l:title = join(self.cmd) let l:title = join(a:state.cmd)
endif endif
let l:i = 0 let l:i = 0
while l:i < len(self.stdout) while l:i < len(a:state.stdout)
let self.stdout[l:i] = substitute(self.stdout[l:i], "\r$", '', 'g') let a:state.stdout[l:i] = substitute(a:state.stdout[l:i], "\r$", '', 'g')
let l:i += 1 let l:i += 1
endwhile endwhile
call go#list#ParseFormat(l:listtype, self.errorformat, self.stdout, l:title) call go#list#ParseFormat(l:listtype, a:state.errorformat, a:state.stdout, l:title, 0)
let l:errors = go#list#Get(l:listtype) let l:errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(l:errors)) call go#list#Window(l:listtype, len(l:errors))
" close terminal; we don't need it anymore
if go#config#TermCloseOnExit()
call win_gotoid(a:state.termwinid)
close!
endif
if empty(l:errors) if empty(l:errors)
call go#util#EchoError( '[' . l:title . '] ' . "FAIL") call go#util#EchoError( '[' . l:title . '] ' . "FAIL")
call go#util#Chdir(l:dir)
call win_gotoid(l:winid) call win_gotoid(l:winid)
return return
endif endif
" close terminal; we don't need it anymore if a:state.bang
call win_gotoid(self.termwinid) call go#util#Chdir(l:dir)
close!
if self.bang
call win_gotoid(l:winid) call win_gotoid(l:winid)
return return
endif endif
call win_gotoid(self.winid) call win_gotoid(a:state.winid)
call go#list#JumpToFirst(l:listtype) call go#list#JumpToFirst(l:listtype)
" change back to original working directory
call go#util#Chdir(l:dir)
endfunction
function! go#term#ToggleCloseOnExit() abort
if go#config#TermCloseOnExit()
call go#config#SetTermCloseOnExit(0)
call go#util#EchoProgress("term close on exit disabled")
return
endif
call go#config#SetTermCloseOnExit(1)
call go#util#EchoProgress("term close on exit enabled")
return
endfunction
function! s:closeterm()
if !exists('s:lasttermwinid')
return
endif
try
let l:termwinid = s:lasttermwinid
unlet s:lasttermwinid
let l:info = getwininfo(l:termwinid)
if empty(l:info)
return
endif
let l:info = l:info[0]
if !get(l:info, 'terminal', 0) is 1
return
endif
if has('nvim')
if 'goterm' == nvim_buf_get_option(nvim_win_get_buf(l:termwinid), 'filetype')
call nvim_win_close(l:termwinid, v:true)
endif
return
endif
if stridx(bufname(winbufnr(l:termwinid)), s:bufnameprefix, 0) == 0
let l:winid = win_getid()
call win_gotoid(l:termwinid)
close!
call win_gotoid(l:winid)
endif
catch
call go#util#EchoError(printf("vim-go: %s", v:exception))
endtry
endfunction endfunction
" restore Vi compatibility settings " restore Vi compatibility settings

View file

@ -3,7 +3,7 @@ let s:cpo_save = &cpo
set cpo&vim set cpo&vim
func! Test_GoTermNewMode() func! Test_GoTermNewMode()
if !has('nvim') if !(has('nvim') || has('terminal'))
return return
endif endif
@ -22,12 +22,13 @@ func! Test_GoTermNewMode()
call assert_equal(actual, l:expected) call assert_equal(actual, l:expected)
finally finally
sleep 50m
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
endtry endtry
endfunc endfunc
func! Test_GoTermNewMode_SplitRight() func! Test_GoTermNewMode_SplitRight()
if !has('nvim') if !(has('nvim') || has('terminal'))
return return
endif endif
@ -46,11 +47,46 @@ func! Test_GoTermNewMode_SplitRight()
call assert_equal(actual, l:expected) call assert_equal(actual, l:expected)
finally finally
sleep 50m
call delete(l:tmp, 'rf') call delete(l:tmp, 'rf')
set nosplitright set nosplitright
endtry endtry
endfunc endfunc
func! Test_GoTermReuse()
if !(has('nvim') || has('terminal'))
return
endif
try
let l:filename = 'term/term.go'
let l:tmp = gotest#load_fixture(l:filename)
exe 'cd ' . l:tmp . '/src/term'
let expected = expand('%:p')
let cmd = "go run ". go#util#Shelljoin(go#tool#Files())
set nosplitright
let g:go_term_reuse = 1
call go#term#new(0, cmd, &errorformat)
let actual = expand('%:p')
call assert_equal(actual, l:expected)
call assert_equal(3, len(getwininfo()))
call go#term#new(0, cmd, &errorformat)
let actual = expand('%:p')
call assert_equal(actual, l:expected)
call assert_equal(3, len(getwininfo()))
finally
sleep 50m
unlet g:go_term_reuse
call delete(l:tmp, 'rf')
endtry
endfunc
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -0,0 +1,5 @@
package config
func Example() {
foo()
}

View file

@ -0,0 +1,8 @@
// +build constrained
package config
// foo is constrained and this comment exists to make the line numbers different than foo.go
func foo() {
println("foo")
}

View file

@ -0,0 +1,7 @@
// +build !constrained
package config
func foo() {
println("foo")
}

View file

@ -0,0 +1,3 @@
module config
go 1.13

View file

@ -1,13 +0,0 @@
package main
import (
"fmt"
)
func Foo(log *logging.TestLogger) {
log.Debug("vim-go")
}
func main() {
fmt.Println("vim-go")
}

View file

@ -1,15 +0,0 @@
package main
import (
"fmt"
logging "gh.com/gi/foo-logging"
)
func Foo(log *logging.TestLogger) {
log.Debug("vim-go")
}
func main() {
fmt.Println("vim-go")
}

View file

@ -1,12 +0,0 @@
package logging
import "fmt"
type TestLogger struct {
Value string
}
func (l *TestLogger) Debug(msg string) {
fmt.Println(msg)
fmt.Println(l.Value)
}

View file

@ -0,0 +1,6 @@
package main
func main() {
notafunc()
println("vim-go")
}

View file

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

View file

@ -0,0 +1,10 @@
package errcheck
import (
"io"
"os"
)
func foo() {
io.Copy(os.Stdout, os.Stdin)
}

View file

@ -0,0 +1,11 @@
package errcheck
import (
"io"
"os"
"testing"
)
func TestFoo(t *testing.T) {
io.Copy(os.Stdout, os.Stdin)
}

View file

@ -0,0 +1,3 @@
package lint
func baz() {}

View file

@ -0,0 +1,3 @@
package problems
func bar() {}

View file

@ -0,0 +1,5 @@
package problems
import "/quux"
func baz() {}

View file

@ -0,0 +1,9 @@
package problems
import (
"time"
)
func mySleep(time int) {
time.Sleep(500 * time.Millisecond)
}

View file

@ -0,0 +1,5 @@
package problems
func mySleep(time int) {
time.Sleep(500 * time.Millisecond)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
package main
import (
"fmt"
)
func main() {
io.Copy(ioutil.Discard, os.Stdin)
fmt.Println("vim-go")
}

View file

@ -0,0 +1,13 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"os"
)
func main() {
io.Copy(ioutil.Discard, os.Stdin)
fmt.Println("vim-go")
}

View file

@ -0,0 +1,10 @@
package main
import (
"fmt"
)
func ExampleHelloWorld() {
fmt.Println("Hello, World")
// Output: What's shakin
}

View file

@ -6,7 +6,10 @@ set cpo&vim
" compile the tests instead of running them (useful to catch errors in the " compile the tests instead of running them (useful to catch errors in the
" test files). Any other argument is appended to the final `go test` command. " test files). Any other argument is appended to the final `go test` command.
function! go#test#Test(bang, compile, ...) abort function! go#test#Test(bang, compile, ...) abort
let args = ["test", '-tags', go#config#BuildTags()] let args = ["test"]
if len(go#config#BuildTags()) > 0
call extend(args, ["-tags", go#config#BuildTags()])
endif
" don't run the test, only compile it. Useful to capture and fix errors. " don't run the test, only compile it. Useful to capture and fix errors.
if a:compile if a:compile
@ -32,6 +35,7 @@ function! go#test#Test(bang, compile, ...) abort
if go#config#TermEnabled() if go#config#TermEnabled()
call go#term#new(a:bang, ["go"] + args, s:errorformat()) call go#term#new(a:bang, ["go"] + args, s:errorformat())
return
endif endif
if go#util#has_job() if go#util#has_job()
@ -76,7 +80,7 @@ function! go#test#Test(bang, compile, ...) abort
if l:err != 0 if l:err != 0
let l:winid = win_getid(winnr()) let l:winid = win_getid(winnr())
call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), l:cmd) call go#list#ParseFormat(l:listtype, s:errorformat(), split(out, '\n'), l:cmd, 0)
let errors = go#list#Get(l:listtype) let errors = go#list#Get(l:listtype)
call go#list#Window(l:listtype, len(errors)) call go#list#Window(l:listtype, len(errors))
if empty(errors) if empty(errors)
@ -166,9 +170,17 @@ function! s:errorformat() abort
let format .= ",%-G" . indent . "%#--- PASS: %.%#" let format .= ",%-G" . indent . "%#--- PASS: %.%#"
" Match failure lines. " Match failure lines.
"
" Example failures start with '--- FAIL: ', followed by the example name
" followed by a space , followed by the duration of the example in
" parantheses. They aren't nested, though, so don't check for indentation.
" The errors from them also aren't indented and don't report file location
" or line numbers, so those won't show up. This will at least let the user
" know which example failed, though.
let format .= ',%G--- FAIL: %\\%(Example%\\)%\\@=%m (%.%#)'
" Test failures start with '--- FAIL: ', followed by the test name followed " Test failures start with '--- FAIL: ', followed by the test name followed
" by a space the duration of the test in parentheses " by a space, followed by the duration of the test in parentheses.
" "
" e.g.: " e.g.:
" '--- FAIL: TestSomething (0.00s)' " '--- FAIL: TestSomething (0.00s)'
@ -207,6 +219,14 @@ function! s:errorformat() abort
let format .= ",%G" . indent . "%#%\\t%\\{2}%m" let format .= ",%G" . indent . "%#%\\t%\\{2}%m"
" }}}1 " }}}1
" Go 1.14 test verbose output {{{1
" Match test output lines similarly to Go 1.11 test output lines, but they
" have the test name followed by a colon before the filename when run with
" the -v flag.
let format .= ",%A" . indent . "%\\+%[%^:]%\\+: %f:%l: %m"
let format .= ",%A" . indent . "%\\+%[%^:]%\\+: %f:%l: "
" }}}1
" Go 1.11 test output {{{1 " Go 1.11 test output {{{1
" Match test output lines similarly to Go 1.10 test output lines, but they " 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 " use an indent level where the Go 1.10 test output uses tabs, so they'll

View file

@ -66,9 +66,9 @@ endfunc
func! Test_GoTestShowName() abort func! Test_GoTestShowName() abort
let expected = [ let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld'},
\ {'lnum': 6, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'}, \ {'lnum': 6, 'bufnr': 8, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'so long'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld/sub'}, \ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'TestHelloWorld/sub'},
\ {'lnum': 9, 'bufnr': 7, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'}, \ {'lnum': 9, 'bufnr': 8, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'thanks for all the fish'},
\ ] \ ]
let g:go_test_show_name=1 let g:go_test_show_name=1
@ -78,20 +78,27 @@ endfunc
func! Test_GoTestVet() abort func! Test_GoTestVet() abort
let expected = [ 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'}, \ {'lnum': 6, 'bufnr': 11, '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) call s:test('veterror/veterror.go', expected)
endfunc endfunc
func! Test_GoTestTestCompilerError() abort func! Test_GoTestTestCompilerError() abort
let expected = [ 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': 10, 'bufnr': 9, '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)'} \ {'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) call s:test('testcompilerror/testcompilerror_test.go', expected)
endfunc endfunc
func! Test_GoTestExample() abort
let expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'ExampleHelloWorld'}
\ ]
call s:test('example/example_test.go', expected)
endfunc
func! s:test(file, expected, ...) abort func! s:test(file, expected, ...) abort
let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test' let $GOPATH = fnameescape(fnamemodify(getcwd(), ':p')) . 'test-fixtures/test'
silent exe 'e ' . $GOPATH . '/src/' . a:file silent exe 'e ' . $GOPATH . '/src/' . a:file

View file

@ -82,14 +82,16 @@ endfunction
function! go#tool#Info(showstatus) abort function! go#tool#Info(showstatus) abort
let l:mode = go#config#InfoMode() let l:mode = go#config#InfoMode()
if l:mode == 'gocode' if l:mode == 'guru'
call go#complete#Info(a:showstatus)
elseif l:mode == 'guru'
call go#guru#DescribeInfo(a:showstatus) call go#guru#DescribeInfo(a:showstatus)
elseif l:mode == 'gopls' elseif l:mode == 'gopls'
if !go#config#GoplsEnabled()
call go#util#EchoError("go_info_mode is 'gopls', but gopls is disabled")
return
endif
call go#lsp#Info(a:showstatus) call go#lsp#Info(a:showstatus)
else else
call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [gocode, guru, gopls]') call go#util#EchoError('go_info_mode value: '. l:mode .' is not valid. Valid values are: [guru, gopls]')
endif endif
endfunction endfunction
@ -115,7 +117,15 @@ endfunction
function! go#tool#DescribeBalloon() function! go#tool#DescribeBalloon()
let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p') let l:fname = fnamemodify(bufname(v:beval_bufnr), ':p')
call go#lsp#Hover(l:fname, v:beval_lnum, v:beval_col, funcref('s:balloon', []))
let l:winid = win_getid()
call win_gotoid(bufwinid(v:beval_bufnr))
let [l:line, l:col] = go#lsp#lsp#Position(v:beval_lnum, v:beval_col)
call go#lsp#Hover(l:fname, l:line, l:col, funcref('s:balloon', []))
call win_gotoid(l:winid)
return '' return ''
endfunction endfunction

View file

@ -18,7 +18,7 @@ function! s:encode(value, unreserved)
return substitute( return substitute(
\ a:value, \ a:value,
\ a:unreserved, \ a:unreserved,
\ '\="%".printf(''%02X'', char2nr(submatch(0)))', \ '\=s:encodechar(submatch(0))',
\ 'g' \ 'g'
\) \)
endfunction endfunction
@ -27,10 +27,27 @@ function! go#uri#Decode(value) abort
return substitute( return substitute(
\ a:value, \ a:value,
\ '%\(\x\x\)', \ '%\(\x\x\)',
\ '\=nr2char(''0X'' . submatch(1))', \ '\=s:decodehex(submatch(1))',
\ 'g' \ 'g'
\) \)
endfunction endfunction
function! s:encodechar(value)
let l:idx = 0
let l:encoded = ''
while l:idx < strlen(a:value)
let l:byte = strpart(a:value, l:idx, 1)
let l:encoded = printf('%s%%%02X', l:encoded, char2nr(l:byte))
let l:idx += 1
endwhile
return l:encoded
endfunction
function! s:decodehex(value)
return eval(printf('"\x%s"', a:value))
endfunction
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -0,0 +1,56 @@
" don't spam the user when Vim is started in Vi compatibility mode
let s:cpo_save = &cpo
set cpo&vim
scriptencoding utf-8
func! Test_EncodePath_simple() abort
let l:uri = '/simple/foo'
let l:expected = '/simple/foo'
let l:actual = go#uri#EncodePath(l:uri)
call assert_equal(l:expected, l:actual)
endfunc
func! Test_EncodePath_multibyte() abort
let l:uri = '/multi-byte/⌘⌘'
let l:expected = '/multi-byte/%E2%8C%98%E2%8C%98'
let l:actual = go#uri#EncodePath(l:uri)
call assert_equal(l:expected, l:actual)
endfunc
func! Test_Decode_simple() abort
let l:uri = '/simple/foo'
let l:expected = '/simple/foo'
let l:actual = go#uri#Decode(l:uri)
call assert_equal(l:expected, l:actual)
endfunc
func! Test_Decode_multibyte() abort
let l:uri = '/multi-byte/%E2%8C%98%E2%8C%98'
let l:expected = '/multi-byte/⌘⌘'
let l:actual = go#uri#Decode(l:uri)
call assert_equal(l:expected, l:actual)
endfunc
func! Test_Roundtrip_simple() abort
let l:expected = '/simple/foo'
let l:actual = go#uri#Decode(go#uri#EncodePath(l:expected))
call assert_equal(l:expected, l:actual)
endfunc
func! Test_Roundtrip_multibyte() abort
let l:expected = '/multi-byte/⌘⌘'
let l:actual = go#uri#Decode(go#uri#EncodePath(l:expected))
call assert_equal(l:expected, l:actual)
endfunc
" restore Vi compatibility settings
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: sw=2 ts=2 et

View file

@ -36,15 +36,9 @@ function! go#util#Join(...) abort
endfunction endfunction
" IsWin returns 1 if current OS is Windows or 0 otherwise " IsWin returns 1 if current OS is Windows or 0 otherwise
" Note that has('win32') is always 1 when has('win64') is 1, so has('win32') is enough.
function! go#util#IsWin() abort function! go#util#IsWin() abort
let win = ['win16', 'win32', 'win64', 'win95'] return has('win32')
for w in win
if (has(w))
return 1
endif
endfor
return 0
endfunction endfunction
" IsMac returns 1 if current OS is macOS or 0 otherwise. " IsMac returns 1 if current OS is macOS or 0 otherwise.
@ -68,18 +62,7 @@ endfunction
" The (optional) first parameter can be added to indicate the 'cwd' or 'env' " 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. " parameters will be used, which wasn't added until a later version.
function! go#util#has_job(...) abort function! go#util#has_job(...) abort
if has('nvim') return has('job') || 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")
endfunction endfunction
let s:env_cache = {} let s:env_cache = {}
@ -137,9 +120,39 @@ function! go#util#gomod() abort
return substitute(s:exec(['go', 'env', 'GOMOD'])[0], '\n', '', 'g') return substitute(s:exec(['go', 'env', 'GOMOD'])[0], '\n', '', 'g')
endfunction endfunction
" gomodcache returns 'go env GOMODCACHE'. Instead use 'go#util#env("gomodcache")'
function! go#util#gomodcache() abort
return substitute(s:exec(['go', 'env', 'GOMODCACHE'])[0], '\n', '', 'g')
endfunction
function! go#util#osarch() abort " hostosarch returns the OS and ARCH values that the go binary is intended for.
return go#util#env("goos") . '_' . go#util#env("goarch") function! go#util#hostosarch() abort
let [l:hostos, l:err] = s:exec(['go', 'env', 'GOHOSTOS'])
let [l:hostarch, l:err] = s:exec(['go', 'env', 'GOHOSTARCH'])
return [substitute(l:hostos, '\n', '', 'g'), substitute(l:hostarch, '\n', '', 'g')]
endfunction
" go#util#ModuleRoot returns the root directory of the module of the current
" buffer.
function! go#util#ModuleRoot() abort
let [l:out, l:err] = go#util#ExecInDir(['go', 'env', 'GOMOD'])
if l:err != 0
return -1
endif
let l:module = split(l:out, '\n', 1)[0]
" When run with `GO111MODULE=on and not in a module directory, the module will be reported as /dev/null.
let l:fakeModule = '/dev/null'
if go#util#IsWin()
let l:fakeModule = 'NUL'
endif
if l:fakeModule == l:module
return expand('%:p:h')
endif
return resolve(fnamemodify(l:module, ':p:h'))
endfunction endfunction
" Run a shell command. " Run a shell command.
@ -157,6 +170,13 @@ function! s:system(cmd, ...) abort
set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c set shell=/bin/sh shellredir=>%s\ 2>&1 shellcmdflag=-c
endif endif
if go#util#IsWin()
if executable($COMSPEC)
let &shell = $COMSPEC
set shellcmdflag=/C
endif
endif
try try
return call('system', [a:cmd] + a:000) return call('system', [a:cmd] + a:000)
finally finally
@ -196,18 +216,25 @@ function! go#util#Exec(cmd, ...) abort
return call('s:exec', [[l:bin] + a:cmd[1:]] + a:000) return call('s:exec', [[l:bin] + a:cmd[1:]] + a:000)
endfunction endfunction
" ExecInDir will execute cmd with the working directory set to the current
" buffer's directory.
function! go#util#ExecInDir(cmd, ...) abort function! go#util#ExecInDir(cmd, ...) abort
if !isdirectory(expand("%:p:h")) let l:wd = expand('%:p:h')
return call('go#util#ExecInWorkDir', [a:cmd, l:wd] + a:000)
endfunction
" ExecInWorkDir will execute cmd with the working diretory set to wd. Additional arguments will be passed
" to cmd.
function! go#util#ExecInWorkDir(cmd, wd, ...) abort
if !isdirectory(a:wd)
return ['', 1] return ['', 1]
endif endif
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' let l:dir = go#util#Chdir(a:wd)
let dir = getcwd()
try try
execute cd . fnameescape(expand("%:p:h"))
let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000) let [l:out, l:err] = call('go#util#Exec', [a:cmd] + a:000)
finally finally
execute cd . fnameescape(l:dir) call go#util#Chdir(l:dir)
endtry endtry
return [l:out, l:err] return [l:out, l:err]
endfunction endfunction
@ -446,7 +473,7 @@ function! go#util#tempdir(prefix) abort
endif endif
" Not great randomness, but "good enough" for our purpose here. " Not great randomness, but "good enough" for our purpose here.
let l:rnd = sha256(printf('%s%s', localtime(), fnamemodify(bufname(''), ":p"))) let l:rnd = sha256(printf('%s%s', reltimestr(reltime()), fnamemodify(bufname(''), ":p")))
let l:tmp = printf("%s/%s%s", l:dir, a:prefix, l:rnd) let l:tmp = printf("%s/%s%s", l:dir, a:prefix, l:rnd)
call mkdir(l:tmp, 'p', 0700) call mkdir(l:tmp, 'p', 0700)
return l:tmp return l:tmp
@ -519,6 +546,170 @@ function! go#util#ShowInfo(info)
echo "vim-go: " | echohl Function | echon a:info | echohl None echo "vim-go: " | echohl Function | echon a:info | echohl None
endfunction endfunction
" go#util#SetEnv takes the name of an environment variable and what its value
" should be and returns a function that will restore it to its original value.
function! go#util#SetEnv(name, value) abort
let l:state = {}
if len(a:name) == 0
return function('s:noop', [], l:state)
endif
let l:remove = 0
if exists('$' . a:name)
let l:oldvalue = eval('$' . a:name)
else
let l:remove = 1
endif
" wrap the value in single quotes so that it will work on windows when there
" are backslashes present in the value (e.g. $PATH).
call execute('let $' . a:name . " = '" . a:value . "'")
if l:remove
return function('s:unset', [a:name], l:state)
endif
return function('go#util#SetEnv', [a:name, l:oldvalue], l:state)
endfunction
function! go#util#ClearHighlights(group) abort
if has('textprop')
" the property type may not exist when syntax highlighting is not enabled.
if empty(prop_type_get(a:group))
return
endif
if !has('patch-8.1.1035')
return prop_remove({'type': a:group, 'all': 1}, 1, line('$'))
endif
return prop_remove({'type': a:group, 'all': 1})
endif
if exists("*matchaddpos")
return s:clear_group_from_matches(a:group)
endif
endfunction
function! s:clear_group_from_matches(group) abort
let l:cleared = 0
let m = getmatches()
for item in m
if item['group'] == a:group
call matchdelete(item['id'])
let l:cleared = 1
endif
endfor
return l:cleared
endfunction
function! s:unset(name) abort
try
" unlet $VAR was introducted in Vim 8.0.1832, which is newer than the
" minimal version that vim-go supports. Set the environment variable to
" the empty string in that case. It's not perfect, but it will work fine
" for most things, and is really the best alternative that's available.
if !has('patch-8.0.1832')
call go#util#SetEnv(a:name, '')
return
endif
call execute('unlet $' . a:name)
catch
call go#util#EchoError(printf('could not unset $%s: %s', a:name, v:exception))
endtry
endfunction
function! s:noop(...) abort dict
endfunction
" go#util#HighlightPositions highlights using text properties if possible and
" falls back to matchaddpos() if necessary. It works around matchaddpos()'s
" limit of only 8 positions per call by calling matchaddpos() with no more
" than 8 positions per call.
"
" pos should be a list of 3 element lists. The lists should be [line, col,
" length] as used by matchaddpos().
function! go#util#HighlightPositions(group, pos) abort
if has('textprop')
for l:pos in a:pos
" use a single line prop by default
let l:prop = {'type': a:group, 'length': l:pos[2]}
let l:line = getline(l:pos[0])
" l:max is the 1-based index within the buffer of the first character after l:pos.
let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1
if has('patch-8.2.115')
" Use byte2line as long as 8.2.115 (which resolved
" https://github.com/vim/vim/issues/5334) is available.
let l:end_lnum = byte2line(l:max)
" specify end line and column if needed.
if l:pos[0] != l:end_lnum
let l:end_col = l:max - line2byte(l:end_lnum)
let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col}
endif
elseif l:pos[1] + l:pos[2] - 1 > len(l:line)
let l:end_lnum = l:pos[0]
while line2byte(l:end_lnum+1) < l:max
let l:end_lnum += 1
endwhile
" l:end_col is the full length - the byte position of l:end_lnum +
" the number of newlines (number of newlines is l:end_lnum -
" l:pos[0].
let l:end_col = l:max - line2byte(l:end_lnum) + l:end_lnum - l:pos[0]
let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col}
endif
try
call prop_add(l:pos[0], l:pos[1], l:prop)
catch
" Swallow any exceptions encountered while trying to add the property
" Due to the asynchronous nature, it's possible that the buffer has
" changed since the buffer was analyzed and that the specified
" position is no longer valid.
endtry
endfor
return
endif
if exists('*matchaddpos')
return s:matchaddpos(a:group, a:pos)
endif
endfunction
" s:matchaddpos works around matchaddpos()'s limit of only 8 positions per
" call by calling matchaddpos() with no more than 8 positions per call.
function! s:matchaddpos(group, pos) abort
let l:partitions = []
let l:partitionsIdx = 0
let l:posIdx = 0
for l:pos in a:pos
if l:posIdx % 8 == 0
let l:partitions = add(l:partitions, [])
let l:partitionsIdx = len(l:partitions) - 1
endif
let l:partitions[l:partitionsIdx] = add(l:partitions[l:partitionsIdx], l:pos)
let l:posIdx = l:posIdx + 1
endfor
for l:positions in l:partitions
call matchaddpos(a:group, l:positions)
endfor
endfunction
function! go#util#Chdir(dir) abort
if !exists('*chdir')
let l:olddir = getcwd()
let cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
execute cd . fnameescape(a:dir)
return l:olddir
endif
return chdir(a:dir)
endfunction
" restore Vi compatibility settings " restore Vi compatibility settings
let &cpo = s:cpo_save let &cpo = s:cpo_save
unlet s:cpo_save unlet s:cpo_save

View file

@ -18,16 +18,23 @@ fun! gotest#write_file(path, contents) abort
call mkdir(fnamemodify(l:full_path, ':h'), 'p') call mkdir(fnamemodify(l:full_path, ':h'), 'p')
call writefile(a:contents, l:full_path) call writefile(a:contents, l:full_path)
exe 'cd ' . l:dir . '/src' call go#util#Chdir(l:dir . '/src')
silent exe 'e! ' . a:path silent exe 'e! ' . a:path
" Set cursor. " Set cursor.
let l:lnum = 1 let l:lnum = 1
for l:line in a:contents for l:line in a:contents
let l:m = match(l:line, "\x1f") let l:m = stridx(l:line, "\x1f")
if l:m > -1 if l:m > -1
call setpos('.', [0, l:lnum, l:m, 0]) let l:byte = line2byte(l:lnum) + l:m
exe 'goto '. l:byte
call setline('.', substitute(getline('.'), "\x1f", '', '')) call setline('.', substitute(getline('.'), "\x1f", '', ''))
silent noautocmd w!
call go#lsp#DidClose(expand('%:p'))
call go#lsp#DidOpen(expand('%:p'))
break break
endif endif
@ -42,15 +49,21 @@ endfun
" The file will be copied to a new GOPATH-compliant temporary directory and " The file will be copied to a new GOPATH-compliant temporary directory and
" loaded as the current buffer. " loaded as the current buffer.
fun! gotest#load_fixture(path) abort fun! gotest#load_fixture(path) abort
if go#util#has_job()
call go#lsp#CleanWorkspaces()
endif
let l:dir = go#util#tempdir("vim-go-test/testrun/") let l:dir = go#util#tempdir("vim-go-test/testrun/")
let $GOPATH .= ':' . l:dir let $GOPATH .= ':' . l:dir
let l:full_path = l:dir . '/src/' . a:path let l:full_path = l:dir . '/src/' . a:path
call mkdir(fnamemodify(l:full_path, ':h'), 'p') call mkdir(fnamemodify(l:full_path, ':h'), 'p')
exe 'cd ' . l:dir . '/src' call go#util#Chdir(l:dir . '/src')
silent exe 'noautocmd e ' . a:path silent exe 'noautocmd e! ' . a:path
silent exe printf('read %s/test-fixtures/%s', g:vim_go_root, a:path) silent exe printf('read %s/test-fixtures/%s', g:vim_go_root, a:path)
silent noautocmd w! silent noautocmd w!
if go#util#has_job()
call go#lsp#AddWorkspaceDirectory(fnamemodify(l:full_path, ':p:h'))
endif
return l:dir return l:dir
endfun endfun
@ -109,26 +122,29 @@ endfun
func! gotest#assert_quickfix(got, want) abort func! gotest#assert_quickfix(got, want) abort
call assert_equal(len(a:want), len(a:got), "number of errors") call assert_equal(len(a:want), len(a:got), "number of errors")
if len(a:want) != len(a:got) if len(a:want) != len(a:got)
call assert_equal(a:want, a:got) return assert_equal(a:want, a:got)
return
endif endif
let l:retval = 0
let i = 0 let i = 0
while i < len(a:want) while i < len(a:want)
let want_item = a:want[i] let want_item = a:want[i]
let got_item = a:got[i] let got_item = a:got[i]
let i += 1 let i += 1
call assert_equal(want_item.bufnr, got_item.bufnr, "bufnr") let l:retval = assert_equal(want_item.bufnr, got_item.bufnr, "bufnr") || l:retval
call assert_equal(want_item.lnum, got_item.lnum, "lnum") let l:retval = assert_equal(want_item.lnum, got_item.lnum, "lnum") || l:retval
call assert_equal(want_item.col, got_item.col, "col") let l:retval = assert_equal(want_item.col, got_item.col, "col") || l:retval
call assert_equal(want_item.vcol, got_item.vcol, "vcol") let l:retval = assert_equal(want_item.vcol, got_item.vcol, "vcol") || l:retval
call assert_equal(want_item.nr, got_item.nr, "nr") let l:retval = assert_equal(want_item.nr, got_item.nr, "nr") || l:retval
call assert_equal(want_item.pattern, got_item.pattern, "pattern") let l:retval = assert_equal(want_item.pattern, got_item.pattern, "pattern") || l:retval
call assert_equal(want_item.text, got_item.text, "text") let l:retval = assert_equal(want_item.text, got_item.text, "text") || l:retval
call assert_equal(want_item.type, got_item.type, "type") let l:retval = assert_equal(want_item.type, got_item.type, "type") || l:retval
call assert_equal(want_item.valid, got_item.valid, "valid") let l:retval = assert_equal(want_item.valid, got_item.valid, "valid") || l:retval
endwhile endwhile
return l:retval
endfunc endfunc
" restore Vi compatibility settings " restore Vi compatibility settings

View file

@ -31,14 +31,13 @@ endif
" use a different output, for those we define them directly and modify the " use a different output, for those we define them directly and modify the
" errorformat ourselves. More information at: " errorformat ourselves. More information at:
" http://vimdoc.sourceforge.net/htmldoc/quickfix.html#errorformat " http://vimdoc.sourceforge.net/htmldoc/quickfix.html#errorformat
CompilerSet errorformat =%-G#\ %.%# " Ignore lines beginning with '#' ('# command-line-arguments' line sometimes appears?) CompilerSet errorformat =%-G#\ %.%# " Ignore lines beginning with '#' ('# command-line-arguments' line sometimes appears?)
CompilerSet errorformat+=%-G%.%#panic:\ %m " Ignore lines containing 'panic: message' CompilerSet errorformat+=%-G%.%#panic:\ %m " Ignore lines containing 'panic: message'
CompilerSet errorformat+=%Ecan\'t\ load\ package:\ %m " Start of multiline error string is 'can\'t load package' CompilerSet errorformat+=%Ecan\'t\ load\ package:\ %m " Start of multiline error string is 'can\'t load package'
CompilerSet errorformat+=%A%f:%l:%c:\ %m " Start of multiline unspecified string is 'filename:linenumber:columnnumber:' CompilerSet errorformat+=%A%\\%%(%[%^:]%\\+:\ %\\)%\\?%f:%l:%c:\ %m " Start of multiline unspecified string is 'filename:linenumber:columnnumber:'
CompilerSet errorformat+=%A%f:%l:\ %m " Start of multiline unspecified string is 'filename:linenumber:' CompilerSet errorformat+=%A%\\%%(%[%^:]%\\+:\ %\\)%\\?%f:%l:\ %m " Start of multiline unspecified string is 'filename:linenumber:'
CompilerSet errorformat+=%C%*\\s%m " Continuation of multiline error message is indented CompilerSet errorformat+=%C%*\\s%m " Continuation of multiline error message is indented
CompilerSet errorformat+=%-G%.%# " All lines not matching any of the above patterns are ignored CompilerSet errorformat+=%-G%.%# " All lines not matching any of the above patterns are ignored
let &cpo = s:save_cpo let &cpo = s:save_cpo
unlet s:save_cpo unlet s:save_cpo

File diff suppressed because it is too large Load diff

View file

@ -13,8 +13,8 @@ let b:did_ftplugin = 1
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
let b:undo_ftplugin = "setl fo< com< cms< let b:undo_ftplugin = "setl fo< com< cms<"
\ | exe 'au! vim-go-buffer * <buffer>'" \ . "| exe 'au! vim-go-buffer * <buffer>'"
setlocal formatoptions-=t setlocal formatoptions-=t
@ -25,10 +25,9 @@ setlocal noexpandtab
compiler go compiler go
" Set autocompletion if go#config#CodeCompletionEnabled()
setlocal omnifunc=go#complete#Complete " Set autocompletion
if !go#util#has_job() setlocal omnifunc=go#complete#Complete
setlocal omnifunc=go#complete#GocodeComplete
endif endif
if get(g:, "go_doc_keywordprg_enabled", 1) if get(g:, "go_doc_keywordprg_enabled", 1)
@ -72,43 +71,59 @@ if get(g:, "go_textobj_enabled", 1)
xnoremap <buffer> <silent> [[ :<c-u>call go#textobj#FunctionJump('v', 'prev')<cr> xnoremap <buffer> <silent> [[ :<c-u>call go#textobj#FunctionJump('v', 'prev')<cr>
endif endif
if go#config#AutoTypeInfo() || go#config#AutoSameids()
let &l:updatetime= get(g:, "go_updatetime", 800)
endif
" Autocommands " Autocommands
" ============================================================================ " ============================================================================
" "
augroup vim-go-buffer augroup vim-go-buffer
autocmd! * <buffer> autocmd! * <buffer>
" The file is registered (textDocument/DidOpen) with gopls in in " The file is registered (textDocument/DidOpen) with gopls in plugin/go.vim
" plugin/go.vim on the FileType event. " 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() if go#util#has_job()
autocmd BufWritePost <buffer> call go#lsp#DidChange(expand('<afile>:p')) autocmd BufWritePost,FileChangedShellPost <buffer> call go#lsp#DidChange(expand('<afile>:p'))
autocmd FileChangedShell <buffer> call go#lsp#DidChange(expand('<afile>:p'))
autocmd BufDelete <buffer> call go#lsp#DidClose(expand('<afile>:p')) autocmd BufDelete <buffer> call go#lsp#DidClose(expand('<afile>:p'))
endif endif
autocmd CursorHold <buffer> call go#auto#auto_type_info() " send the textDocument/didChange notification when idle. go#lsp#DidChange
autocmd CursorHold <buffer> call go#auto#auto_sameids() " will not send an event if the buffer hasn't changed since the last
" notification.
autocmd CursorHold,CursorHoldI <buffer> call go#lsp#DidChange(expand('<afile>:p'))
autocmd BufEnter,CursorHold <buffer> call go#auto#update_autocmd()
" Echo the identifier information when completion is done. Useful to see " Echo the identifier information when completion is done. Useful to see
" the signature of a function, etc... " the signature of a function, etc...
if exists('##CompleteDone') if exists('##CompleteDone')
autocmd CompleteDone <buffer> call go#auto#echo_go_info() autocmd CompleteDone <buffer> call go#auto#complete_done()
endif endif
autocmd BufWritePre <buffer> call go#auto#fmt_autosave() autocmd BufWritePre <buffer> call go#auto#fmt_autosave()
autocmd BufWritePost <buffer> call go#auto#metalinter_autosave() autocmd BufWritePost <buffer> call go#auto#metalinter_autosave()
" clear SameIds when the buffer is unloaded so that loading another buffer if !has('textprop')
" in the same window doesn't highlight the most recently matched "TODO(bc): how to clear sameids and diagnostics when a non-go buffer is
" identifier's positions. " loaded into a window and the previously loaded buffer is still loaded in
autocmd BufWinEnter <buffer> call go#guru#ClearSameIds() " another window?
" TODO(bc): only clear when the new buffer isn't the old buffer
" clear SameIds when the buffer is unloaded from its last window so that
" loading another buffer (especially of a different filetype) in the same
" window doesn't highlight the most recently matched identifier's positions.
autocmd BufWinLeave <buffer> call go#guru#ClearSameIds()
" clear SameIds when a new buffer is loaded in the window so that the
" previous buffer's highlighting isn't used.
autocmd BufWinEnter <buffer> call go#guru#ClearSameIds()
" clear diagnostics when the buffer is unloaded from its last window so that
" loading another buffer (especially of a different filetype) in the same
" window doesn't highlight the previously loaded buffer's diagnostics.
autocmd BufWinLeave <buffer> call go#lsp#ClearDiagnosticHighlights()
" clear diagnostics when a new buffer is loaded in the window so that the
" previous buffer's diagnostics aren't used.
"autocmd BufWinEnter <buffer> call go#lsp#ClearDiagnosticHighlights()
endif
autocmd BufEnter <buffer> autocmd BufEnter <buffer>
\ if go#config#AutodetectGopath() && !exists('b:old_gopath') \ if go#config#AutodetectGopath() && !exists('b:old_gopath')

View file

@ -3,7 +3,7 @@ command! -nargs=? -complete=customlist,go#rename#Complete GoRename call go#renam
" -- guru " -- guru
command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope(<f-args>) command! -nargs=* -complete=customlist,go#package#Complete GoGuruScope call go#guru#Scope(<f-args>)
command! -range=% GoImplements call go#guru#Implements(<count>) command! -range=% GoImplements call go#implements#Implements(<count>)
command! -range=% GoPointsTo call go#guru#PointsTo(<count>) command! -range=% GoPointsTo call go#guru#PointsTo(<count>)
command! -range=% GoWhicherrs call go#guru#Whicherrs(<count>) command! -range=% GoWhicherrs call go#guru#Whicherrs(<count>)
command! -range=% GoCallees call go#guru#Callees(<count>) command! -range=% GoCallees call go#guru#Callees(<count>)
@ -12,7 +12,7 @@ command! -range=% GoCallers call go#guru#Callers(<count>)
command! -range=% GoCallstack call go#guru#Callstack(<count>) command! -range=% GoCallstack call go#guru#Callstack(<count>)
command! -range=% GoFreevars call go#guru#Freevars(<count>) command! -range=% GoFreevars call go#guru#Freevars(<count>)
command! -range=% GoChannelPeers call go#guru#ChannelPeers(<count>) command! -range=% GoChannelPeers call go#guru#ChannelPeers(<count>)
command! -range=% GoReferrers call go#guru#Referrers(<count>) command! -range=% GoReferrers call go#referrers#Referrers(<count>)
command! -range=0 GoSameIds call go#guru#SameIds(1) command! -range=0 GoSameIds call go#guru#SameIds(1)
command! -range=0 GoSameIdsClear call go#guru#ClearSameIds() command! -range=0 GoSameIdsClear call go#guru#ClearSameIds()
@ -105,8 +105,9 @@ command! -nargs=0 GoFillStruct call go#fillstruct#FillStruct()
" -- debug " -- debug
if !exists(':GoDebugStart') if !exists(':GoDebugStart')
command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(0, <f-args>) command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start('debug', <f-args>)
command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start(1, <f-args>) command! -nargs=* -complete=customlist,go#package#Complete GoDebugTest call go#debug#Start('test', <f-args>)
command! -nargs=1 GoDebugAttach call go#debug#Start('attach', <f-args>)
command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>) command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
endif endif
@ -116,4 +117,12 @@ command! -nargs=0 GoReportGitHubIssue call go#issue#New()
" -- iferr " -- iferr
command! -nargs=0 GoIfErr call go#iferr#Generate() command! -nargs=0 GoIfErr call go#iferr#Generate()
" -- lsp
command! -nargs=+ -complete=dir GoAddWorkspace call go#lsp#AddWorkspaceDirectory(<f-args>)
command! -nargs=0 GoLSPDebugBrowser call go#lsp#DebugBrowser()
command! -nargs=* -bang GoDiagnostics call go#lint#Diagnostics(<bang>0, <f-args>)
" -- term
command! GoToggleTermCloseOnExit call go#term#ToggleCloseOnExit()
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -11,7 +11,7 @@ endif
" Some handy plug mappings " Some handy plug mappings
nnoremap <silent> <Plug>(go-run) :<C-u>call go#cmd#Run(!g:go_jump_to_error)<CR> nnoremap <silent> <Plug>(go-run) :<C-u>call go#cmd#Run(!g:go_jump_to_error)<CR>
if has("nvim") if has("nvim") || has("terminal")
nnoremap <silent> <Plug>(go-run-vertical) :<C-u>call go#cmd#RunTerm(!g:go_jump_to_error, 'vsplit', [])<CR> nnoremap <silent> <Plug>(go-run-vertical) :<C-u>call go#cmd#RunTerm(!g:go_jump_to_error, 'vsplit', [])<CR>
nnoremap <silent> <Plug>(go-run-split) :<C-u>call go#cmd#RunTerm(!g:go_jump_to_error, 'split', [])<CR> nnoremap <silent> <Plug>(go-run-split) :<C-u>call go#cmd#RunTerm(!g:go_jump_to_error, 'split', [])<CR>
nnoremap <silent> <Plug>(go-run-tab) :<C-u>call go#cmd#RunTerm(!g:go_jump_to_error, 'tabe', [])<CR> nnoremap <silent> <Plug>(go-run-tab) :<C-u>call go#cmd#RunTerm(!g:go_jump_to_error, 'tabe', [])<CR>
@ -35,14 +35,14 @@ nnoremap <silent> <Plug>(go-info) :<C-u>call go#tool#Info(1)<CR>
nnoremap <silent> <Plug>(go-import) :<C-u>call go#import#SwitchImport(1, '', expand('<cword>'), '')<CR> nnoremap <silent> <Plug>(go-import) :<C-u>call go#import#SwitchImport(1, '', expand('<cword>'), '')<CR>
nnoremap <silent> <Plug>(go-imports) :<C-u>call go#fmt#Format(1)<CR> nnoremap <silent> <Plug>(go-imports) :<C-u>call go#fmt#Format(1)<CR>
nnoremap <silent> <Plug>(go-implements) :<C-u>call go#guru#Implements(-1)<CR> nnoremap <silent> <Plug>(go-implements) :<C-u>call go#implements#Implements(-1)<CR>
nnoremap <silent> <Plug>(go-callees) :<C-u>call go#guru#Callees(-1)<CR> nnoremap <silent> <Plug>(go-callees) :<C-u>call go#guru#Callees(-1)<CR>
nnoremap <silent> <Plug>(go-callers) :<C-u>call go#guru#Callers(-1)<CR> nnoremap <silent> <Plug>(go-callers) :<C-u>call go#guru#Callers(-1)<CR>
nnoremap <silent> <Plug>(go-describe) :<C-u>call go#guru#Describe(-1)<CR> nnoremap <silent> <Plug>(go-describe) :<C-u>call go#guru#Describe(-1)<CR>
nnoremap <silent> <Plug>(go-callstack) :<C-u>call go#guru#Callstack(-1)<CR> nnoremap <silent> <Plug>(go-callstack) :<C-u>call go#guru#Callstack(-1)<CR>
xnoremap <silent> <Plug>(go-freevars) :<C-u>call go#guru#Freevars(0)<CR> xnoremap <silent> <Plug>(go-freevars) :<C-u>call go#guru#Freevars(0)<CR>
nnoremap <silent> <Plug>(go-channelpeers) :<C-u>call go#guru#ChannelPeers(-1)<CR> nnoremap <silent> <Plug>(go-channelpeers) :<C-u>call go#guru#ChannelPeers(-1)<CR>
nnoremap <silent> <Plug>(go-referrers) :<C-u>call go#guru#Referrers(-1)<CR> nnoremap <silent> <Plug>(go-referrers) :<C-u>call go#referrers#Referrers(-1)<CR>
nnoremap <silent> <Plug>(go-sameids) :<C-u>call go#guru#SameIds(1)<CR> nnoremap <silent> <Plug>(go-sameids) :<C-u>call go#guru#SameIds(1)<CR>
nnoremap <silent> <Plug>(go-pointsto) :<C-u>call go#guru#PointsTo(-1)<CR> nnoremap <silent> <Plug>(go-pointsto) :<C-u>call go#guru#PointsTo(-1)<CR>
nnoremap <silent> <Plug>(go-whicherrs) :<C-u>call go#guru#Whicherrs(-1)<CR> nnoremap <silent> <Plug>(go-whicherrs) :<C-u>call go#guru#Whicherrs(-1)<CR>
@ -83,4 +83,6 @@ nnoremap <silent> <Plug>(go-alternate-split) :<C-u>call go#alternate#Switch(0, "
nnoremap <silent> <Plug>(go-iferr) :<C-u>call go#iferr#Generate()<CR> nnoremap <silent> <Plug>(go-iferr) :<C-u>call go#iferr#Generate()<CR>
nnoremap <silent> <Plug>(go-diagnostics) :<C-u>call go#lint#Diagnostics(!g:go_jump_to_error)<CR>
" vim: sw=2 ts=2 et " vim: sw=2 ts=2 et

View file

@ -52,19 +52,11 @@ endfunction
let s:engine = go#config#SnippetEngine() let s:engine = go#config#SnippetEngine()
if s:engine is? "automatic" if s:engine is? 'ultisnips'
if get(g:, 'did_plugin_ultisnips') is 1
call s:GoUltiSnips()
elseif get(g:, 'loaded_neosnippet') is 1
call s:GoNeosnippet()
elseif get(g:, 'loaded_minisnip') is 1
call s:GoMinisnip()
endif
elseif s:engine is? "ultisnips"
call s:GoUltiSnips() call s:GoUltiSnips()
elseif s:engine is? "neosnippet" elseif s:engine is? 'neosnippet'
call s:GoNeosnippet() call s:GoNeosnippet()
elseif s:engine is? "minisnip" elseif s:engine is? 'minisnip'
call s:GoMinisnip() call s:GoMinisnip()
endif endif

View file

@ -144,20 +144,42 @@ else {
endsnippet endsnippet
# if inline error # if inline error
snippet ife "If with inline erro" snippet ife "If with inline error"
if err := ${1:condition}; err != nil { if err := ${1:condition}; err != nil {
${0:${VISUAL}} ${0:${VISUAL}}
} }
endsnippet endsnippet
snippet ew "errors.Wrap"
errors.Wrap(${1:err}, "${2:message}")
endsnippet
snippet ewf "errors.Wrapf"
errors.Wrapf(${1:err}, "${2:message %v}", ${3:args...})
endsnippet
# error snippet # error snippet
snippet errn "Error return " !b snippet errn "Error return" !b
if err != nil { if err != nil {
return err return err
} }
${0} ${0}
endsnippet endsnippet
snippet errnw "Error return wrap" !b
if err != nil {
return errors.Wrap(err, "${1:message}")
}
${0}
endsnippet
snippet errnwf "Error return wrapf" !b
if err != nil {
return errors.Wrapf(err, "${1:message %v}", ${2:args...})
}
${0}
endsnippet
# error log snippet # error log snippet
snippet errl "Error with log.Fatal(err)" !b snippet errl "Error with log.Fatal(err)" !b
if err != nil { if err != nil {
@ -174,6 +196,20 @@ if err != nil {
${0} ${0}
endsnippet endsnippet
snippet errn,w "Error return wrap with two return values" !b
if err != nil {
return nil, errors.Wrap(err, "${1:message}")
}
${0}
endsnippet
snippet errn,wf "Error return wrapf with two return values" !b
if err != nil {
return nil, errors.Wrapf(err, "${1:message %v}", ${2:args...})
}
${0}
endsnippet
# error panic # error panic
snippet errp "Error panic" !b snippet errp "Error panic" !b
if err != nil { if err != nil {
@ -235,6 +271,20 @@ for ${2:k}, ${3:v} := range ${1} {
} }
endsnippet endsnippet
snippet forsel "for select"
for {
select {
case ${2:${1:result} := }<- ${3:channel}:
${0}
}
}
endsnippet
snippet selc "select case" !b
case ${1:${2:var} := }<-${3:channel}:
${0}
endsnippet
# function # function
snippet func "func Function(...) [error] { ... }" snippet func "func Function(...) [error] { ... }"
func ${1:name}(${2:params})${3/(.+)/ /}`!p opening_par(snip, 3)`$3`!p closing_par(snip, 3)` { func ${1:name}(${2:params})${3/(.+)/ /}`!p opening_par(snip, 3)`$3`!p closing_par(snip, 3)` {
@ -244,7 +294,12 @@ endsnippet
# Fmt Printf debug # Fmt Printf debug
snippet ff "fmt.Printf(...)" snippet ff "fmt.Printf(...)"
fmt.Printf("${1:${VISUAL}} = %+v\n", $1) fmt.Printf("$1 = %+v\n", ${1:${VISUAL}})
endsnippet
# Fmt Printf debug with hash
snippet ffh "fmt.Printf(#...) hash"
fmt.Printf("$1 = %#v\n", ${1:${VISUAL}})
endsnippet endsnippet
# Fmt Println debug # Fmt Println debug
@ -257,6 +312,18 @@ snippet fe "fmt.Errorf(...)"
fmt.Errorf("${1:${VISUAL}}") fmt.Errorf("${1:${VISUAL}}")
endsnippet endsnippet
# Fmt Errorf wrap
snippet few "fmt.Errorf(%w, err)"
fmt.Errorf("${1:message}: %w", ${2:${VISUAL:err}})
endsnippet
# Fmt Errorf wrap and return
snippet errnfw "Error return fmt.Errorf(%w, err)" !b
if ${1:${VISUAL:err}} != nil {
return fmt.Errorf("${2:message}: %w", $1)
}
endsnippet
# log printf # log printf
snippet lf "log.Printf(...)" snippet lf "log.Printf(...)"
log.Printf("${1:${VISUAL}} = %+v\n", $1) log.Printf("${1:${VISUAL}} = %+v\n", $1)
@ -326,7 +393,7 @@ endsnippet
# struct # struct
snippet st "type T struct { ... }" snippet st "type T struct { ... }"
type ${1:Type} struct { type ${1:Type} struct {
${0} ${0}
} }
endsnippet endsnippet
@ -338,6 +405,12 @@ case ${2:value1}:
} }
endsnippet endsnippet
snippet tswitch "type switch x { ... }"
switch ${2:$1 := }${1:v}.(type) {
${0}
}
endsnippet
# sprintf # sprintf
snippet sp "fmt.Sprintf(...)" snippet sp "fmt.Sprintf(...)"
fmt.Sprintf("%${1:s}", ${2:var}) fmt.Sprintf("%${1:s}", ${2:var})
@ -385,7 +458,7 @@ for _, tt := range tests {
endsnippet endsnippet
snippet hf "http.HandlerFunc" !b snippet hf "http.HandlerFunc"
func ${1:handler}(w http.ResponseWriter, r *http.Request) { func ${1:handler}(w http.ResponseWriter, r *http.Request) {
${0:fmt.Fprintf(w, "hello world")} ${0:fmt.Fprintf(w, "hello world")}
} }

View file

@ -9,30 +9,22 @@ let s:cpo_save = &cpo
set cpo&vim set cpo&vim
function! s:checkVersion() abort 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 let l:unsupported = 0
if go#config#VersionWarning() != 0 if go#config#VersionWarning() != 0
if has('nvim') if has('nvim')
let l:unsupported = !has('nvim-0.3.1') let l:unsupported = !has('nvim-0.4.0')
else else
let l:unsupported = (v:version < 704 || (v:version == 704 && !has('patch2009'))) let l:unsupported = !has('patch-8.0.1453')
endif endif
if l:unsupported == 1 if l:unsupported == 1
echohl Error echohl Error
echom "vim-go requires Vim 7.4.2009 or Neovim 0.3.1, but you're using an older version." echom "vim-go requires at least Vim 8.0.1453 or Neovim 0.4.0, but you're using an older version."
echom "Please update your Vim for the best vim-go experience." 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 "If you really want to continue you can set this to make the error go away:"
echom " let g:go_version_warning = 0" echom " let g:go_version_warning = 0"
echom "Note that some features may error out or behave incorrectly." 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." echom "Please do not report bugs unless you're using at least Vim 8.0.1453 or Neovim 0.4.0."
echohl None echohl None
" Make sure people see this. " Make sure people see this.
@ -45,28 +37,26 @@ call s:checkVersion()
" these packages are used by vim-go and can be automatically installed if " 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.
" NOTE(bc): varying the binary name and the tail of the import path does not yet work in module aware mode.
let s:packages = { let s:packages = {
\ 'asmfmt': ['github.com/klauspost/asmfmt/cmd/asmfmt'], \ 'asmfmt': ['github.com/klauspost/asmfmt/cmd/asmfmt@master'],
\ 'dlv': ['github.com/go-delve/delve/cmd/dlv'], \ 'dlv': ['github.com/go-delve/delve/cmd/dlv@master'],
\ 'errcheck': ['github.com/kisielk/errcheck'], \ 'errcheck': ['github.com/kisielk/errcheck@master'],
\ 'fillstruct': ['github.com/davidrjenni/reftools/cmd/fillstruct'], \ 'fillstruct': ['github.com/davidrjenni/reftools/cmd/fillstruct@master'],
\ 'gocode': ['github.com/mdempsky/gocode', {'windows': ['-ldflags', '-H=windowsgui']}], \ 'godef': ['github.com/rogpeppe/godef@master'],
\ 'gocode-gomod': ['github.com/stamblerre/gocode'], \ 'goimports': ['golang.org/x/tools/cmd/goimports@master'],
\ 'godef': ['github.com/rogpeppe/godef'], \ 'golint': ['golang.org/x/lint/golint@master'],
\ 'gogetdoc': ['github.com/zmb3/gogetdoc'], \ 'gopls': ['golang.org/x/tools/gopls@latest', {}, {'after': function('go#lsp#Restart', [])}],
\ 'goimports': ['golang.org/x/tools/cmd/goimports'], \ 'golangci-lint': ['github.com/golangci/golangci-lint/cmd/golangci-lint@master'],
\ 'golint': ['golang.org/x/lint/golint'], \ 'gomodifytags': ['github.com/fatih/gomodifytags@master'],
\ 'gopls': ['golang.org/x/tools/cmd/gopls'], \ 'gorename': ['golang.org/x/tools/cmd/gorename@master'],
\ 'gometalinter': ['github.com/alecthomas/gometalinter'], \ 'gotags': ['github.com/jstemmer/gotags@master'],
\ 'golangci-lint': ['github.com/golangci/golangci-lint/cmd/golangci-lint'], \ 'guru': ['golang.org/x/tools/cmd/guru@master'],
\ 'gomodifytags': ['github.com/fatih/gomodifytags'], \ 'impl': ['github.com/josharian/impl@master'],
\ 'gorename': ['golang.org/x/tools/cmd/gorename'], \ 'keyify': ['honnef.co/go/tools/cmd/keyify@master'],
\ 'gotags': ['github.com/jstemmer/gotags'], \ 'motion': ['github.com/fatih/motion@master'],
\ 'guru': ['golang.org/x/tools/cmd/guru'], \ 'iferr': ['github.com/koron/iferr@master'],
\ 'impl': ['github.com/josharian/impl'],
\ 'keyify': ['honnef.co/go/tools/cmd/keyify'],
\ 'motion': ['github.com/fatih/motion'],
\ 'iferr': ['github.com/koron/iferr'],
\ } \ }
" These commands are available on any filetypes " These commands are available on any filetypes
@ -90,22 +80,21 @@ function! s:GoInstallBinaries(updateBinaries, ...)
endif endif
if go#path#Default() == "" if go#path#Default() == ""
echohl Error call go#util#EchoError('$GOPATH is not set and `go env GOPATH` returns empty')
echomsg "vim.go: $GOPATH is not set and 'go env GOPATH' returns empty"
echohl None
return return
endif endif
let go_bin_path = go#path#BinPath() let go_bin_path = go#path#BinPath()
" change $GOBIN so go get can automatically install to it let [l:goos, l:goarch] = go#util#hostosarch()
let $GOBIN = go_bin_path let Restore_goos = go#util#SetEnv('GOOS', l:goos)
let Restore_goarch = go#util#SetEnv('GOARCH', l:goarch)
" old_path is used to restore users own path " change $GOBIN so go get can automatically install to it
let old_path = $PATH let Restore_gobin = go#util#SetEnv('GOBIN', go_bin_path)
" vim's executable path is looking in PATH so add our go_bin path to it " vim's executable path is looking in PATH so add our go_bin path to it
let $PATH = go_bin_path . go#util#PathListSep() . $PATH let Restore_path = go#util#SetEnv('PATH', go_bin_path . go#util#PathListSep() . $PATH)
" when shellslash is set on MS-* systems, shellescape puts single quotes " when shellslash is set on MS-* systems, shellescape puts single quotes
" around the output string. cmd on Windows does not handle single quotes " around the output string. cmd on Windows does not handle single quotes
@ -117,10 +106,7 @@ function! s:GoInstallBinaries(updateBinaries, ...)
set noshellslash set noshellslash
endif endif
let l:dl_cmd = ['go', 'get', '-v', '-d'] let l:get_base_cmd = ['go', 'get', '-v']
if get(g:, "go_get_update", 1) != 0
let l:dl_cmd += ['-u']
endif
" Filter packages from arguments (if any). " Filter packages from arguments (if any).
let l:packages = {} let l:packages = {}
@ -142,50 +128,103 @@ function! s:GoInstallBinaries(updateBinaries, ...)
let l:platform = 'windows' let l:platform = 'windows'
endif endif
for [binary, pkg] in items(l:packages) let l:oldmore = &more
let l:importPath = pkg[0] let &more = 0
let l:run_cmd = copy(l:dl_cmd) for [l:binary, l:pkg] in items(l:packages)
if len(l:pkg) > 1 && get(l:pkg[1], l:platform, '') isnot '' let l:importPath = l:pkg[0]
let l:run_cmd += get(l:pkg[1], l:platform, '')
endif
let bin_setting_name = "go_" . binary . "_bin" " TODO(bc): how to support this with modules? Do we have to clone and then
" install manually? Probably not. I suspect that we can just use GOPATH
" mode and then do the legacy method.
let bin_setting_name = "go_" . l:binary . "_bin"
if exists("g:{bin_setting_name}") if exists("g:{bin_setting_name}")
let bin = g:{bin_setting_name} let bin = g:{bin_setting_name}
else else
if go#util#IsWin() if go#util#IsWin()
let bin = binary . '.exe' let bin = l:binary . '.exe'
else else
let bin = binary let bin = l:binary
endif endif
endif endif
if !executable(bin) || a:updateBinaries == 1 if !executable(bin) || a:updateBinaries == 1
if a:updateBinaries == 1 if a:updateBinaries == 1
echo "vim-go: Updating " . binary . ". Reinstalling ". importPath . " to folder " . go_bin_path echo "vim-go: Updating " . l:binary . ". Reinstalling ". importPath . " to folder " . go_bin_path
else else
echo "vim-go: ". binary ." not found. Installing ". importPath . " to folder " . go_bin_path echo "vim-go: ". l:binary ." not found. Installing ". importPath . " to folder " . go_bin_path
endif endif
" first download the binary if l:importPath =~ "@"
let [l:out, l:err] = go#util#Exec(l:run_cmd + [l:importPath]) let Restore_modules = go#util#SetEnv('GO111MODULE', 'on')
if l:err let l:tmpdir = go#util#tempdir('vim-go')
echom "Error downloading " . l:importPath . ": " . l:out let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd '
let l:dir = getcwd()
try
execute l:cd . fnameescape(l:tmpdir)
let l:get_cmd = copy(l:get_base_cmd)
if len(l:pkg) > 1 && get(l:pkg[1], l:platform, []) isnot []
let l:get_cmd += get(l:pkg[1], l:platform, [])
endif
" TODO(bc): how to install the bin to a different name than the
" binary path? go get does not support -o
" let l:get_cmd += ['-o', printf('%s%s%s', go_bin_path, go#util#PathSep(), bin)]
let [l:out, l:err] = go#util#Exec(l:get_cmd + [l:importPath])
if l:err
call go#util#EchoError(printf('Error installing %s: %s', l:importPath, l:out))
endif
call call(Restore_modules, [])
finally
execute l:cd . fnameescape(l:dir)
endtry
call call(Restore_modules, [])
else
let l:get_cmd = copy(l:get_base_cmd)
let l:get_cmd += ['-d']
if get(g:, "go_get_update", 1) != 0
let l:get_cmd += ['-u']
endif
let Restore_modules = go#util#SetEnv('GO111MODULE', 'off')
" first download the binary
let [l:out, l:err] = go#util#Exec(l:get_cmd + [l:importPath])
if l:err
call go#util#EchoError(printf('Error downloading %s: %s', l:importPath, l:out))
endif
" and then build and install it
let l:build_cmd = ['go', 'build']
if len(l:pkg) > 1 && get(l:pkg[1], l:platform, []) isnot []
let l:build_cmd += get(l:pkg[1], l:platform, [])
endif
let l:build_cmd += ['-o', printf('%s%s%s', go_bin_path, go#util#PathSep(), bin), l:importPath]
let [l:out, l:err] = go#util#Exec(l:build_cmd)
if l:err
call go#util#EchoError(printf('Error installing %s: %s', l:importPath, l:out))
endif
call call(Restore_modules, [])
endif endif
" and then build and install it if len(l:pkg) > 2
let l:build_cmd = ['go', 'build', '-o', go_bin_path . go#util#PathSep() . bin, l:importPath] call call(get(l:pkg[2], 'after', function('s:noop', [])), [])
let [l:out, l:err] = go#util#Exec(l:build_cmd)
if l:err
echom "Error installing " . l:importPath . ": " . l:out
endif endif
endif endif
endfor endfor
" restore back! " restore back!
let $PATH = old_path call call(Restore_path, [])
call call(Restore_gobin, [])
call call(Restore_goarch, [])
call call(Restore_goos, [])
if resetshellslash if resetshellslash
set shellslash set shellslash
endif endif
@ -195,18 +234,20 @@ function! s:GoInstallBinaries(updateBinaries, ...)
else else
call go#util#EchoInfo('installing finished!') call go#util#EchoInfo('installing finished!')
endif endif
let &more = l:oldmore
endfunction endfunction
" CheckBinaries checks if the necessary binaries to install the Go tool " CheckBinaries checks if the necessary binaries to install the Go tool
" commands are available. " commands are available.
function! s:CheckBinaries() function! s:CheckBinaries()
if !executable('go') if !executable('go')
echohl Error | echomsg "vim-go: go executable not found." | echohl None call go#util#EchoError('go executable not found.')
return -1 return -1
endif endif
if !executable('git') if !executable('git')
echohl Error | echomsg "vim-go: git executable not found." | echohl None call go#util#EchoError('git executable not found.')
return -1 return -1
endif endif
endfunction endfunction
@ -239,7 +280,15 @@ function! s:register()
return return
endif endif
let l:RestoreGopath = function('s:noop')
if go#config#AutodetectGopath()
let l:RestoreGopath = go#util#SetEnv('GOPATH', go#path#Detect())
endif
call go#lsp#DidOpen(expand('<afile>:p')) call go#lsp#DidOpen(expand('<afile>:p'))
call call(l:RestoreGopath, [])
endfunction
function! s:noop(...) abort
endfunction endfunction
augroup vim-go augroup vim-go

2
pack/acp/start/vim-go/scripts/bench-syntax Normal file → Executable file
View file

@ -13,7 +13,7 @@ cd "$vimgodir"
if [ -z "${1:-}" ]; then if [ -z "${1:-}" ]; then
echo "unknown version: '${1:-}'" echo "unknown version: '${1:-}'"
echo "First argument must be 'vim-7.4', 'vim-8.0', or 'nvim'." echo "First argument must be 'vim-8.0' or 'nvim'."
exit 1 exit 1
fi fi

2
pack/acp/start/vim-go/scripts/docker-test Normal file → Executable file
View file

@ -10,6 +10,6 @@ cd "$vimgodir"
docker build --tag vim-go-test . docker build --tag vim-go-test .
# seccomp=confined is required for dlv to run in a container, hence it's # seccomp=confined is required for dlv to run in a container, hence it's
# required for vim-go's debug tests. # required for vim-go's debug tests.
docker run --rm --security-opt="seccomp=unconfined" vim-go-test docker run -e VIMS --rm --security-opt="seccomp=unconfined" vim-go-test
# vim:ts=2:sts=2:sw=2:et # vim:ts=2:sts=2:sw=2:et

View file

@ -0,0 +1,35 @@
#!/bin/sh
#
# Install and setup a Vim or Neovim for running tests.
# This should work on both GitHub Actions and people's desktop computers, and
# be 100% independent from any system installed Vim.
#
set -euC
vimgodir=$(cd -P "$(dirname "$0")/.." > /dev/null && pwd)
cd "$vimgodir"
vim=${1:-}
installdir="/tmp/vim-go-test/$1-install"
# 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'
echo "Installing lint tools"
(
mkdir -p "$installdir/share/vim/vimgo/pack/vim-go/start/"
cd "$installdir/share/vim/vimgo/pack/vim-go/start/"
[ -d "vim-vimhelplint" ] || git clone --depth 1 --quiet https://github.com/machakann/vim-vimhelplint
[ -d "vim-vimlparser" ] || git clone --depth 1 --quiet https://github.com/ynkdir/vim-vimlparser
[ -d "vim-vimlint" ] || git clone --depth 1 --quiet https://github.com/syngan/vim-vimlint
)
echo "vim-go tools installed to: $installdir/share/vim/vimgo/pack/vim-go/start"
# vim:ts=2:sts=2:sw=2:et

44
pack/acp/start/vim-go/scripts/install-vim Normal file → Executable file
View file

@ -1,8 +1,8 @@
#!/bin/sh #!/bin/sh
# #
# Install and setup a Vim or Neovim for running tests. # Install and setup a Vim or Neovim for running tests.
# This should work on both Travis and people's desktop computers, and be 100% # This should work on both GitHub Actions and people's desktop computers, and
# independent from any system installed Vim. # be 100% independent from any system installed Vim.
# #
# It will echo the full path to a Vim binary, e.g.: # It will echo the full path to a Vim binary, e.g.:
# /some/path/src/vim # /some/path/src/vim
@ -15,28 +15,32 @@ cd "$vimgodir"
vim=${1:-} vim=${1:-}
case "$vim" in case "$vim" in
"vim-7.4") "vim-8.0")
tag="v7.4.2009" # This follows the version in Ubuntu LTS. 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.1453"
giturl="https://github.com/vim/vim" giturl="https://github.com/vim/vim"
;; ;;
"vim-8.0") "vim-8.2")
# This follows the version in Arch Linux. Vim's master branch isn't always # This is the version that's installed by homebrew currently. It doesn't
# stable, and we don't want to have the build fail because Vim introduced a # have to stay up to date with homebrew, and is only chosen here because
# bug. # that's what homebrew was using at the the time and we need a version to
tag="v8.0.1542" # vimlint with.
tag="v8.2.0200"
giturl="https://github.com/vim/vim" giturl="https://github.com/vim/vim"
;; ;;
"nvim") "nvim")
# Use latest stable version. # Use latest stable version.
tag="v0.3.1" tag="v0.4.0"
giturl="https://github.com/neovim/neovim" giturl="https://github.com/neovim/neovim"
;; ;;
*) *)
echo "unknown version: '${1:-}'" echo "unknown version: '${1:-}'"
echo "First argument must be 'vim-7.4', 'vim-8.0', or 'nvim'." echo "First argument must be 'vim-8.0', vim-8.2, or 'nvim'."
exit 1 exit 1
;; ;;
esac esac
@ -62,7 +66,7 @@ cd "$srcdir"
if [ "$1" = "nvim" ]; then if [ "$1" = "nvim" ]; then
# TODO: Use macOS binaries on macOS # TODO: Use macOS binaries on macOS
curl -Ls https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz | curl -Ls https://github.com/neovim/neovim/releases/download/$tag/nvim-linux64.tar.gz |
tar xzf - -C /tmp/vim-go-test/ tar xzf - -C /tmp/vim-go-test/
mv /tmp/vim-go-test/nvim-linux64 /tmp/vim-go-test/nvim-install mv /tmp/vim-go-test/nvim-linux64 /tmp/vim-go-test/nvim-install
mkdir -p "$installdir/share/nvim/runtime/pack/vim-go/start" mkdir -p "$installdir/share/nvim/runtime/pack/vim-go/start"
@ -88,22 +92,6 @@ else
ln -s "$vimgodir" "$installdir/share/vim/vimgo/pack/vim-go/start/vim-go" ln -s "$vimgodir" "$installdir/share/vim/vimgo/pack/vim-go/start/vim-go"
fi 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'
echo "Installing lint tools"
(
mkdir -p "$installdir/share/vim/vimgo/pack/vim-go/start/"
cd "$installdir/share/vim/vimgo/pack/vim-go/start/"
[ -d "vim-vimhelplint" ] || git clone --depth 1 --quiet https://github.com/machakann/vim-vimhelplint
[ -d "vim-vimlparser" ] || git clone --depth 1 --quiet https://github.com/ynkdir/vim-vimlparser
[ -d "vim-vimlint" ] || git clone --depth 1 --quiet https://github.com/syngan/vim-vimlint
)
# Don't really need source after successful install. # Don't really need source after successful install.
rm -rf "$srcdir" rm -rf "$srcdir"

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